Repository: lastfm/lastfm-desktop Branch: master Commit: f5962d15dd30 Files: 964 Total size: 11.7 MB Directory structure: gitextract_3180lsex/ ├── .gitignore ├── .gitmodules ├── COPYING ├── Last.fm.pro ├── README.md ├── admin/ │ ├── Doxyfile │ ├── dist/ │ │ ├── mac/ │ │ │ ├── AppleScriptSuite.sdef │ │ │ ├── Growl Registration Ticket.growlRegDict │ │ │ ├── Standard.plist │ │ │ ├── build-release.rb │ │ │ ├── bundleFrameworks.sh │ │ │ ├── dacp.fm.last.Scrobbler.scpt │ │ │ ├── dsa_pub.pem │ │ │ ├── fm.last.Scrobbler.scpt │ │ │ └── sign_update.rb │ │ ├── updates/ │ │ │ ├── Changelog.txt │ │ │ ├── Mac/ │ │ │ │ └── updates.xml │ │ │ ├── Win/ │ │ │ │ └── updates.xml │ │ │ ├── updates.html │ │ │ ├── updates.linux.xml │ │ │ ├── updates.mac.beta.xml │ │ │ ├── updates.mac.xml │ │ │ ├── updates.win.beta.xml │ │ │ └── updates.win.xml │ │ └── win/ │ │ ├── Last.fm.iss │ │ ├── build-release-win.pl │ │ ├── isspp │ │ └── wix/ │ │ ├── boffin.wxs │ │ ├── client.wxs │ │ ├── eula.rtf │ │ ├── qt.conf │ │ ├── wixboff.cmd │ │ └── wixclient.cmd │ ├── include.qmake │ ├── qmake/ │ │ ├── 1stparty.pro.inc │ │ ├── 3rdparty.pro.inc │ │ ├── QtOverride.pro.inc │ │ └── debug.pro.inc │ └── tests/ │ ├── QtTest_to_JUnit.xslt │ ├── log_test.sh │ └── run_tests.sh ├── app/ │ ├── boffin/ │ │ ├── App.cpp │ │ ├── App.h │ │ ├── HistoryWidget.cpp │ │ ├── HistoryWidget.h │ │ ├── Info.plist.in │ │ ├── LocalCollectionScanner.cpp │ │ ├── LocalCollectionScanner.h │ │ ├── MainWindow.cpp │ │ ├── MainWindow.h │ │ ├── MediaPipeline.cpp │ │ ├── MediaPipeline.h │ │ ├── PickDirsDialog.cpp │ │ ├── PickDirsDialog.h │ │ ├── PlaydarHostsModel.cpp │ │ ├── PlaydarHostsModel.h │ │ ├── PlaydarTagCloudModel.cpp │ │ ├── PlaydarTagCloudModel.h │ │ ├── Playlist.cpp │ │ ├── Playlist.h │ │ ├── PlaylistModel.cpp │ │ ├── PlaylistModel.h │ │ ├── PlaylistWidget.h │ │ ├── ScanProgressWidget.cpp │ │ ├── ScanProgressWidget.h │ │ ├── ScrobSocket.cpp │ │ ├── ScrobSocket.h │ │ ├── Shuffler.cpp │ │ ├── Shuffler.h │ │ ├── TagBrowserWidget.cpp │ │ ├── TagBrowserWidget.h │ │ ├── TagCloudView.cpp │ │ ├── TagCloudView.h │ │ ├── TagDelegate.cpp │ │ ├── TagDelegate.h │ │ ├── TrackSource.cpp │ │ ├── TrackSource.h │ │ ├── WordleDialog.h │ │ ├── XspfDialog.cpp │ │ ├── XspfDialog.h │ │ ├── XspfReader.cpp │ │ ├── XspfReader.h │ │ ├── boffin.pro │ │ ├── comet/ │ │ │ ├── CometParser.cpp │ │ │ ├── CometParser.h │ │ │ └── YajlCallbacks.hpp │ │ ├── json_spirit/ │ │ │ ├── README │ │ │ ├── json_spirit.h │ │ │ ├── json_spirit_reader.cpp │ │ │ ├── json_spirit_reader.h │ │ │ ├── json_spirit_utils.h │ │ │ ├── json_spirit_value.cpp │ │ │ ├── json_spirit_value.h │ │ │ ├── json_spirit_writer.cpp │ │ │ └── json_spirit_writer.h │ │ ├── layouts/ │ │ │ ├── SideBySideLayout.cpp │ │ │ └── SideBySideLayout.h │ │ ├── main.cpp │ │ ├── playdar/ │ │ │ ├── BoffinPlayableItem.cpp │ │ │ ├── BoffinPlayableItem.h │ │ │ ├── BoffinRqlRequest.cpp │ │ │ ├── BoffinRqlRequest.h │ │ │ ├── BoffinTagRequest.cpp │ │ │ ├── BoffinTagRequest.h │ │ │ ├── CometRequest.cpp │ │ │ ├── CometRequest.h │ │ │ ├── PlaydarApi.h │ │ │ ├── PlaydarAuthRequest.cpp │ │ │ ├── PlaydarAuthRequest.h │ │ │ ├── PlaydarCometRequest.cpp │ │ │ ├── PlaydarCometRequest.h │ │ │ ├── PlaydarConnection.cpp │ │ │ ├── PlaydarConnection.h │ │ │ ├── PlaydarRosterRequest.cpp │ │ │ ├── PlaydarRosterRequest.h │ │ │ ├── PlaydarStatRequest.cpp │ │ │ ├── PlaydarStatRequest.h │ │ │ ├── TPlaydarApi.hpp │ │ │ ├── TrackResolveRequest.cpp │ │ │ ├── TrackResolveRequest.h │ │ │ ├── jsonGetMember.cpp │ │ │ └── jsonGetMember.h │ │ ├── qrc/ │ │ │ └── boffin.qrc │ │ └── sample/ │ │ └── SampleFromDistribution.h │ ├── client/ │ │ ├── Application.cpp │ │ ├── Application.h │ │ ├── AudioscrobblerSettings.cpp │ │ ├── AudioscrobblerSettings.h │ │ ├── Bootstrapper/ │ │ │ ├── AbstractBootstrapper.cpp │ │ │ ├── AbstractBootstrapper.h │ │ │ ├── AbstractFileBootstrapper.cpp │ │ │ ├── AbstractFileBootstrapper.h │ │ │ ├── ITunesDevice/ │ │ │ │ ├── ITunesParser.h │ │ │ │ ├── MediaDeviceInterface.h │ │ │ │ ├── itunesdevice.cpp │ │ │ │ └── itunesdevice.h │ │ │ ├── PluginBootstrapper.cpp │ │ │ ├── PluginBootstrapper.h │ │ │ ├── iTunesBootstrapper.cpp │ │ │ └── iTunesBootstrapper.h │ │ ├── CommandReciever/ │ │ │ ├── CommandReciever.h │ │ │ └── CommandReciever.mm │ │ ├── Dialogs/ │ │ │ ├── BetaDialog.cpp │ │ │ ├── BetaDialog.h │ │ │ ├── BetaDialog.ui │ │ │ ├── DiagnosticsDialog.cpp │ │ │ ├── DiagnosticsDialog.h │ │ │ ├── DiagnosticsDialog.ui │ │ │ ├── LicensesDialog.cpp │ │ │ ├── LicensesDialog.h │ │ │ └── LicensesDialog.ui │ │ ├── Fingerprinter/ │ │ │ ├── AacSource.cpp │ │ │ ├── AacSource.h │ │ │ ├── AacSource_p.h │ │ │ ├── FlacSource.cpp │ │ │ ├── FlacSource.h │ │ │ ├── MadSource.cpp │ │ │ ├── MadSource.h │ │ │ ├── VorbisSource.cpp │ │ │ └── VorbisSource.h │ │ ├── Last.fm Scrobbler.css │ │ ├── MainWindow.cpp │ │ ├── MainWindow.h │ │ ├── MediaDevices/ │ │ │ ├── DeviceScrobbler.cpp │ │ │ ├── DeviceScrobbler.h │ │ │ ├── IpodDevice.cpp │ │ │ ├── IpodDevice.h │ │ │ ├── IpodDevice_linux.cpp │ │ │ ├── IpodDevice_linux.h │ │ │ ├── MediaDevice.cpp │ │ │ └── MediaDevice.h │ │ ├── Mpris2/ │ │ │ ├── DBusAbstractAdaptor.cpp │ │ │ ├── DBusAbstractAdaptor.h │ │ │ ├── MediaPlayer2.cpp │ │ │ ├── MediaPlayer2.h │ │ │ ├── MediaPlayer2Player.cpp │ │ │ ├── MediaPlayer2Player.h │ │ │ ├── Mpris2.cpp │ │ │ └── Mpris2.h │ │ ├── PrefPane/ │ │ │ ├── English.lproj/ │ │ │ │ ├── InfoPlist.strings │ │ │ │ └── MainWindow.xib │ │ │ ├── FmLastPrefPane.h │ │ │ ├── FmLastPrefPanePrefWidget.h │ │ │ ├── FmLastPrefPanePrefWidget.mm │ │ │ ├── FmLastPrefPaneQtView.h │ │ │ ├── FmLastPrefPaneQtView.mm │ │ │ ├── Info.plist │ │ │ ├── PrefPane.pro │ │ │ ├── PrefPane.xcodeproj/ │ │ │ │ └── project.pbxproj │ │ │ └── PrefPane_prefix.pch │ │ ├── ScrobSocket.cpp │ │ ├── ScrobSocket.h │ │ ├── Services/ │ │ │ ├── AnalyticsService/ │ │ │ │ ├── AnalyticsService.cpp │ │ │ │ ├── AnalyticsService.h │ │ │ │ ├── PersistentCookieJar.cpp │ │ │ │ └── PersistentCookieJar.h │ │ │ ├── AnalyticsService.h │ │ │ ├── RadioService/ │ │ │ │ └── RadioService.cpp │ │ │ ├── ScrobbleService/ │ │ │ │ ├── ScrobbleService.cpp │ │ │ │ ├── ScrobbleService.h │ │ │ │ ├── StopWatch.cpp │ │ │ │ └── StopWatch.h │ │ │ └── ScrobbleService.h │ │ ├── Settings/ │ │ │ ├── AccountSettingsWidget.cpp │ │ │ ├── AccountSettingsWidget.h │ │ │ ├── AccountSettingsWidget.ui │ │ │ ├── AdvancedSettingsWidget.cpp │ │ │ ├── AdvancedSettingsWidget.h │ │ │ ├── AdvancedSettingsWidget.ui │ │ │ ├── CheckFileSystemModel.cpp │ │ │ ├── CheckFileSystemModel.h │ │ │ ├── CheckFileSystemView.cpp │ │ │ ├── CheckFileSystemView.h │ │ │ ├── GeneralSettingsWidget.cpp │ │ │ ├── GeneralSettingsWidget.h │ │ │ ├── GeneralSettingsWidget.ui │ │ │ ├── IpodSettingsWidget.cpp │ │ │ ├── IpodSettingsWidget.h │ │ │ ├── IpodSettingsWidget.ui │ │ │ ├── PreferencesDialog.cpp │ │ │ ├── PreferencesDialog.h │ │ │ ├── PreferencesDialog.ui │ │ │ ├── ScrobbleSettingsWidget.cpp │ │ │ ├── ScrobbleSettingsWidget.h │ │ │ ├── ScrobbleSettingsWidget.ui │ │ │ ├── SettingsWidget.cpp │ │ │ └── SettingsWidget.h │ │ ├── Widgets/ │ │ │ ├── BioWidget.cpp │ │ │ ├── BioWidget.h │ │ │ ├── ContextLabel.cpp │ │ │ ├── ContextLabel.h │ │ │ ├── FriendListWidget.cpp │ │ │ ├── FriendListWidget.h │ │ │ ├── FriendListWidget.ui │ │ │ ├── FriendWidget.cpp │ │ │ ├── FriendWidget.h │ │ │ ├── FriendWidget.ui │ │ │ ├── MetadataWidget.cpp │ │ │ ├── MetadataWidget.h │ │ │ ├── MetadataWidget.ui │ │ │ ├── NothingPlayingWidget.cpp │ │ │ ├── NothingPlayingWidget.h │ │ │ ├── NothingPlayingWidget.ui │ │ │ ├── NothingPlayingWidget_mac.mm │ │ │ ├── NowPlayingStackedWidget.cpp │ │ │ ├── NowPlayingStackedWidget.h │ │ │ ├── NowPlayingWidget.cpp │ │ │ ├── NowPlayingWidget.h │ │ │ ├── PlaybackControlsWidget.cpp │ │ │ ├── PlaybackControlsWidget.h │ │ │ ├── PlaybackControlsWidget.ui │ │ │ ├── PointyArrow.cpp │ │ │ ├── PointyArrow.h │ │ │ ├── ProfileArtistWidget.cpp │ │ │ ├── ProfileArtistWidget.h │ │ │ ├── ProfileWidget.cpp │ │ │ ├── ProfileWidget.h │ │ │ ├── ProfileWidget.ui │ │ │ ├── PushButton.cpp │ │ │ ├── PushButton.h │ │ │ ├── RefreshButton.cpp │ │ │ ├── RefreshButton.h │ │ │ ├── ScrobbleControls.cpp │ │ │ ├── ScrobbleControls.h │ │ │ ├── ScrobblesListWidget.cpp │ │ │ ├── ScrobblesListWidget.h │ │ │ ├── ScrobblesWidget.cpp │ │ │ ├── ScrobblesWidget.h │ │ │ ├── ScrobblesWidget.ui │ │ │ ├── ShortcutEdit.cpp │ │ │ ├── ShortcutEdit.h │ │ │ ├── SideBar.cpp │ │ │ ├── SideBar.h │ │ │ ├── SimilarArtistWidget.cpp │ │ │ ├── SimilarArtistWidget.h │ │ │ ├── StatusBar.cpp │ │ │ ├── StatusBar.h │ │ │ ├── TagWidget.cpp │ │ │ ├── TagWidget.h │ │ │ ├── TitleBar.cpp │ │ │ ├── TitleBar.h │ │ │ ├── TrackWidget.cpp │ │ │ ├── TrackWidget.h │ │ │ ├── TrackWidget.ui │ │ │ ├── WidgetTextObject.cpp │ │ │ └── WidgetTextObject.h │ │ ├── Wizard/ │ │ │ ├── AccessPage.cpp │ │ │ ├── AccessPage.h │ │ │ ├── BootstrapInProgressPage.h │ │ │ ├── BootstrapPage.cpp │ │ │ ├── BootstrapPage.h │ │ │ ├── BootstrapProgressPage.cpp │ │ │ ├── BootstrapProgressPage.h │ │ │ ├── FirstRunWizard.cpp │ │ │ ├── FirstRunWizard.h │ │ │ ├── FirstRunWizard.ui │ │ │ ├── LoginPage.cpp │ │ │ ├── LoginPage.h │ │ │ ├── PluginsInstallPage.cpp │ │ │ ├── PluginsInstallPage.h │ │ │ ├── PluginsPage.cpp │ │ │ ├── PluginsPage.h │ │ │ ├── TourFinishPage.cpp │ │ │ ├── TourFinishPage.h │ │ │ ├── TourLocationPage.cpp │ │ │ ├── TourLocationPage.h │ │ │ ├── TourMetadataPage.cpp │ │ │ ├── TourMetadataPage.h │ │ │ ├── TourScrobblesPage.cpp │ │ │ ├── TourScrobblesPage.h │ │ │ ├── WizardPage.cpp │ │ │ └── WizardPage.h │ │ ├── audioscrobbler.icns │ │ ├── audioscrobbler.rc │ │ ├── client.pro │ │ ├── lastfm-scrobbler.desktop │ │ ├── main.cpp │ │ └── qrc/ │ │ └── audioscrobbler.qrc │ ├── fingerprinter/ │ │ ├── Fingerprinter.cpp │ │ ├── Fingerprinter.h │ │ ├── LAV_Source.cpp │ │ ├── LAV_Source.h │ │ ├── fingerprinter.pro │ │ └── main.cpp │ └── twiddly/ │ ├── IPod.cpp │ ├── IPod.h │ ├── IPodScrobble.h │ ├── IPodSettings.h │ ├── ITunesLibrary.h │ ├── ITunesLibraryTrack.h │ ├── ITunesLibrary_mac.cpp │ ├── ITunesLibrary_win.cpp │ ├── PlayCountsDatabase.cpp │ ├── PlayCountsDatabase.h │ ├── README │ ├── Twiddly.rc │ ├── TwiddlyApplication.cpp │ ├── TwiddlyApplication.h │ ├── Utils.cpp │ ├── Utils.h │ ├── Utils_mac.mm │ ├── main.cpp │ └── twiddly.pro ├── common/ │ ├── HideStupidWarnings.h │ ├── c++/ │ │ ├── Logger.cpp │ │ ├── Logger.h │ │ ├── fileCreationTime.cpp │ │ ├── mac/ │ │ │ └── getBsdProcessList.c │ │ ├── string.h │ │ └── win/ │ │ └── scrobSubPipeName.cpp │ ├── precompiled.h │ └── qt/ │ ├── README │ ├── msleep.cpp │ ├── override/ │ │ ├── QDebug │ │ ├── QHttp │ │ ├── QMessageBox │ │ ├── QNetworkAccessManager │ │ └── README │ ├── reverse.cpp │ └── sort.cpp ├── i18n/ │ ├── i18n.pro │ ├── lastfm_de.ts │ ├── lastfm_en.ts │ ├── lastfm_es.ts │ ├── lastfm_fr.ts │ ├── lastfm_it.ts │ ├── lastfm_ja.ts │ ├── lastfm_pl.ts │ ├── lastfm_pt.ts │ ├── lastfm_ru.ts │ ├── lastfm_sv.ts │ ├── lastfm_tr.ts │ └── lastfm_zh_CN.ts ├── lib/ │ ├── 3rdparty/ │ │ ├── README │ │ ├── fetch.sh │ │ ├── fftw3.h │ │ ├── iTunesCOMAPI/ │ │ │ ├── LicenseAgreement.rtf │ │ │ ├── ReadMe.rtf │ │ │ ├── SampleScripts/ │ │ │ │ ├── CreateAlbumPlaylists.js │ │ │ │ ├── RemoveDeadTracks.js │ │ │ │ └── RemoveUserPlaylists.js │ │ │ ├── iTunesCOM.chm │ │ │ ├── iTunesCOMInterface.h │ │ │ └── iTunesCOMInterface_i.c │ │ ├── mad.h │ │ ├── patches/ │ │ │ ├── README │ │ │ └── tbytevector_cpp.patch │ │ └── samplerate.h │ ├── DllExportMacro.h │ ├── listener/ │ │ ├── PlayerCommand.h │ │ ├── PlayerCommandParser.cpp │ │ ├── PlayerCommandParser.h │ │ ├── PlayerConnection.cpp │ │ ├── PlayerConnection.h │ │ ├── PlayerListener.cpp │ │ ├── PlayerListener.h │ │ ├── PlayerMediator.cpp │ │ ├── PlayerMediator.h │ │ ├── State.h │ │ ├── legacy/ │ │ │ ├── LegacyPlayerListener.cpp │ │ │ └── LegacyPlayerListener.h │ │ ├── listener.pro │ │ ├── mac/ │ │ │ ├── ITunesListener.cpp │ │ │ ├── ITunesListener.h │ │ │ ├── SpotifyListener.h │ │ │ └── SpotifyListener.mm │ │ ├── mpris2/ │ │ │ ├── Mpris2Listener.cpp │ │ │ ├── Mpris2Listener.h │ │ │ ├── Mpris2Service.cpp │ │ │ └── Mpris2Service.h │ │ ├── tests/ │ │ │ ├── TestPlayerCommandParser.cpp │ │ │ └── test_liblistener.pro │ │ └── win/ │ │ ├── NamedPipeServer.cpp │ │ ├── NamedPipeServer.h │ │ ├── SpotifyListener.cpp │ │ └── SpotifyListener.h │ ├── logger/ │ │ └── logger.pro │ └── unicorn/ │ ├── AnimatedPushButton.h │ ├── AnimatedStatusBar.cpp │ ├── AnimatedStatusBar.h │ ├── CrashReporter/ │ │ ├── CrashReporter.cpp │ │ ├── CrashReporter.h │ │ └── CrashReporter_mac.mm │ ├── DesktopServices.cpp │ ├── DesktopServices.h │ ├── LoginProcess.cpp │ ├── LoginProcess.h │ ├── PlayBus/ │ │ ├── Bus.cpp │ │ ├── Bus.h │ │ ├── PlayBus.cpp │ │ └── PlayBus.h │ ├── QMessageBoxBuilder.cpp │ ├── QMessageBoxBuilder.h │ ├── ScrobblesModel.cpp │ ├── ScrobblesModel.h │ ├── SignalBlocker.h │ ├── TrackImageFetcher.cpp │ ├── TrackImageFetcher.h │ ├── UnicornApplication.cpp │ ├── UnicornApplication.h │ ├── UnicornApplicationDelegate.h │ ├── UnicornApplicationDelegate.mm │ ├── UnicornApplication_mac.mm │ ├── UnicornCoreApplication.cpp │ ├── UnicornCoreApplication.h │ ├── UnicornMainWindow.cpp │ ├── UnicornMainWindow.h │ ├── UnicornSession.cpp │ ├── UnicornSession.h │ ├── UnicornSettings.cpp │ ├── UnicornSettings.h │ ├── UpdateInfoFetcher.cpp │ ├── UpdateInfoFetcher.h │ ├── Updater/ │ │ ├── Updater.cpp │ │ ├── Updater.h │ │ └── Updater_mac.mm │ ├── dialogs/ │ │ ├── AboutDialog.cpp │ │ ├── AboutDialog.h │ │ ├── AboutDialog.ui │ │ ├── CloseAppsDialog.cpp │ │ ├── CloseAppsDialog.h │ │ ├── CloseAppsDialog.ui │ │ ├── CloseAppsDialog_mac.mm │ │ ├── LoginContinueDialog.cpp │ │ ├── LoginContinueDialog.h │ │ ├── LoginDialog.cpp │ │ ├── LoginDialog.h │ │ ├── ProxyDialog.cpp │ │ ├── ProxyDialog.h │ │ ├── ProxyDialog.ui │ │ ├── ScrobbleConfirmationDialog.cpp │ │ ├── ScrobbleConfirmationDialog.h │ │ ├── ScrobbleConfirmationDialog.ui │ │ ├── ShareDialog.cpp │ │ ├── ShareDialog.h │ │ ├── ShareDialog.ui │ │ ├── TagDialog.cpp │ │ ├── TagDialog.h │ │ ├── TagDialog.ui │ │ ├── UnicornDialog.h │ │ ├── UserManagerDialog.cpp │ │ └── UserManagerDialog.h │ ├── layouts/ │ │ ├── FlowLayout.cpp │ │ └── FlowLayout.h │ ├── mac/ │ │ ├── AppleScript.cpp │ │ └── AppleScript.h │ ├── notify/ │ │ ├── Notify.h │ │ └── Notify.mm │ ├── plugins/ │ │ ├── FooBar08PluginInfo.cpp │ │ ├── FooBar08PluginInfo.h │ │ ├── Foobar09PluginInfo.cpp │ │ ├── Foobar09PluginInfo.h │ │ ├── IPluginInfo.cpp │ │ ├── IPluginInfo.h │ │ ├── ITunesPluginInfo.cpp │ │ ├── ITunesPluginInfo.h │ │ ├── ITunesPluginInstaller.cpp │ │ ├── ITunesPluginInstaller.h │ │ ├── KillProcess.h │ │ ├── PluginList.cpp │ │ ├── PluginList.h │ │ ├── Version.cpp │ │ ├── Version.h │ │ ├── WinampPluginInfo.cpp │ │ ├── WinampPluginInfo.h │ │ ├── WmpPluginInfo.cpp │ │ └── WmpPluginInfo.h │ ├── qrc/ │ │ ├── spinner.mng │ │ └── unicorn.qrc │ ├── qtsingleapplication/ │ │ ├── qtlocalpeer.cpp │ │ ├── qtlocalpeer.h │ │ ├── qtlockedfile.cpp │ │ ├── qtlockedfile.h │ │ ├── qtlockedfile_unix.cpp │ │ ├── qtlockedfile_win.cpp │ │ ├── qtsingleapplication.cpp │ │ ├── qtsingleapplication.h │ │ ├── qtsinglecoreapplication.cpp │ │ └── qtsinglecoreapplication.h │ ├── qtwin.cpp │ ├── qtwin.h │ ├── unicorn.pro │ └── widgets/ │ ├── ActionButton.cpp │ ├── ActionButton.h │ ├── AvatarWidget.cpp │ ├── AvatarWidget.h │ ├── BannerWidget.cpp │ ├── BannerWidget.h │ ├── DataBox.h │ ├── DataListWidget.cpp │ ├── DataListWidget.h │ ├── FriendsPicker.cpp │ ├── FriendsPicker.h │ ├── GhostWidget.cpp │ ├── GhostWidget.h │ ├── HttpImageWidget.cpp │ ├── HttpImageWidget.h │ ├── ImageButton.cpp │ ├── ImageButton.h │ ├── ItemSelectorWidget.cpp │ ├── ItemSelectorWidget.h │ ├── Label.cpp │ ├── Label.h │ ├── LfmListViewWidget.cpp │ ├── LfmListViewWidget.h │ ├── MessageBar.cpp │ ├── MessageBar.h │ ├── PlayableMimeData.h │ ├── ProxyWidget.cpp │ ├── ProxyWidget.h │ ├── ProxyWidget.ui │ ├── SearchBox.cpp │ ├── SearchBox.h │ ├── Seed.h │ ├── SlidingStackedWidget.cpp │ ├── SlidingStackedWidget.h │ ├── SpinnerLabel.h │ ├── StackedWidget.cpp │ ├── StackedWidget.h │ ├── StatusLight.cpp │ ├── StatusLight.h │ ├── TagListWidget.cpp │ ├── TagListWidget.h │ ├── TrackWidget.cpp │ ├── TrackWidget.h │ ├── UnicornTabWidget.cpp │ ├── UnicornTabWidget.h │ ├── UserComboSelector.h │ ├── UserManagerWidget.cpp │ ├── UserManagerWidget.h │ ├── UserMenu.cpp │ ├── UserMenu.h │ ├── UserToolButton.cpp │ └── UserToolButton.h └── plugins/ ├── LFMRadio/ │ └── PluginInfo.h ├── foobar08/ │ ├── audioscrobbler.cpp │ ├── foo_install.iss │ └── tools/ │ └── append_once.bat ├── foobar09/ │ ├── ChangeLog.txt │ ├── audioscrobbler.cpp │ ├── foo_audioscrobbler.rc │ ├── foo_install.iss │ ├── foobar_sdk/ │ │ ├── foobar2000/ │ │ │ ├── SDK/ │ │ │ │ ├── abort_callback.cpp │ │ │ │ ├── abort_callback.h │ │ │ │ ├── advconfig.h │ │ │ │ ├── app_close_blocker.cpp │ │ │ │ ├── app_close_blocker.h │ │ │ │ ├── audio_chunk.cpp │ │ │ │ ├── audio_chunk.h │ │ │ │ ├── audio_chunk_channel_config.cpp │ │ │ │ ├── audio_postprocessor.h │ │ │ │ ├── cfg_var.cpp │ │ │ │ ├── cfg_var.h │ │ │ │ ├── chapterizer.cpp │ │ │ │ ├── chapterizer.h │ │ │ │ ├── commandline.cpp │ │ │ │ ├── commandline.h │ │ │ │ ├── completion_notify.cpp │ │ │ │ ├── completion_notify.h │ │ │ │ ├── component.h │ │ │ │ ├── component_client.h │ │ │ │ ├── components_menu.h │ │ │ │ ├── componentversion.h │ │ │ │ ├── config_io_callback.cpp │ │ │ │ ├── config_io_callback.h │ │ │ │ ├── config_object.cpp │ │ │ │ ├── config_object.h │ │ │ │ ├── config_object_impl.h │ │ │ │ ├── console.cpp │ │ │ │ ├── console.h │ │ │ │ ├── contextmenu.h │ │ │ │ ├── contextmenu_manager.h │ │ │ │ ├── core_api.h │ │ │ │ ├── coreversion.h │ │ │ │ ├── dsp.cpp │ │ │ │ ├── dsp.h │ │ │ │ ├── dsp_manager.cpp │ │ │ │ ├── dsp_manager.h │ │ │ │ ├── file_info.cpp │ │ │ │ ├── file_info.h │ │ │ │ ├── file_info_impl.cpp │ │ │ │ ├── file_info_impl.h │ │ │ │ ├── file_info_merge.cpp │ │ │ │ ├── file_operation_callback.cpp │ │ │ │ ├── file_operation_callback.h │ │ │ │ ├── filesystem.cpp │ │ │ │ ├── filesystem.h │ │ │ │ ├── filesystem_helper.cpp │ │ │ │ ├── filesystem_helper.h │ │ │ │ ├── foobar2000.h │ │ │ │ ├── genrand.h │ │ │ │ ├── guids.cpp │ │ │ │ ├── hasher_md5.cpp │ │ │ │ ├── hasher_md5.h │ │ │ │ ├── info_lookup_handler.h │ │ │ │ ├── initquit.h │ │ │ │ ├── input.cpp │ │ │ │ ├── input.h │ │ │ │ ├── input_file_type.cpp │ │ │ │ ├── input_file_type.h │ │ │ │ ├── input_impl.h │ │ │ │ ├── library_manager.h │ │ │ │ ├── link_resolver.cpp │ │ │ │ ├── link_resolver.h │ │ │ │ ├── main_thread_callback.h │ │ │ │ ├── mainmenu.cpp │ │ │ │ ├── masstagger_action.h │ │ │ │ ├── mem_block_container.cpp │ │ │ │ ├── mem_block_container.h │ │ │ │ ├── menu.h │ │ │ │ ├── menu_helpers.cpp │ │ │ │ ├── menu_helpers.h │ │ │ │ ├── menu_item.cpp │ │ │ │ ├── menu_manager.cpp │ │ │ │ ├── message_loop.h │ │ │ │ ├── metadb.cpp │ │ │ │ ├── metadb.h │ │ │ │ ├── metadb_handle.cpp │ │ │ │ ├── metadb_handle.h │ │ │ │ ├── metadb_handle_list.cpp │ │ │ │ ├── modeless_dialog.cpp │ │ │ │ ├── modeless_dialog.h │ │ │ │ ├── packet_decoder.cpp │ │ │ │ ├── packet_decoder.h │ │ │ │ ├── play_callback.h │ │ │ │ ├── playable_location.cpp │ │ │ │ ├── playable_location.h │ │ │ │ ├── playback_control.cpp │ │ │ │ ├── playback_control.h │ │ │ │ ├── playlist.cpp │ │ │ │ ├── playlist.h │ │ │ │ ├── playlist_loader.cpp │ │ │ │ ├── playlist_loader.h │ │ │ │ ├── playlist_lock.cpp │ │ │ │ ├── popup_message.cpp │ │ │ │ ├── popup_message.h │ │ │ │ ├── preferences_page.cpp │ │ │ │ ├── preferences_page.h │ │ │ │ ├── replaygain.cpp │ │ │ │ ├── replaygain.h │ │ │ │ ├── replaygain_info.cpp │ │ │ │ ├── resampler.h │ │ │ │ ├── service.cpp │ │ │ │ ├── service.h │ │ │ │ ├── service_impl.h │ │ │ │ ├── shared.h │ │ │ │ ├── shortcut_actions.h │ │ │ │ ├── stdafx.cpp │ │ │ │ ├── tag_processor.cpp │ │ │ │ ├── tag_processor.h │ │ │ │ ├── tag_processor_id3v2.cpp │ │ │ │ ├── threaded_process.cpp │ │ │ │ ├── threaded_process.h │ │ │ │ ├── titleformat.cpp │ │ │ │ ├── titleformat.h │ │ │ │ ├── titleformat_config.cpp │ │ │ │ ├── titleformat_config.h │ │ │ │ ├── track_property.h │ │ │ │ ├── ui.cpp │ │ │ │ ├── ui.h │ │ │ │ ├── unpack.h │ │ │ │ ├── utf8api.cpp │ │ │ │ └── vis.h │ │ │ ├── foo_input_raw/ │ │ │ │ ├── foo_input_raw.cpp │ │ │ │ └── readme.txt │ │ │ ├── foo_input_validator/ │ │ │ │ └── readme.txt │ │ │ ├── foobar2000_component_client/ │ │ │ │ └── component_client.cpp │ │ │ ├── helpers/ │ │ │ │ ├── StdAfx.cpp │ │ │ │ ├── StdAfx.h │ │ │ │ ├── bitreader_helper.h │ │ │ │ ├── cfg_guidlist.h │ │ │ │ ├── cfg_structlist.h │ │ │ │ ├── create_directory_helper.cpp │ │ │ │ ├── create_directory_helper.h │ │ │ │ ├── cue_creator.cpp │ │ │ │ ├── cue_creator.h │ │ │ │ ├── cue_parser.cpp │ │ │ │ ├── cue_parser.h │ │ │ │ ├── cue_parser_embedding.cpp │ │ │ │ ├── cuesheet_index_list.cpp │ │ │ │ ├── cuesheet_index_list.h │ │ │ │ ├── dialog_resize_helper.cpp │ │ │ │ ├── dialog_resize_helper.h │ │ │ │ ├── dropdown_helper.cpp │ │ │ │ ├── dropdown_helper.h │ │ │ │ ├── dynamic_bitrate_helper.cpp │ │ │ │ ├── dynamic_bitrate_helper.h │ │ │ │ ├── file_cached.h │ │ │ │ ├── file_info_const_impl.cpp │ │ │ │ ├── file_info_const_impl.h │ │ │ │ ├── file_list_helper.cpp │ │ │ │ ├── file_list_helper.h │ │ │ │ ├── file_move_helper.cpp │ │ │ │ ├── file_move_helper.h │ │ │ │ ├── file_win32_wrapper.h │ │ │ │ ├── file_wrapper_simple.cpp │ │ │ │ ├── file_wrapper_simple.h │ │ │ │ ├── format_title_group.cpp │ │ │ │ ├── format_title_group.h │ │ │ │ ├── helpers.h │ │ │ │ ├── inplace_edit.cpp │ │ │ │ ├── inplace_edit.h │ │ │ │ ├── input_helpers.cpp │ │ │ │ ├── input_helpers.h │ │ │ │ ├── listview_helper.cpp │ │ │ │ ├── listview_helper.h │ │ │ │ ├── meta_table_builder.h │ │ │ │ ├── metadb_io_hintlist.cpp │ │ │ │ ├── metadb_io_hintlist.h │ │ │ │ ├── mp3_utils.cpp │ │ │ │ ├── mp3_utils.h │ │ │ │ ├── preload_info_helper.cpp │ │ │ │ ├── preload_info_helper.h │ │ │ │ ├── search_filter.cpp │ │ │ │ ├── search_filter.h │ │ │ │ ├── seekabilizer.cpp │ │ │ │ ├── seekabilizer.h │ │ │ │ ├── stream_buffer_helper.cpp │ │ │ │ ├── stream_buffer_helper.h │ │ │ │ ├── string_filter.h │ │ │ │ ├── text_file_loader.cpp │ │ │ │ ├── text_file_loader.h │ │ │ │ ├── wildcard.cpp │ │ │ │ ├── wildcard.h │ │ │ │ ├── win32_dialog.cpp │ │ │ │ ├── win32_dialog.h │ │ │ │ ├── win32_misc.cpp │ │ │ │ ├── win32_misc.h │ │ │ │ ├── window_placement_helper.cpp │ │ │ │ └── window_placement_helper.h │ │ │ └── shared/ │ │ │ ├── audio_math.h │ │ │ ├── shared.h │ │ │ ├── shared.lib │ │ │ └── win32_misc.h │ │ ├── pfc/ │ │ │ ├── alloc.h │ │ │ ├── array.h │ │ │ ├── avltree.h │ │ │ ├── bit_array.h │ │ │ ├── bit_array_impl.h │ │ │ ├── bsearch.cpp │ │ │ ├── bsearch.h │ │ │ ├── bsearch_inline.h │ │ │ ├── byte_order_helper.h │ │ │ ├── chainlist.h │ │ │ ├── com_ptr_t.h │ │ │ ├── guid.cpp │ │ │ ├── guid.h │ │ │ ├── instance_tracker.h │ │ │ ├── int_types.h │ │ │ ├── license.txt │ │ │ ├── list.h │ │ │ ├── map.h │ │ │ ├── mem_block_mgr.h │ │ │ ├── order_helper.h │ │ │ ├── other.cpp │ │ │ ├── other.h │ │ │ ├── pfc.h │ │ │ ├── primitives.h │ │ │ ├── printf.cpp │ │ │ ├── profiler.cpp │ │ │ ├── profiler.h │ │ │ ├── ptr_list.h │ │ │ ├── rcptr.h │ │ │ ├── ref_counter.h │ │ │ ├── selftest.cpp │ │ │ ├── sort.cpp │ │ │ ├── sort.h │ │ │ ├── stdafx.cpp │ │ │ ├── string.cpp │ │ │ ├── string.h │ │ │ ├── string8_impl.h │ │ │ ├── string_conv.cpp │ │ │ ├── string_conv.h │ │ │ ├── string_list.h │ │ │ ├── traits.h │ │ │ └── utf8.cpp │ │ ├── sdk-license.txt │ │ ├── sdk-readme.css │ │ └── sdk-readme.html │ ├── resource.h │ └── tools/ │ └── append_once.bat ├── iTunes/ │ ├── ChangeLog.txt │ ├── IPod.cpp │ ├── IPod.h │ ├── IPodDetector.h │ ├── IPodDetector_mac.cpp │ ├── IPodDetector_win.cpp │ ├── IPod_mac.cpp │ ├── IPod_win.cpp │ ├── ITunesComThread.cpp │ ├── ITunesComThread.h │ ├── ITunesComWrapper.cpp │ ├── ITunesComWrapper.h │ ├── ITunesEventInterface.h │ ├── ITunesExceptions.h │ ├── ITunesPlaysDatabase.cpp │ ├── ITunesPlaysDatabase.h │ ├── ITunesPlaysDatabase_mac.cpp │ ├── ITunesPlaysDatabase_win.cpp │ ├── ITunesTrack.cpp │ ├── ITunesTrack.h │ ├── Moose.h │ ├── Moose_mac.cpp │ ├── Moose_win.cpp │ ├── Plist.cpp │ ├── Plist.h │ ├── README.dist │ ├── _iTunes.iss │ ├── _iTunes.plist │ ├── _iTunes.rc │ ├── _iTunes.xcodeproj/ │ │ └── project.pbxproj │ ├── iTunesVisualAPI/ │ │ ├── iTunesAPI.cpp │ │ ├── iTunesAPI.h │ │ └── iTunesVisualAPI.h │ ├── libs/ │ │ ├── sqlite3.c │ │ └── sqlite3.h │ ├── main.cpp │ ├── main.h │ ├── main_mac.cpp │ ├── main_win.cpp │ ├── resource.h │ ├── scripts/ │ │ ├── currentTrack.scpt │ │ ├── currentTrackLocation.scpt │ │ ├── currentTrackPersistentId.scpt │ │ ├── playCountForDatabaseId.scpt │ │ ├── playCountForPersistentId.scpt │ │ └── uninstall.sh │ ├── tests/ │ │ ├── ITunesPlaysDatabaseMac.pro │ │ ├── TestITunesPlaysDatabaseMac.cpp │ │ ├── comtest.cpp │ │ └── tests.pro │ └── tools/ │ └── append_once.bat ├── scrobsub/ │ ├── BlockingClient.cpp │ ├── BlockingClient.h │ ├── Bootstrap.cpp │ ├── Bootstrap.h │ ├── Bootstrap.rc │ ├── Doxyfile │ ├── EncodingUtils.cpp │ ├── EncodingUtils.h │ ├── RegistryUtils.cpp │ ├── RegistryUtils.h │ ├── ScrobSub.xcodeproj/ │ │ └── project.pbxproj │ ├── ScrobSubmitter.cpp │ ├── ScrobSubmitter.h │ ├── StdString.h │ ├── resource.h │ ├── resource1.h │ └── tests/ │ ├── BlockingClient.cpp │ └── EncodingUtils.cpp ├── winamp/ │ ├── AutoChar.h │ ├── Dbg.h │ ├── GEN.H │ ├── Gen_AudioScrobbler.cpp │ ├── Gen_AudioScrobbler.dsp │ ├── Gen_AudioScrobbler.dsw │ ├── Gen_AudioScrobbler.h │ ├── Gen_AudioScrobbler.rc │ ├── OSVer.cpp │ ├── OSVer.h │ ├── Registry.cpp │ ├── Registry.h │ ├── Scrobbler.cpp │ ├── Scrobbler.h │ ├── StdAfx.cpp │ ├── StdAfx.h │ ├── StdString.h │ ├── StdStringArray.cpp │ ├── StdStringArray.h │ ├── UTF.cpp │ ├── UTF.h │ ├── VersionApp.cpp │ ├── VersionApp.h │ ├── WhatsNew.txt │ ├── WinampBootstrap.cpp │ ├── WinampBootstrap.h │ ├── WinampController.cpp │ ├── WinampController.h │ ├── WinampScrobbler.cpp │ ├── WinampScrobbler.h │ ├── comdate.h │ ├── dbg.cpp │ ├── ml.h │ ├── ml_lib.cpp │ ├── res/ │ │ └── Gen_AudioScrobbler.rc2 │ ├── resource.h │ ├── wa_dlg.h │ ├── wa_hotkeys.h │ ├── wa_ipc.h │ ├── winamp_install.iss │ └── winampcmd.h └── wmp/ ├── Dbg.h ├── OSVer.cpp ├── OSVer.h ├── Scrobbler.cpp ├── Scrobbler.h ├── StdAfx.cpp ├── StdAfx.h ├── StdString.h ├── StdStringArray.cpp ├── StdStringArray.h ├── VersionApp.cpp ├── VersionApp.h ├── WMPScrobbler.cpp ├── WMPScrobbler.h ├── WhatsNew.txt ├── comdate.h ├── dbg.cpp ├── resource.h ├── tools/ │ └── append_once.bat ├── wmp.h ├── wmpBootStrap.cpp ├── wmpBootStrap.h ├── wmp_install.iss ├── wmp_scrobbler.cpp ├── wmp_scrobbler.def ├── wmp_scrobbler.dsp ├── wmp_scrobbler.h ├── wmp_scrobbler.rc ├── wmp_scrobbler.rgs ├── wmp_scrobbler.vcxproj ├── wmp_scrobblerEvents.cpp ├── wmp_scrobblerdll.cpp └── wmpplug.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ _build/ _files.qmake _build_parameters.pl.h Makefile* /_bin/ /_include/ admin/dist/mac/dsa_priv.pem *.idb *.pdb *.ncb *.vcproj* *.sln *.suo .DS_Store *.breakpad *.user *.qm *.exe *.vscode Growl Sparkle ================================================ FILE: .gitmodules ================================================ ================================================ FILE: COPYING ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS ================================================ FILE: Last.fm.pro ================================================ TEMPLATE = subdirs CONFIG += ordered SUBDIRS = lib/logger \ lib/unicorn \ lib/listener \ i18n \ app/client \ app/twiddly \ app/fingerprinter #app/boffin unix:!mac:SUBDIRS -= app/twiddly CONFIG( tests ) { SUBDIRS += \ lib/lastfm/core/tests/test_libcore.pro \ lib/lastfm/types/tests/test_libtypes.pro \ lib/lastfm/scrobble/tests/test_libscrobble.pro \ lib/listener/tests/test_liblistener.pro } ================================================ FILE: README.md ================================================ Join us for chats on IRC! Server: irc.last.fm Channel: #last.desktop # Build Dependencies * Qt >= 4.8 (http://download.qt.io/archive/qt/4.8/4.8.7/qt-opensource-windows-x86-vs2010-4.8.7.exe) * liblastfm >= 1.0.7 You will also need depending on your chosen platform:- ## Mac OS X ### Homebrew We recommend that you use Homebrew to install most of the dependencies. We recommend you have XCode set as your build toolchain. ``` sudo xcode-select -s /Applications/Xcode.app/Contents/Developer ``` ``` brew install ffmpeg coreutils cmake fftw libsamplerate ``` We recommend Qt 4.8.7, the last version with Webkit support. ``` brew install cartr/qt4/qt@4 brew install cartr/qt4/qt-webkit@2.3 ``` ### liblastfm Download liblastfm from https://github.com/lastfm/liblastfm parallel to the build of lastfm-desktop. As the Desktop Client supports only Qt4 at the moment, you will need to set it to Qt4 mode. In CMakeLists.txt, change ``` option(BUILD_WITH_QT4 "Build liblastfm with Qt4" ON) ``` Then build and make. ``` cd liblastfm mkdir _build && cd _build cmake .. make -j4 make install ``` ### Other dependencies You'll also need the Growl and libsparkle frameworks. Get the latest Growl SDk from here http://code.google.com/p/growl/downloads/list - latest tested 1.2.2 Get the latest Sparkle from here http://sparkle.andymatuschak.org/ - latest tested 1.21.3 Unzip both and put their frameworks in /Library/Frameworks/ so the build will find them. You may need to symlink the headers files into the lastfm-desktop directory: ``` ln -s /Library/Frameworks/Sparkle.framework/Headers Sparkle ln -s /Library/Frameworks/Growl.framework/Headers Growl ``` ### Now you're ready! ``` cd lastfm-desktop rm -r _bin qmake -r make clean make -j4 ``` ## Windows We used to build using Cygwin, but now we prefer not to. You should get Windows version of the tool chain. Here are some recommendations. - Git: http://code.google.com/p/msysgit/downloads/list - CMake: http://www.cmake.org/cmake/resources/software.html - pkg-config: http://www.gtk.org/download/win32.php - Ruby: http://rubyinstaller.org/ - Perl: http://www.perl.org/get.html - Win Platform SDK:http://www.microsoft.com/en-us/download/details.aspx?id=8279 - KDE Support: http://windows.kde.org/ Install the automoc and dbus packages. ### Qt Install Qt binaries from either the Qt SDK or standalone binary package. You should be able to find everything here http://qt.nokia.com/downloads You will also need the latest Windows SDK. We build using Visual Studio 2008. ### Winsparkle This is the library we use to check for app updates. You should download the latest dll and headers form here http://winsparkle.org This step should be optional really as most people will not want to add the update checking. I found that I also needed to copy the dll into the lastfm-desktop/_bin folder. Create a pkg-config file for WinSparkle like this: Name: sparkle Description: Multimedia Library Version: 0.3 Libs: -LC:/dev/Install/WinSparkle/Release -lWinSparkle Cflags: -IC:/dev/Install/WinSparkle/include ## Linux On Debian or Ubuntu, you can download all the build dependencies by running: sudo apt-get install libavformat-dev libgpod-nogtk-dev liblastfm-dev \ libqt4-dev libqtwebkit-dev pkg-config \ zlib1g-dev You should also install the `libqt4-sql-sqlite` plugin if you want to use the software to scrobble your iPod. # Build Instructions qmake -r make -j4 `make install` currently does not work on Windows or OSX. Windows note: use nmake on Windows Linux note: Linux users can set the install prefix like so `qmake PREFIX=/usr/local` OSX note: if you installed Qt through homebrew it will default to a release build. # Run Instructions Apps are styled using stylesheets which are found in the source directory of the app. By default the executable path is checked for the css file on Windows and on OSX the bundle Resource directory is checked otherwise you'll need to tell the app where the stylesheet is, like this: ./Last.fm.exe -stylesheet path/to/Last.fm.css On Linux, if you have not run `make install`, you can run the app like this from the root of the source directory: _bin/lastfm-scrobbler -stylesheet app/client/Last.fm\ Scrobbler.css # Build Support We support developers trying to build the source on any platform. Seeing as we don't provide many varieties of binary package on Linux, we also support non-developers building from source there. However within reason! Please check around the net a little first. Ask your friends. Demand help from people standing at bus-stops. Maybe check the official forum: http://getsatisfaction.com/lastfm # Bugs If you find a bug in the software, please let us know about it. Michael Coffey Desktop App Lead Developer, Last.fm ================================================ FILE: admin/Doxyfile ================================================ # Doxyfile 1.6.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = lastfm-desktop # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = /Users/jono/dev/lastfm-desktop/_docs # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = /Users/dimitri/doxygen/mail/1.5.7/doxywizard/ # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it parses. # With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this tag. # The format is ext=language, where ext is a file extension, and language is one of # the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, # Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat # .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. Note that for custom extensions you also need to set # FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penality. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will rougly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols SYMBOL_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen # will sort the (brief and detailed) documentation of class members so that # constructors and destructors are listed first. If set to NO (the default) # the constructors will appear in the respective orders defined by # SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. # This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO # and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 26 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by # doxygen. The layout file controls the global structure of the generated output files # in an output format independent way. The create the layout file that represents # doxygen's defaults, run doxygen with the -l option. You can optionally specify a # file name after the option, if omitted DoxygenLayout.xml will be used as the name # of the layout file. LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = /Users/jono/dev/lastfm-desktop # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.d \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.idl \ *.odl \ *.cs \ *.php \ *.php3 \ *.inc \ *.m \ *.mm \ *.dox \ *.py \ *.f90 \ *.f \ *.vhd \ *.vhdl # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = /Users/jono/dev/lastfm-desktop/plugins \ /Users/jono/dev/lastfm-desktop/lib/3rdparty # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = moc_* \ qrc_* \ ui_* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER # are set, an additional index file will be generated that can be used as input for # Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated # HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. # For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's # filter section matches. # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. GENERATE_TREEVIEW = NO # By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list. USE_INLINE_TREES = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 # When the SEARCHENGINE tag is enable doxygen will generate a search box # for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using # HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP) # there is already a search function so this one should typically # be disabled. SEARCHENGINE = NO #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = NO # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = YES # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = YES # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES ================================================ FILE: admin/dist/mac/AppleScriptSuite.sdef ================================================ ================================================ FILE: admin/dist/mac/Growl Registration Ticket.growlRegDict ================================================ TicketVersion 1 AllNotifications New track DefaultNotifications New track ================================================ FILE: admin/dist/mac/Standard.plist ================================================ CFBundleIconFile @ICON@ CFBundlePackageType APPL CFBundleExecutable @EXECUTABLE@ CFBundleIdentifier fm.last.Scrobbler CFBundleVersion 2.1.39 SUFeedURL https://cdn.last.fm/client/Mac/updates.xml SUEnableAutomaticChecks SUEnableSystemProfiling SUShowReleaseNotes SUScheduledCheckInterval 86400 SUAllowsAutomaticUpdates SUPublicDSAKeyFile dsa_pub.pem CFBundleDisplayName @EXECUTABLE@ OSAScriptingDefinition AppleScriptSuite.sdef NSAppleScriptEnabled NSAppleEventsUsageDescription For Music and iTunes, this enables scrobbling. For Spotify, this lets you see Last.fm info about the currently playing song. BreakpadProduct @EXECUTABLE@ BreakpadVendor @EXECUTABLE@ BreakpadURL http://oops.last.fm/report/add BreakpadReportInterval 30 BreakpadRequestComments YES CFBundleName Last.fm Scrobbler NSHighResolutionCapable CFBundleURLTypes CFBundleURLName Last.fm CFBundleURLSchemes lastfm LSUIElement ================================================ FILE: admin/dist/mac/build-release.rb ================================================ ### Usage build-release.rb --deltas [--no-build] [--no-package] # the deltas should be in a zip in the filder under bin with the expected filename format # example /_bin//Last.fm- # TODO: download the previous versions from cdn and use that for the delta # there's no chance they'll be wrong then require 'rexml/document' # find the app version from the app's Info.plist xml_data = File::read( "admin/dist/mac/Standard.plist" ) # extract event information doc = REXML::Document.new(xml_data) doc.elements.each('plist/dict/key') do |ele| if ele.text == "CFBundleVersion" $version = ele.next_element().text end end print "Building version #{$version}\n" # get the delta versions from the command line $deltas = [] deltasIndex = ARGV.index("--deltas") if deltasIndex != nil while deltasIndex + 1 < ARGV.length and not ARGV.at( deltasIndex + 1 ).start_with?("--") deltasIndex = deltasIndex + 1 print "Creating binary delta for version #{ARGV.at(deltasIndex)}\n" $deltas << ARGV.at(deltasIndex) end end $upload_folder = '/tmp/desktop_client_app_release/' $download_folder = 'http://users.last.fm/~michael/client/Mac' ## Check that we are running from the root of the lastfm-desktop project # ? def clean ## clean everything system 'make clean' system 'make distclean' end def build ## build everything Dir.chdir("../liblastfm") do system "rm -rf _release" Dir.mkdir("_release") Dir.chdir("_release") do system 'cmake -D CMAKE_BUILD_TYPE:STRING=Release ..' system 'make -j8 install' end end system 'qmake -r CONFIG+=release' # Hack to fix the /Library/Framework includes system 'sed -i -e "s/-I-F/-F/g" lib/unicorn/Makefile' system 'make' system 'rm -rf _bin/Last.fm.app' system 'mv "_bin/Last.fm Scrobbler.app" _bin/Last.fm.app' end def copy_plugin ## copy the iTunes plugin into the bundle system "cp -R _bin/Audioscrobbler.bundle '_bin/Last.fm.app/Contents/MacOS/'" end def sign_app # We need to fix the QT dependencies, as they are not in a state to be signed in El Capitan # and we're not upgrading to QT5 at the moment puts "======= Fixing Qt Framework and Codesigning ===========" Dir.foreach('_bin/Last.fm.app/Contents/Frameworks') do |framework| if framework.start_with?('Qt') puts "fixing #{framework} for codesigning" system "mkdir -p _bin/Last.fm.app/Contents/Frameworks/#{framework}/Versions/Current/Resources" system "mv _bin/Last.fm.app/Contents/Frameworks/#{framework}/Contents/Info.plist _bin/Last.fm.app/Contents/Frameworks/#{framework}/Versions/Current/Resources/Info.plist" system "rmdir _bin/Last.fm.app/Contents/Frameworks/#{framework}/Contents" system "rm _bin/Last.fm.app/Contents/Frameworks/#{framework}/*.prl" end end puts "signing application and bundled frameworks" system 'codesign -f --deep -s "Developer ID Application: Last.fm" -i fm.last.Scrobbler _bin/Last.fm.app' end def create_zip ## create a zip file Dir.chdir("_bin") do system "rm -rf #{$version}" system "mkdir #{$version}" system "tar cjf #{$version}/Last.fm-#{$version}.tar.bz2 'Last.fm.app'" Dir.chdir("Last.fm.app/Contents") do system "tar cjf ../../#{$version}/Last.fm_Mac_Update_#{$version}.tar.bz2 *" end system "zip -ry #{$version}/Last.fm-#{$version}.zip 'Last.fm.app'" end end def create_deltas ## create any deltas Dir.chdir("_bin") do # unzip the new app puts "unzipping #{$version}" system "unzip -q #{$version}/Last.fm-#{$version}.zip -d #{$version}" $deltas.each do |delta| # unzip the old version (try both compression formats) puts "unzipping #{delta}" #system 'tar -xjf Last.fm-#{delta}.tar.bz2 -C #{delta}' system "unzip -q #{delta}/Last.fm-#{delta}.zip -d #{delta}" # create the delta system "./BinaryDelta create #{delta}/Last.fm.app #{$version}/Last.fm.app #{$version}/Last.fm-#{$version}-#{delta}.delta" # check that it worked system "./BinaryDelta apply #{delta}/Last.fm.app #{$version}/Last.fm.app #{$version}/Last.fm-#{$version}-#{delta}.delta" # remove the unzipped old version system "rm -rf #{delta}/Last.fm.app" end # delete the unzipped new app system "rm -rf #{$version}/Last.fm.app" end end def upload_files # scp the main zip file # scp all the deltas # put them in my userhome if we are doing a test update system "cp _bin/#{$version}/Last.fm-#{$version}.tar.bz2 #{$upload_folder}" system "cp _bin/#{$version}/Last.fm_Mac_Update_#{$version}.tar.bz2 #{$upload_folder}" system "cp _bin/#{$version}/Last.fm-#{$version}.zip #{$upload_folder}" $deltas.each do |delta| system "scp _bin/#{$version}/Last.fm-#{$version}-#{delta}.delta badger:#{$upload_folder}" end end def generate_appcast_xml ## sign the zip file and deltas File.open("update.xml", 'w') do |f| item = "\n" item << "\t#{$version}\n" item << "\t\n" version_sig = `ruby admin/dist/mac/sign_update.rb _bin/#{$version}/Last.fm-#{$version}.tar.bz2 admin/dist/mac/dsa_priv.pem`.strip version_size = `du _bin/#{$version}/Last.fm-#{$version}.tar.bz2`.split[0] item << "\t\n" item << "\t\n" $deltas.each do |delta| delta_sig = `ruby admin/dist/mac/sign_update.rb _bin/#{$version}/Last.fm-#{$version}-#{delta}.delta admin/dist/mac/dsa_priv.pem`.strip delta_du = `du _bin/#{$version}/Last.fm-#{$version}-#{delta}.delta`.split[0] item << "\t\t\n" end item << "\t\n" item << "\t\n" item << "\n" f.syswrite item end end if not ARGV.include?( "--no-build" ) # build the app clean build copy_plugin sign_app end if not ARGV.include?( "--no-package" ) # package and upload the app create_zip create_deltas upload_files generate_appcast_xml end ================================================ FILE: admin/dist/mac/bundleFrameworks.sh ================================================ #!/bin/bash # Author: Jono Cole # # A tool for distributing Mac OSX bundles. # # Finds and copies dependant frameworks and local dylibs # not installed on stardard systems. # Also ensures that all binaries have the correct paths # installed relative to the bundle's @executable_path function getBundleBin { if echo $1|grep -q framework; then echo $1/`cat "$1/Contents/Info.plist" | sed -n '/CFBundleExecutable<\/key>/,/<\/string>/ s/.*\(.*\)<.*/\1/p'|sed s/_debug//` else echo $1/Contents/MacOS/`cat "$1/Contents/Info.plist" | sed -n '/CFBundleExecutable<\/key>/,/<\/string>/ s/.*\(.*\)<.*/\1/p'` fi } function fixFrameworks { echo -n F if [ -d "$1" ]; then local bin="`getBundleBin "$1"`" else local bin="$1" fi # echo Fixing Frameworks for $bin libs=`otool -L "$bin"|sed -n '/\/usr\/local.*/ s/^[^\/]*\([^(]*\) [^(]*([^)]*)/\1/p'` mkdir -p "$bundlePath/Contents/Frameworks" local lib for lib in $libs; do #ignore non-frameworks if echo $lib | grep -vq framework; then continue; fi #examples for /opt/qt/lib/QtXml.framework/Contents/QtXml #framework=/opt/qt/qt.git/lib/QtXml.framework framework=`echo $lib |sed -n 's/\(\.framework\).*/\1/p'` #frameworkLib=/Contents/QtXml frameworkLib=`echo $lib |sed -n 's/^.*\.framework//p'` #frameworkName=QtXml.framework frameworkName=`basename $framework` destFramework=$bundlePath/Contents/Frameworks/$frameworkName installFramework=@executable_path/../Frameworks/$frameworkName if [ "`basename $lib`" == "`basename $bin`" ]; then continue; fi if [ ! -e "$bundlePath/Contents/Frameworks/$frameworkName" ]; then #cp -Rf -P /opt/qt/qt.git/lib/QtXml.framework (app name.app)/Contents/Frameworks cp -R -H -f $framework "$bundlePath/Contents/Frameworks" chmod -R u+w "$bundlePath/Contents/Frameworks" #install_name_tool -id /opt/qt/qt.git/lib/QtXml.framework/Contents/QtXml install_name_tool -id $installFramework$frameworkLib "$destFramework$frameworkLib" fi #install_name_tool -change /opt/qt/qt.git/lib/QtXml.framework/Contents/QtXml @executable_path/../Frameworks/QtXml.framework/Contents/QtXml (bin) install_name_tool -change $lib $installFramework$frameworkLib "$bin" fixLocalLibs "$destFramework" fixFrameworks "$destFramework" done } function fixLocalLibs { echo -n L if [ -d "$1" ]; then local bin=`getBundleBin "$1"` else local bin="$1" fi echo Fixing Local Lib for $bin local libs=`otool -L "$bin" | sed -n '/^[^\/]*$/ s/^[[:space:]]*\(.*\) (com.*/\1/p'` local extralibs=`otool -L "$bin" | sed -n '/\/usr\/local.*/ s/^[^\/]*\([^(]*\) [^(]*([^)]*)/\1/p'|grep -v framework` local moreExtralibs=`otool -L "$bin" | sed -n '/\/usr\/X11.*/ s/^[^\/]*\([^(]*\) [^(]*([^)]*)/\1/p'|grep -v framework` local libs="$libs $extralibs $moreExtralibs" local lib local cpPath for lib in $libs; do local libPath=$lib if [ ! -e $lib ]; then cpPath=`locateLib $lib` else cpPath=$lib fi resolvedLib=`/usr/local/bin/greadlink -f $cpPath` basenameLib=`basename $resolvedLib` echo $cpPath "$bundlePath/Contents/MacOS" cp -RLf $resolvedLib "$bundlePath/Contents/MacOS/" chmod -R u+w "$bundlePath/Contents/MacOS" install_name_tool -id @executable_path/../MacOS/$basenameLib "$bundlePath/Contents/MacOS/$basenameLib" install_name_tool -change $libPath @executable_path/../MacOS/$basenameLib "$bin" fixFrameworks "$bundlePath/Contents/MacOS/$basenameLib" fixLocalLibs "$bundlePath/Contents/MacOS/$basenameLib" done } function locateLib { for p in {$rootdir,/usr/local/lib}; do if [ -e $p/$1 ]; then echo $p/$1 return 0 fi done return 1 } if [ -d '$1' ]; then bundlePath="$1" else bundlePath=$(echo $1 | sed -E "s|^(.*)\.app.*$|\1\.app|g") fi rootdir=`dirname "$bundlePath"` binPath=$bundlePath/Contents/MacOS echo =========== Fix Local Libs ============== if [ -d '$1' ]; then fixLocalLibs "$bundlePath" else fixLocalLibs "$1" fi echo echo =========== Fix Frameworks ============== if [ -d '$1' ]; then fixFrameworks "$bundlePath" else fixFrameworks "$1" fi echo echo ======= Copying 3rd party frameworks =========== cp -R -H -f /Library/Frameworks/Growl.framework "$bundlePath/Contents/Frameworks" cp -R -H -f /Library/Frameworks/Sparkle.framework "$bundlePath/Contents/Frameworks" #cp -R -L -f /Library/Frameworks/Breakpad.framework "$bundlePath/Contents/Frameworks" echo echo ======= Copying Qt plugins =========== mkdir -p "$bundlePath/Contents/plugins" plugins="imageformats sqldrivers bearer" for plugin in $plugins; do if [ -d /Developer/Applications/Qt/plugins/ ]; then pluginDir=/Developer/Applications/Qt/plugins elif [ -d /usr/local/lib/qt4/plugins/ ]; then # Qt installed using Homebrew will be found in your local lib. # Currently we only support qt v4.8.7. pluginDir=/usr/local/lib/qt4/plugins else pluginDir=`qmake --version |sed -n 's/^.*in \(\/.*$\)/\1/p'`/../plugins fi cp -v -R -L -f $pluginDir/$plugin "$bundlePath/Contents/plugins" chmod -R -v u+w "$bundlePath/Contents/plugins" for i in "$bundlePath"/Contents/plugins/$plugin/*; do fixFrameworks "$i" fixLocalLibs "$i" echo -n P done echo done echo ======= Copying Qt translations =========== mkdir -p "$bundlePath/Contents/Resources/qm" translations="qt_de.qm qt_es.qm qt_fr.qm qt_ja.qm qt_pl.qm qt_pt.qm qt_ru.qm qt_sv.qm qt_zh_CN.qm" for translation in $translations; do if [ -d /Developer/Applications/Qt/plugins/ ]; then translationDir=/Developer/Applications/Qt/translations elif [ -d /usr/local/Cellar/qt@4/4.8.7_5/translations/ ]; then # Qt installed using Homebrew will be found in the Homebrew Cellar. # Currently we only support qt v4.8.7. translationDir=/usr/local/Cellar/qt@4/4.8.7_5/translations else translationDir=`qmake --version |sed -n 's/^.*in \(\/.*$\)/\1/p'`/../translations fi cp -v -f $translationDir/$translation "$bundlePath/Contents/Resources/qm" echo done echo ======= creating qt.conf =========== qtconf=$bundlePath/Contents/Resources/qt.conf echo [Paths] > "$qtconf" echo Plugins = ../plugins >> "$qtconf" ================================================ FILE: admin/dist/mac/dsa_pub.pem ================================================ -----BEGIN PUBLIC KEY----- MIIDPDCCAi4GByqGSM44BAEwggIhAoIBAQDagjvSe6+JNNvHjB6aWriA6YXnVdqS s//YkmhwiCwkHGk0set/4P4IZbl2xkpd/hyo9qQnnhjou0llHOaljU8Yp6g61lt9 n/BhlhPt7tvBkTtsugJ4YIRkaakzscLUIoeHneF8D3B3Ej2xoZ734Rq/LDc9jZgd UNjS+sZec69waEp2ikcmkvfAbZ0yPHHhl0xftaA0A9OqaBBDUyxiBOqWfLQexj9L DHlx38kGy3lsuqY9DQQ0BbIv24QKDWDFf9GD/TEXgVOBAiIC4cGVUh2JgQVAZ0nk 86pwavc1uIPynTyje902MI0ojFPLuhuod7QAO9NNBFD3024n+uv7mPi5AhUAqo2v msdlE3NsONzW4uBVGujvNNUCggEBANlg+4w6ikFI8agyolAl9YepP0dcwk33MSXY 2Plwqp/41yIpFzb9qp0QKjfmtwy+cAdWcVcN8xKM9MghVgTukttdV9Rz8fwnfHAa QMoYVhHBNDaipR0RRpx+NPnMkKPrYiH1+J4C8/MYAsDEB0xa7Vv/EasA0yWD3mhK ydm9O2Njj1J2ZEQ4Nz+9ba7wDLpZVM/P9fitZalVyWxFVPxhYs3ifUSCgE+fYGR2 VBiwBZiQ8uKf+yJpmYQkUA4/h54S2hiCQHrSS9A9L0Ekdj+Jy07lPGv6IAlhzcfQ OyiNzmbEgsR8LLo6H54/JxTwElt/TsdpWP8n83l4KFVN00eAZSgDggEGAAKCAQEA us92grNvv1zRXmBpsEPPhy5hGhcslBy5ZWygUytgwupjq8MkpkD3yez649qVTUAt E4SJUW+6EKbcLZSBRBdMWZNwudrHCly4n6OMOh4ZqHDA2YQFqMFnLaOHMBxHDU7b YiBPYxjHlm7yBXTnXIVqOnts3yRPRfHUCvNdiMvNzLQaDtP343jew2W4R8arwFpC U01NbHWoOiQ6GggTJEfGIxTHhjkd5Xd0hB97KccPF4YvOTGnXpNiHN+hGteF2CrZ pZAyYIqshyeijR9ipbOpYxdrYOGKfA5nUSDjeGpG2Yqg6R6eJrrBw49JsJP6VqhD VM0wmv7fyfPZHD/g9CAx0w== -----END PUBLIC KEY----- ================================================ FILE: admin/dist/mac/sign_update.rb ================================================ #!/usr/bin/ruby if ARGV.length < 2 puts "Usage: ruby sign_update.rb update_archive private_key" exit end openssl = "/usr/bin/openssl" puts `#{openssl} dgst -sha1 -binary < "#{ARGV[0]}" | #{openssl} dgst -dss1 -sign "#{ARGV[1]}" | #{openssl} enc -base64` ================================================ FILE: admin/dist/updates/Changelog.txt ================================================ The changelog is publicly places into the update files, which live on cdn.last.fm. Add your changes there. Note that build-release.rb will output a stub Sparkle update XML block (called update.xml in the project root) to be added to various files here (beta, then release versions) which is correctly signed with our dsa_priv.pem key. You must incorporate this into the update XMLs. ================================================ FILE: admin/dist/updates/Mac/updates.xml ================================================ Last.fm Scrobbler Updates https://cdn.last.fm/client/Mac/updates.xml Last.fm Scrobbler Updates - Mac en Last.fm Scrobbler 2.1.39 Wed, 18 Sep 2019 16:26:00 +0100 Features
  • This update ensures that scrobbling continues to work with Mac OS Catalina. It is now compatible with the Music app, which was formerly known as iTunes.

Bug Fixes

  • The link to activate scrobbling in Spotify has been fixed. You can add scrobbling for Spotify through your Last.fm settings page.
]]>
Last.fm Scrobbler 2.1.37 Tue, 22 Mar 2016 15:31:00 +0000 Features
  • This is mainly a security update for OSX El Capitan to allow loading of all future updates over HTTPS.
  • This update also removes some unused functions of the client.

Bug Fixes

  • Some users were experiencing crashes on sleep/wake. This should be fixed.
]]>
Last.fm Scrobbler 2.1.36 Tue, 03 Sep 2013 12:14:00 +0000 Features
  • New playback controls and progress bar design!

Bug fixes

  • iPod scrobbles with multiple plays are now submitted at 1 second intervals instead of all at the same time
  • Fingerprinting is fixed and back - it's now also in a different process so fingerprinting problems don't bring down the whole app (just a precaution)
  • Fixed some memory leaks
  • Scrobble count on profile tab now updated correctly (unless you scrobble elsewhere)
  • Do not display corrections in the client (it wasn't respecting the website setting and was causing a few issues)
  • 'Similar Artist' header in the track metadata view is now a link to more similar artists on the site
  • There is now a warning/explanation in the iPod scrobble confirmation dialog when you have red/invalid iPod scrobbles
  • Now shows the correct modifier keys on Windows for the raise/hide Scrobbler option
  • Mute now works from the system tray and menu option
  • The last page of the wizard now doesn't say we've imported your listening history, if we haven't
  • Fixed an issue where the tray icon would be invisible if the first run wizard tour was skipped
  • Less logging (there was an issue with log file sizes getting very big)
]]>
Last.fm Scrobbler 2.1.35 Wed, 06 Mar 2013 10:43:00 +0000 Features
  • Exclude directories! You can now exclude music in certain directories from being scrobbled
  • Systray/menu bar icon is greyed out when scrobbling is disabled
  • Users can now hide both the dock icon and the menu bar icon (restart only required when hiding dock icon on 10.6)
  • Binary delta updates will be supported from the release after this one
  • [Experimental] Added the ability to use SSL for all web services calls

Bug fixes

  • Fingerprinting was causing crashes so has been temporarily disabled until we find a proper fix
  • Fixed an issue where it was possible to get two running versions of the applications after an update. Please note that as this fix was to the update mechanism it will only be corrected in the update after this one so please make sure you don't have two versions of the app running after this update.
  • Device scrobbling improvements
    • We now force that a compatible iTunes plugin version is installed before trying to device scrobble
    • Prompt to install the new plugin has been improved
  • Don't crash if Airfoil is connected during the first run wizard
  • Tracks now get their scrobble and loved status updated when the scrobble list is refreshed
  • Fixed scrobbling problems associated with setting the scrobble point to 100%
    • Fixed double scrobble and missed scrobbles
    • There's now a setting to turn of the maximum scrobble point being 4 minutes
    • Scrobble point and max scrobble turn off now take effect during the current track

  • Users that only authenticated with 2.1.32 will be asked to authenticate again. Sorry!
  • First run wizard's continue button no longer gets stuck
  • Bug fixes from 2.1.32

    • All users should now be able to authenticate - changed to a simpler authentication mechanism
    • Added a beta updates option - warning: only for the brave!
    • Fixed some minor account bootstrap issues
    • Open 'media player' links now work on Windows
    Full changelog ]]>
    Last.fm Scrobbler 2.1.30 Mon, 14 Jan 2013 16:20:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    • A temporary workaround for an device scrobbling error
    ]]>
    Last.fm Scrobbler 2.1.29 Mon, 14 Jan 2013 12:00:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enableing/disabling device scrobbling
    • Fixed a crash when switching user accounts
    ]]>
    Last.fm Scrobbler 2.1.28 Tue, 08 Jan 2013 11:30:00 +0000 Changes
    • Side bar images updated slightly

    Bug fixes

    • Restore the main window size and position correctly after restart
    • Fixed a crash caused by VLC when monitors plugged in, etc
    • Fixed spurious device scrobbles caused by a race condition
    • Fixed triple scrobbling when scrobbling offline cached scrobbles
    • Don't run the device scrobble checker so often - it was running almost all the time on Windows
    • Device scrobble performance improvements
    • Minimize and Zoom menu options now work
    ]]>
    Last.fm Scrobbler 2.1.27 Fri, 21 Dec 2012 15:30:00 +0000 Bug fixes
    • Fixed a crash on startup for new users
    • Fixed iTunes plays being picked up as device scrobbles Windows (may improve Mac too)
    ]]>
    Last.fm Scrobbler 2.1.26 Mon, 17 Dec 2012 12:15:00 +0000 Features
    • Translations! (almost complete)
    • Proxy settings - you can set them from the advanced settings tab and the first step of first run wizard
    • Text should now look nicer on Retina screens

    Bug fixes

    • iPod scrobbling - fixed a problem where non-local tracks (iTunes Match) would not get their playcounts synced and lead to duplicates scrobbles
    • Media keys are now ignored if you last listened to audio through another app
    • Don't divide by zero (crash) for users that are using the app on the same day as they registered
    • Fixed restarting the app in Mac, still not working on Windows.
    • Radio now goes into the stopped state correctly
    • Device scrobble Window is now a child of the main window (it appears positioned on top of it nicely now)
    • Fixed a double delete crash when the fingerprinter failed
    ]]>
    Last.fm Scrobbler 2.1.25 Thr, 25 Oct 2012 13:20:00 +0000 Features
    • Now called "Last.fm Scrobbler"
    • iPod scrobbling has been reworked to allow for WiFi sync - Scrobble your device before updating so you don't lose scrobbles
    • The app is now partially translated with translations from the old app. A fully translated version will be coming soon

    Bug fixes

    • Some CPU usage fixes
    • Fingerprinting backend has changed, fixing a rare crash
    • Other minor bugs
    ]]>
    Last.fm Scrobbler 2.1.21 Mon, 10 Sep 2012 10:26:00 +0000 Features
    • Added option to hide app icon from the dock (also has to hide app's menu bar, and icon in tab switcher)
    • We now scrobble 'album artist' as well as 'artist' for cleaner album pages on the website (all plugins have been updated)
    • Fingerprinting local tracks has been added (can be turned off in preferences)
    • "Scrobbling is off" message has been added to the status bar

    Bug fixes

    • Better album images in the scrobble list
    • We now only ask you to install plugins for apps we've detected you've installed
    • We now make sure you close your media players before plugins are installed
    • We now make sure you close WMP or Winamp before bootstrapping (won't bootstrap if they are left open)
    • Profile widget now refreshed when you switch to it so data doesn't get as out of date
    • Fixed some bugs in the preferences dialog
    ]]>
    Last.fm Scrobbler 2.1.20 Fri, 01 Jun 2012 14:20:00 +0000 Bug fixes
    • Fixed some radio playback issues introduced with the switch to VLC
    ]]>
    Last.fm Scrobbler 2.1.19 Wed, 30 May 2012 14:50:00 +0000 Features
    • Media keys on your keyboard now control the radio - play/pause and skip
    • More scrobble tab design improvements

    Bug fixes

    • Fixed radio playback issue - some users had trouble skipping between songs
      • Updated Phonon library from 4.4 to 4.6
      • Switched to VLC as the audio backend
      • Note: for users that didn't have this problem, they should notice some general playback performace improvements
      • Also note: we made these changes for Windows too - there wasn't this specific bug, but you may still notice a performance improvement
    ]]>
    Last.fm Scrobbler 2.1.18 Thu, 26 Apr 2012 19:20:00 +0000 Features
    • Scrobble tab design improvements
    • Can now delete scrobbles from the list through the right click menu
    • Added a licenses dialog for third party software
    • About box now has the Last.fm logo

    Bug fixes

    • Quit dialog now asks a question
    • Scrobble repeated tracks
    • Now restores same size and position when reopening
    • Initial window size adjusts for very small/low resolution screens
    • Doesn't filter other user's personal stations from your recent stations anymore
    • Windows installer now has updated images
    • Doesn't need to fetch track info when refreshing the scrobbles list anymore
    • Should now run on Mac OSX 10.5.x
    ]]>
    Last.fm Scrobbler 2.1.17 Tue, 10 Apr 2012 12:00:00 +0000 Features
    • Can now set the language - the app itself isn't translated yet, but this affects artist bios, etc
    • Scrobbles list rewritten and improved
      • Corrections asterisk with hover over now shown
      • Displays if the scrobble is cached and yet to be submitted
      • Displays if their was an error when submitting the scrobble - i.e. bad metadata
    • Profile, Friends, and Radio tab design improvements

    Bug fixes

    • Always start on the scrobble list when switching to that tab, not the previous track info view
    • Less calls made to track.getInfo
    • If app is closed to tray and notifications turned off, the only web services called are for scrobbling
    ]]>
    Last.fm Scrobbler 2.1.16 Thu, 15 Mar 2012 12:20:00 +0000 Features
    • Now has a max width of 800px
    • Use app icon for all radio stations

    Bug fixes

    • Fixed the now playing display bug
    • Fixed a crash that manifested itself in several places (cancelling getting info for a track)
    ]]>
    Last.fm Scrobbler Beta Version 2.1.15 Tue, 28 Feb 2012 16:00:00 +0000 Features
    • Radio controls are now in the sys tray too
    • Buy links now in the scrobble list view
    • There's now a diagnostics dialog - only one tab with the ability to force a check for iPod scrobbles and see the log so far

    Bug fixes

    • Dialogs, like preferences, are now brought to the front if reopened
    • Many copy changes around the app
    • Doesn't erroneously display two now playing tracks anymore
    • iPod scrobbles are submitted immeditaly instead of just cached
    • Doesn't fetch track info as much
    • The loved state of the current track is now reliable
    • Don't ask where Spotify is, if it isn't installed
    • Extra dock icons no longer appear
    ]]>
    Last.fm Scrobbler Beta Version 2.1.14 Tue, 14 Feb 2012 17:10:00 +0000 This is the first public beta version!!!

    Bug fixes

    • Updates to the wizard copy
    • Buy links now work for uk, us, and de users (was only ever doing uk links)
    • Other very minor fixes
    ]]>
    Last.fm Scrobbler Alpha Version 2.0.13 Fri, 10 Feb 2012 17:10:00 +0000 Features
    • Changed the API key. Everyone will have to go through the wizard again
    • Now opens lastfm:// radio urls from the web site
    • Upgraded to Qt 4.8.0
    • Now tells you to close iTunes when there is a new plugin to install
    • Message bar now styles nicely

    Bug fixes

    • Doesn't show two now playing tracks in the scrobble list any more
    • Fixed quite a few bugs in the frist run wizard
    • Fixed some bugs with the account management settings
    • Friend list trackpad scrolling is now smooth (Qt 4.8.0)
    ]]>
    Last.fm Scrobbler Version 2.0.12 Thu, 02 Feb 2012 17:10:00 +0000 Features
    • Now branded as a beta version!
    • Tag dialog now implemented to similar standard as the share dialog
    • Only fetches track metadata if the window is visable
    • Scrobble toggle now in preferences
    • 'Play' menu option is now not checkable, but has 'play', 'pause' and 'resume' states
    • Progress bar now reports the state correctly for podcasts and videos
    • Don't display metadata for Spotify ads

    Bug fixes

    • iTunes plugin now handles device names with ' and \ in them correctly
    • Device names are now interpreted as utf-8 by the app
    ]]>
    Last.fm Scrobbler Version 2.0.11 Wed, 25 Jan 2012 19:46:00 +0000 Bug fixes 2.0.11
    • Incorrectly thought some radio tracks were podcasts or videos

    Features - 2.0.10

    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes 2.0.10

    • Some user setting fixes, you will have to go through the wizard again
    • Fixed iTunes plugin crashing iTunes for some Snow Leopard people
    • Fixed the menubar not showing sometimes at startup
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.10 Wed, 25 Jan 2012 16:20:00 +0000 Features
    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes

    • Some user setting fixes, you will have to go through the wizard again
    • Fixed iTunes plugin crashing iTunes for some Snow Leopard people
    • Fixed the menubar not showing sometimes at startup
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.9 Mon, 16 Jan 2012 10:20:00 +0000 Bug fixes
    • iPod scrobble device associations were getting corrupted
    • Cleaned up and fixed some bugs in the apps user settings
    ]]>
    Last.fm Scrobbler Version 2.0.8 Thu, 12 Jan 2012 10:55:00 +0000 Features/Improvements
    • Settings toolbar now looks better and has assets
    • Crash Reporting implemented in client, but disabled. (needs oops server set up by ops)

    Bug fixes

    • Stops from iTunes are picked up - end of a playlist, quit iTunes, etc
    • Resume iTunes track after playing and pausing radio is now picked up
    • Spacebar now works again as a global shortcut
    ]]>
    Last.fm Scrobbler Version 2.0.7 Wed, 04 Jan 2012 12:20:00 +0000
  • Fixed a major bug with batch scrobbling
  • Fixed a crash for unkown length tracks (iTunes radio streams)
  • ]]>
    Last.fm Scrobbler Version 2.0.6 Fri, 23 Dec 2011 17:00:00 +0000
  • New friends list
  • Now installs media player plugins on Windows
  • Bootstrapping for all media players now works
  • Many other bug fixes and improvements
  • ]]>
    Last.fm Scrobbler Version 2.0.5 Thu, 17 Nov 2011 16:57:00 +0000
  • Lots of UI changes - welcome screen changed, metadata view improved, etc etc
  • Supports updates with libsparkle!
  • Uses the Growl framework to show notifications with album art
  • Airfoil metadata support
  • Scriptable with AppleScript (see the dictionary)
  • Campaign tracking when linking to the website
  • Radio quick start widget now shows artist and tag suggestions
  • Big play buttons now resume the last radio
  • Friends list now sorted by most recently listened
  • Switching tabs now has a slide animation
  • ]]>
    Last.fm Scrobbler Version 2.0.4 Tue, 08 Nov 2011 15:30:00 +0000 Embedded release notes! ... ]]>
    ================================================ FILE: admin/dist/updates/Win/updates.xml ================================================ Last.fm Scrobbler Updates http://cdn.last.fm/client/Win/updates.xml Last.fm Scrobbler Updates - Windows en Last.fm Scrobbler 2.1.36 Tue, 03 Sep 2013 12:14:00 +0000 Features
    • New playback controls and progress bar design!

    Bug fixes

    • iPod scrobbles with multiple plays are now submitted at 1 second intervals instead of all at the same time
    • Fingerprinting is fixed and back - it's now also in a different process so fingerprinting problems don't bring down the whole app (just a precaution)
    • Fixed some memory leaks
    • Scrobble count on profile tab now updated correctly (unless you scrobble elsewhere)
    • Do not display corrections in the client (it wasn't respecting the website setting and was causing a few issues)
    • 'Similar Artist' header in the track metadata view is now a link to more similar artists on the site
    • There is now a warning/explanation in the iPod scrobble confirmation dialog when you have red/invalid iPod scrobbles
    • Now shows the correct modifier keys on Windows for the raise/hide Scrobbler option
    • Mute now works from the system tray and menu option
    • The last page of the wizard now doesn't say we've imported your listening history, if we haven't
    • Fixed an issue where the tray icon would be invisible if the first run wizard tour was skipped
    • Less logging (there was an issue with log file sizes getting very big)
    ]]>
    Last.fm Scrobbler 2.1.35 Wed, 06 Mar 2013 11:29:00 +0000 Features
    • Exclude directories! You can now exclude music in certain directories from being scrobbled
    • Systray/menu bar icon is greyed out when scrobbling is disabled
    • New updating framework - currently just looks better and is smaller, but it's the first step in better Windows updating
    • [Experimental] Added the ability to use SSL for all web services calls

    Bug fixes

    • Fingerprinting was causing crashes so has been temporarily disabled until we find a proper fix
    • Device scrobbling improvements
      • We now force that a compatible iTunes plugin version is installed before trying to device scrobble
      • Prompt to install the new plugin has been improved
      • Tracks are now identified by persistent id rather then filename - fixes a few problems and should be more robust
    • Windows Media Player scrobbles now include 'album artist' (just like other media players already do)
    • Only show 'Open iTunes' if it has been installed
    • Settings dialog resizing improved
    • Correctly display track metatdata that has characters such as < in it
    • The prompt to restart now actually restarts the app
    • When installing a plugin from the file menu the full installer is shown - user can select install location, if not default, etc
    • Tracks now get their scrobble and loved status updated when the scrobble list is refreshed
    • Fixed scrobbling problems associated with setting the scrobble point to 100%
      • Fixed double scrobble and missed scrobbles
      • There's now a setting to turn of the maximum scrobble point being 4 minutes
      • Scrobble point and max scrobble turn off now take effect during the current track
    ]]>
    Last.fm Scrobbler 2.1.33 Thr, 24 Jan 2013 11:00:00 +0000
  • First run wizard's continue button no longer gets stuck
  • iTunes plugin update for XP scrobbling
  • Full changelog ]]>
    Last.fm Scrobbler 2.1.32 Tue, 22 Jan 2013 13:48:00 +0000 Bug fixes
    • All users should now be able to authenticate - changed to a simpler authentication mechanism
    • Now runs on Windows XP
    • Added a beta updates option - warning: only for the brave!
    • Fixed some minor account bootstrap issues
    • Open 'media player' links now work on Windows
    ]]>
    Last.fm Scrobbler 2.1.30 (First full release!) Mon, 14 Jan 2013 16:20:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    • A temporary workaround for a device scrobbling error
    ]]>
    Last.fm Scrobbler 2.1.29 Mon, 14 Jan 2013 12:00:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    ]]>
    Last.fm Scrobbler 2.1.28 Tue, 08 Jan 2013 11:30:00 +0000 Changes
    • Side bar images updated slightly

    Bug fixes

    • Restore the main window size and position correctly after restart
    • Fixed a crash when fingerprinting some audio files
    • Fixed spurious device scrobbles caused by a race condition
    • Fixed triple scrobbling when scrobbling offline cached scrobbles
    • Don't run the device scrobble checker so often - it was running almost all the time on Windows
    • Device scrobble performance improvements
    • Minimize and Zoom menu options now work
    ]]>
    Last.fm Scrobbler 2.1.27 Fri, 21 Dec 2012 15:30:00 +0000 Bug fixes
    • Fixed a crash on startup for new users
    • Fixed iTunes plays being picked up as device scrobbles Windows (may improve Mac too)
    ]]>
    Last.fm Scrobbler 2.1.26 Mon, 17 Dec 2012 12:15:00 +0000 Features
    • Translations! (almost complete)
    • Proxy settings - you can set them from the advanced settings tab and the first step of first run wizard
    • iPod scrobbling has been reworked to allow for WiFi sync

    Bug fixes

    • iPod scrobbling
      • iTunes plugin and app now both use the same playcounts db (they didn't before!?)
      • Tracks that end within 20 seconds of starting (user skipped to near the end) now get their playcounts synced (was leading to duplicate scrobbles)
      • iTunes no longer kept open by us not releasing a handle to it
      • Fixed a problem where the iTunes plugin would stop communicating with the app so scrobbling stopped (this also caused the plays to be picked up as iTunes device scrobbles)
      • Fixed a problem where the last track of a playlist would not get it's playcount synced leading to it being picked up as an iTunes device scrobble
    • Don't divide by zero (crash) for users that are using the app on the same day as they registered
    • Fixed restarting the app in Mac, still not working on Windows.
    • Radio now goes into the stopped state correctly
    • Device scrobble Window is now a child of the main window (it appears positioned on top of it nicely now)
    • Fixed a double delete crash when the fingerprinter failed
    ]]>
    Last.fm Scrobbler 2.1.24 Wed, 26 Sep 2012 11:50:00 +0000 2.1.24

    Windows only bug fix release

    • Fixed an issue where iTunes devices (iPod/iPhone) wouldn't scrobble if the app was not installed to the default location
    • Fixed a crash with iTunes device scrobbling introduced with the 'album artist' scrobble changes
    • Fixed a crash caused by fingerprinting .flac files
    • Fixed an issue where the volume would reset to maximum at the start of every radio track
    • Fixed a CPU usage issue
    ]]>
    Last.fm Scrobbler 2.1.21 Tue, 04 Sep 2012 14:20:00 +0000 2.1.21

    Features

    • We now scrobble 'album artist' as well as 'artist' for cleaner album pages on the website (all plugins have been updated)
    • Fingerprinting local tracks has been added (can be turned off in preferences)
    • "Scrobbling is off" message has been added to the status bar

    Bug fixes

    • Better album images in the scrobble list
    • We now only ask you to install plugins for apps we've detcted you've installed
    • We now make sure you close your media players before plugins are installed
    • We now make sure you close WMP or Winamp before bootstrapping (won't bootstrap if they are left open)
    • Profile widget now refreshed when you switch to it so data doesn't get as out of date
    • Fixed some bugs in the preferences dialog
    ]]>
    Last.fm Scrobbler 2.1.20 Fri, 01 Jun 2012 16:30:00 +0000 Bug fixes
    • Fixed some radio playback issues introduced with the switch to VLC
    • Fixed iPod scrobbling (was broken in 2.1.19)
    ]]>
    Last.fm Scrobbler 2.1.19 Wed, 30 May 2012 14:50:00 +0000 Features
    • More scrobble tab design improvements
    ]]>
    Last.fm Scrobbler 2.1.18 Thr, 26 Apr 2012 16:30:00 +0000 Features
    • Scrobble tab design improvements
    • Can now delete scrobbles from the list through the right click menu
    • Added a licenses dialog for third party software
    • About box now has the Last.fm logo

    Bug fixes

    • Quit dialog now asks a question
    • Scrobble repeated tracks
    • Now restores same size and position when reopening
    • Initial window size adjusts for very small/low resolution screens
    • Doesn't filter other user's personal stations from your recent stations anymore
    • Windows installer now has updated images
    • Doesn't need to fetch track info when refreshing the scrobbles list anymore
    • Should now run on Mac OSX 10.5.x
    ]]>
    Last.fm Scrobbler 2.1.17 Mon, 14 Mar 2012 18:30:00 +0000 Features
    • Can now set the language - the app itself isn't translated yet, but this affects artist bios, etc
    • Scrobbles list rewritten and improved
      • Corrections asterisk with hover over now shown
      • Displays if the scrobble is cached and yet to be submitted
      • Displays if their was an error when submitting the scrobble - i.e. bad metadata
    • Profile, Friends, and Radio tab design improvements

    Bug fixes

    • Always start on the scrobble list when switching to that tab, not the previous track info view
    • Less calls made to track.getInfo
    • If app is closed to tray and notifications turned off, the only web services called are for scrobbling
    ]]>
    Last.fm Scrobbler 2.1.16 Thr, 15 Mar 2012 11:10:00 +0000 Features
    • Now has a max width of 800px
    • Use app icon for all radio stations

    Bug fixes

    • Fixed the now playing display bug
    • Fixed a crash that manifested itself in several places (cancelling getting info for a track)
    ]]>
    Last.fm Scrobbler Beta Version 2.1.15 Tue, 28 Feb 2012 16:00:00 +0000 Features
    • Radio controls are now in the sys tray too
    • Buy links now in the scrobble list view
    • There's now a diagnostics dialog - only one tab with the ability to force a check for iPod scrobbles and see the log so far

    Bug fixes

    • Dialogs, like preferences, are now brought to the front if reopened
    • Many copy changes around the app
    • Doesn't erroneously display two now playing tracks anymore
    • iPod scrobbles are submitted immeditaly instead of just cached
    • Doesn't fetch track info as much
    • The loved state of the current track is now reliable
    • iPod scrobbling should now work
    • 'Close' and 'Apply' buttons now work correctly
    ]]>
    Last.fm Scrobbler Beta Version 2.1.14 Tue, 14 Feb 2012 13:30:00 +0000 This is the first public beta version

    Bug fixes

    • Updates to the wizard copy
    • Buy links now work for uk, us, and de users (was only ever doing uk links)
    • Other very minor fixes
    ]]>
    Last.fm Scrobbler Alpha Version 2.0.13 Fri, 10 Feb 2012 18:50:00 +0000 Features
    • Changed the API key. Everyone will have to go through the wizard again
    • Message bar now styles nicely

    Bug fixes

    • Doesn't show two now playing tracks in the scrobble list any more
    • Fixed quite a few bugs in the frist run wizard
    • Fixed some bugs with the account management settings
    ]]>
    Last.fm Scrobbler Version 2.0.12 Thr, 02 Feb 2012 18:20:00 +0000 Features
    • Now branded as a beta version!
    • Tag dialog now implemented to similar standard as the share dialog
    • Only fetches track metadata if the window is visable
    • Scrobble toggle now in preferences
    • 'Play' menu option is now not checkable, but has 'play', 'pause' and 'resume' states
    • Progress bar now reports the state correctly for podcasts and videos
    • Don't display metadata for Spotify ads

    Bug fixes

    • iPod scrobbling should now work (as well as it does on mac)! The installer was incorrect
    • iTunes plugin now handles device names with ' and \ in them correctly
    • Device names are now interpreted as utf-8 by the app
    ]]>
    Last.fm Scrobbler Version 2.0.11 Wed, 25 Jan 2012 20:10:00 +0000 Bug fixes 2.0.11
    • Incorrectly though some radio tracks were podcasts or videos

    Features - 2.0.10

    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes 2.0.10

    • Some user setting fixes, you will have to go through the wizard again
    • Fixes to iTunes plugin communication problems (named pipe type incompatibilities)
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.10 Wed, 25 Jan 2012 17:30:00 +0000 Features
    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes

    • Some user setting fixes, you will have to go through the wizard again
    • Fixes to iTunes plugin communication problems (named pipe type incompatibilities)
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.9 Mon, 16 Jan 2012 11:20:00 +0000 Bug fixes
    • New iTunes plugin was not working due to a change in Qt
    • iPod scrobble device associations were getting corrupted
    • Cleaned up and fixed some bugs in the apps user settings
    ]]>
    Last.fm Scrobbler Version 2.0.8 Thu, 12 Jan 2012 11:45:00 +0000 Features/Improvements
    • Settings toolbar now looks better and has assets
    • Crash Reporting implemented in client, but disabled. (needs oops server set up by ops)

    Bug fixes

    • Spacebar now works again as a global shortcut
    ]]>
    Last.fm Scrobbler Version 2.0.7 Wed, 04 Jan 2012 12:45:00 +0000
  • Fixed a major bug with batch scrobbling
  • Fixed a crash for unkown length tracks (iTunes radio streams)
  • ]]>
    Last.fm Scrobbler Version 2.0.6 Fri, 23 Dec 2011 17:00:00 +0000 Last.fm Scrobbler Version 2.0.1 Wed, 02 Nov 2011 11:11:11 +0000
    ================================================ FILE: admin/dist/updates/updates.html ================================================ Last.fm Scrobbler Changelog
    ================================================ FILE: admin/dist/updates/updates.linux.xml ================================================ Last.fm Scrobbler Updates http://cdn.last.fm/client/updates/updates.lin.xml Last.fm Scrobbler Updates - Linux en Last.fm Scrobbler 2.1.33 Thr, 24 Jan 2013 11:00:00 +0000
  • First run wizard's continue button no longer gets stuck
  • ]]>
    Last.fm Scrobbler 2.1.32 Tue, 22 Jan 2013 13:48:00 +0000 Bug fixes
    • All users should now be able to authenticate - changed to a simpler authentication mechanism
    • Added a beta updates option - warning: only for the brave!
    • Fixed some minor account bootstrap issues
    ]]>
    Last.fm Scrobbler 2.1.30 Mon, 14 Jan 2013 16:20:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    • A temporary workaround for a device scrobbling error
    ]]>
    Last.fm Scrobbler 2.1.29 Mon, 14 Jan 2013 12:00:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    ]]>
    Last.fm Scrobbler 2.1.28 Tue, 08 Jan 2013 11:30:00 +0000 Changes
    • Side bar images updated slightly

    Bug fixes

    • Restore the main window size and position correctly after restart
    • Fixed spurious device scrobbles caused by a race condition
    • Fixed triple scrobbling when scrobbling offline cached scrobbles
    • Don't run the device scrobble checker so often - it was running almost all the time on Windows
    • Device scrobble performance improvements
    • Minimize and Zoom menu options now work
    ]]>
    Last.fm Scrobbler 2.1.27 Fri, 21 Dec 2012 15:30:00 +0000 Bug fixes
    • Fixed a crash on startup for new users
    • Fixed iTunes plays being picked up as device scrobbles Windows (may improve Mac too)
    ]]>
    Last.fm Scrobbler 2.1.26 Mon, 17 Dec 2012 12:15:00 +0000 Features
    • Translations! (almost complete)
    • Proxy settings - you can set them from the advanced settings tab and the first step of first run wizard

    Bug fixes

    • Don't divide by zero (crash) for users that are using the app on the same day as they registered
    • Fixed restarting the app in Mac, still not working on Windows.
    • Radio now goes into the stopped state correctly
    • Device scrobble Window is now a child of the main window (it appears positioned on top of it nicely now)
    • Fixed a double delete crash when the fingerprinter failed
    ]]>
    Last.fm Scrobbler 2.1.24 Wed, 26 Sep 2012 11:50:00 +0000 Windows only bug fix release
    • Fixed an issue where iTunes devices (iPod/iPhone) wouldn't scrobble if the app was not installed to the default location
    • Fixed a crash with iTunes device scrobbling introduced with the 'album artist' scrobble changes
    • Fixed a crash caused by fingerprinting .flac files
    • Fixed an issue where the volume would reset to maximum at the start of every radio track
    • Fixed a CPU usage issue
    ]]>
    Last.fm Scrobbler 2.1.21 Tue, 04 Sep 2012 14:20:00 +0000 Features
    • We now scrobble 'album artist' as well as 'artist' for cleaner album pages on the website (all plugins have been updated)
    • Fingerprinting local tracks has been added (can be turned off in preferences)
    • "Scrobbling is off" message has been added to the status bar

    Bug fixes

    • Better album images in the scrobble list
    • We now only ask you to install plugins for apps we've detcted you've installed
    • We now make sure you close your media players before plugins are installed
    • We now make sure you close WMP or Winamp before bootstrapping (won't bootstrap if they are left open)
    • Profile widget now refreshed when you switch to it so data doesn't get as out of date
    • Fixed some bugs in the preferences dialog
    ]]>
    Last.fm Scrobbler 2.1.20 Fri, 01 Jun 2012 16:30:00 +0000 Bug fixes
    • Fixed some radio playback issues introduced with the switch to VLC
    ]]>
    Last.fm Scrobbler 2.1.19 Wed, 30 May 2012 14:50:00 +0000 Features
    • [Mac] Media keys on your keyboard now control the radio - play/pause and skip
    • More scrobble tab design improvements

    Bug fixes

    • [Mac] Fixed radio playback issue - some users had trouble skipping between songs
      • Updated Phonon library from 4.4 to 4.6
      • Switched to VLC as the audio backend
      • Note: for users that didn't have this problem, they should notice some general playback performace improvements
      • Also note: we made these changes for Windows too - there wasn't this specific bug, but you may still notice a performance improvement
    ]]>
    Last.fm Scrobbler 2.1.18 Thr, 26 Apr 2012 16:30:00 +0000 Features
    • Scrobble tab design improvements
    • Can now delete scrobbles from the list through the right click menu
    • Added a licenses dialog for third party software
    • About box now has the Last.fm logo

    Bug fixes

    • Quit dialog now asks a question
    • Scrobble repeated tracks
    • Now restores same size and position when reopening
    • Initial window size adjusts for very small/low resolution screens
    • Doesn't filter other user's personal stations from your recent stations anymore
    • Windows installer now has updated images
    • Doesn't need to fetch track info when refreshing the scrobbles list anymore
    • Should now run on Mac OSX 10.5.x
    ]]>
    Last.fm Scrobbler 2.1.17 Mon, 14 Mar 2012 18:30:00 +0000 Features
    • Can now set the language - the app itself isn't translated yet, but this affects artist bios, etc
    • Scrobbles list rewritten and improved
      • Corrections asterisk with hover over now shown
      • Displays if the scrobble is cached and yet to be submitted
      • Displays if their was an error when submitting the scrobble - i.e. bad metadata
    • Profile, Friends, and Radio tab design improvements

    Bug fixes

    • Always start on the scrobble list when switching to that tab, not the previous track info view
    • Less calls made to track.getInfo
    • If app is closed to tray and notifications turned off, the only web services called are for scrobbling
    ]]>
    Last.fm Scrobbler 2.1.16 Thr, 15 Mar 2012 11:10:00 +0000 Features
    • Now has a max width of 800px
    • Use app icon for all radio stations

    Bug fixes

    • Fixed the now playing display bug
    • Fixed a crash that manifested itself in several places (cancelling getting info for a track)
    ]]>
    Last.fm Scrobbler Beta Version 2.1.15 Tue, 28 Feb 2012 16:00:00 +0000 Features
    • Radio controls are now in the sys tray too
    • Buy links now in the scrobble list view
    • There's now a diagnostics dialog - only one tab with the ability to force a check for iPod scrobbles and see the log so far

    Bug fixes

    • Dialogs, like preferences, are now brought to the front if reopened
    • Many copy changes around the app
    • Doesn't erroneously display two now playing tracks anymore
    • iPod scrobbles are submitted immeditaly instead of just cached
    • Doesn't fetch track info as much
    • The loved state of the current track is now reliable
    • [Win] iPod scrobbling should now work
    • [Win] 'Close' and 'Apply' buttons now work correctly
    • [Mac] Don't ask where Spotify is, if it isn't installed
    • [Mac] Extra dock icons no longer appear
    ]]>
    Last.fm Scrobbler Beta Version 2.1.14 Tue, 14 Feb 2012 13:30:00 +0000 This is the first public beta version

    Bug fixes

    • Updates to the wizard copy
    • Buy links now work for uk, us, and de users (was only ever doing uk links)
    • Other very minor fixes
    ]]>
    Last.fm Scrobbler Alpha Version 2.0.13 Fri, 10 Feb 2012 18:50:00 +0000 Features
    • Changed the API key. Everyone will have to go through the wizard again
    • [Mac] Now opens lastfm:// radio urls from the web site
    • [Mac] Upgraded to Qt 4.8.0
    • [Mac] Now tells you to close iTunes when there is a new plugin to install
    • Message bar now styles nicely

    Bug fixes

    • Doesn't show two now playing tracks in the scrobble list any more
    • Fixed quite a few bugs in the frist run wizard
    • Fixed some bugs with the account management settings
    • [Mac] Friend list trackpad scrolling is now smooth (Qt 4.8.0)
    ]]>
    Last.fm Scrobbler Version 2.0.12 Thr, 02 Feb 2012 18:20:00 +0000 Features
    • Now branded as a beta version!
    • Tag dialog now implemented to similar standard as the share dialog
    • Only fetches track metadata if the window is visable
    • Scrobble toggle now in preferences
    • 'Play' menu option is now not checkable, but has 'play', 'pause' and 'resume' states
    • Progress bar now reports the state correctly for podcasts and videos
    • Don't display metadata for Spotify ads

    Bug fixes

    • [Win] iPod scrobbling should now work (as well as it does on mac)! The installer was incorrect
    • iTunes plugin now handles device names with ' and \ in them correctly
    • Device names are now interpreted as utf-8 by the app
    ]]>
    Last.fm Scrobbler Version 2.0.11 Wed, 25 Jan 2012 20:10:00 +0000 Bug fixes 2.0.11
    • Incorrectly though some radio tracks were podcasts or videos

    Features - 2.0.10

    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes 2.0.10

    • Some user setting fixes, you will have to go through the wizard again
    • [Win] Fixes to iTunes plugin communication problems (named pipe type incompatibilities)
    • [Mac] Fixed iTunes plugin crashing iTunes for some Snow Leopard people
    • [Mac] Fixed the menubar not showing sometimes at startup
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.10 Wed, 25 Jan 2012 17:30:00 +0000 Features
    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes

    • Some user setting fixes, you will have to go through the wizard again
    • [Win] Fixes to iTunes plugin communication problems (named pipe type incompatibilities)
    • [Mac] Fixed iTunes plugin crashing iTunes for some Snow Leopard people
    • [Mac] Fixed the menubar not showing sometimes at startup
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.9 Mon, 16 Jan 2012 11:20:00 +0000 Bug fixes
    • [Win] New iTunes plugin was not working due to a change in Qt
    • iPod scrobble device associations were getting corrupted
    • Cleaned up and fixed some bugs in the apps user settings
    ]]>
    Last.fm Scrobbler Version 2.0.8 Thu, 12 Jan 2012 11:45:00 +0000 Features/Improvements
    • Settings toolbar now looks better and has assets
    • Crash Reporting implemented in client, but disabled. (needs oops server set up by ops)

    Bug fixes

    • [Mac] Stops from iTunes are picked up - end of a playlist, quit iTunes, etc
    • [Mac] Resume iTunes track after playing and pausing radio is now picked up
    • Spacebar now works again as a global shortcut
    ]]>
    Last.fm Scrobbler Version 2.0.7 Wed, 04 Jan 2012 12:45:00 +0000
  • Fixed a major bug with batch scrobbling
  • Fixed a crash for unkown length tracks (iTunes radio streams)
  • ]]>
    Last.fm Scrobbler Version 2.0.6 Fri, 23 Dec 2011 17:00:00 +0000 Last.fm Scrobbler Version 2.0.1 Wed, 02 Nov 2011 11:11:11 +0000
    ================================================ FILE: admin/dist/updates/updates.mac.beta.xml ================================================ Last.fm Scrobbler Updates https://cdn.last.fm/client/updates/updates.mac.beta.xml Last.fm Scrobbler Updates - Mac Beta en Last.fm Scrobbler 2.1.39 Wed, 18 Sep 2019 16:26:00 +0100 Features
    • This update is for compatibility with MacOS Catalina, where iTunes has been renamed to Music.
    • In this final update we have also fixed the iTunes / Apple Music now-playing text, and Apple Music sends a different player ID to Last.fm.
    ]]>
    Last.fm Scrobbler 2.1.38-beta2 Tue, 17 Sep 2019 10:52:00 +0100 Features
    • This update is for compatibility with MacOS Catalina, where iTunes has been renamed to Music.
    • In this beta update we have also fixed the iTunes / Apple Music launch buttons and the links Spotify Scrobbling.
    ]]>
    Last.fm Scrobbler 2.1.38-beta1 Mon, 16 Sep 2019 12:29:00 +0100 Features
    • This update is for compatibility with MacOS Catalina, where iTunes has been renamed to Music.
    • Please enjoy this beta update and address your kindest words to Stephen and Maciek for figuring out the issues involved in making this update possible.
    ]]>
    Last.fm Scrobbler 2.1.37 Tue, 22 Mar 2016 15:31:00 +0000 Features
    • This is mainly a security update for OSX El Capitan to allow loading of all future updates over HTTPS.
    • This update also removes some unused functions of the client.

    Bug Fixes

    • Some users were experiencing crashes on sleep/wake. This should be fixed.
    ]]>
    Last.fm Scrobbler 2.1.36 Tue, 03 Sep 2013 12:14:00 +0000 Features
    • New playback controls and progress bar design!

    Bug fixes

    • iPod scrobbles with multiple plays are now submitted at 1 second intervals instead of all at the same time
    • Fingerprinting is fixed and back - it's now also in a different process so fingerprinting problems don't bring down the whole app (just a precaution)
    • Fixed some memory leaks
    • Scrobble count on profile tab now updated correctly (unless you scrobble elsewhere)
    • Do not display corrections in the client (it wasn't respecting the website setting and was causing a few issues)
    • 'Similar Artist' header in the track metadata view is now a link to more similar artists on the site
    • There is now a warning/explanation in the iPod scrobble confirmation dialog when you have red/invalid iPod scrobbles
    • Now shows the correct modifier keys on Windows for the raise/hide Scrobbler option
    • Mute now works from the system tray and menu option
    • The last page of the wizard now doesn't say we've imported your listening history, if we haven't
    • Fixed an issue where the tray icon would be invisible if the first run wizard tour was skipped
    • Less logging (there was an issue with log file sizes getting very big)
    ]]>
    Last.fm Scrobbler 2.1.35 Wed, 06 Mar 2013 10:43:00 +0000 Features
    • Exclude directories! You can now exclude music in certain directories from being scrobbled
    • Systray/menu bar icon is greyed out when scrobbling is disabled
    • Users can now hide both the dock icon and the menu bar icon (restart only required when hiding dock icon on 10.6)
    • Binary delta updates will be supported from the release after this one
    • [Experimental] Added the ability to use SSL for all web services calls

    Bug fixes

    • Fingerprinting was causing crashes so has been temporarily disabled until we find a proper fix
    • Fixed an issue where it was possible to get two running versions of the applications after an update. Please note that as this fix was to the update mechanism it will only be corrected in the update after this one so please make sure you don't have two versions of the app running after this update.
    • Device scrobbling improvements
      • We now force that a compatible iTunes plugin version is installed before trying to device scrobble
      • Prompt to install the new plugin has been improved
    • Don't crash if Airfoil is connected during the first run wizard
    • Tracks now get their scrobble and loved status updated when the scrobble list is refreshed
    • Fixed scrobbling problems associated with setting the scrobble point to 100%
      • Fixed double scrobble and missed scrobbles
      • There's now a setting to turn of the maximum scrobble point being 4 minutes
      • Scrobble point and max scrobble turn off now take effect during the current track

  • Users that only authenticated with 2.1.32 will be asked to authenticate again. Sorry!
  • First run wizard's continue button no longer gets stuck
  • Bug fixes from 2.1.32

    • All users should now be able to authenticate - changed to a simpler authentication mechanism
    • Added a beta updates option - warning: only for the brave!
    • Fixed some minor account bootstrap issues
    • Open 'media player' links now work on Windows
    Full changelog ]]>
    Last.fm Scrobbler 2.1.30 Mon, 14 Jan 2013 16:20:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    • A temporary workaround for an device scrobbling error
    ]]>
    Last.fm Scrobbler 2.1.29 Mon, 14 Jan 2013 12:00:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enableing/disabling device scrobbling
    • Fixed a crash when switching user accounts
    ]]>
    Last.fm Scrobbler 2.1.28 Tue, 08 Jan 2013 11:30:00 +0000 Changes
    • Side bar images updated slightly

    Bug fixes

    • Restore the main window size and position correctly after restart
    • Fixed a crash caused by VLC when monitors plugged in, etc
    • Fixed spurious device scrobbles caused by a race condition
    • Fixed triple scrobbling when scrobbling offline cached scrobbles
    • Don't run the device scrobble checker so often - it was running almost all the time on Windows
    • Device scrobble performance improvements
    • Minimize and Zoom menu options now work
    ]]>
    Last.fm Scrobbler 2.1.27 Fri, 21 Dec 2012 15:30:00 +0000 Bug fixes
    • Fixed a crash on startup for new users
    • Fixed iTunes plays being picked up as device scrobbles Windows (may improve Mac too)
    ]]>
    Last.fm Scrobbler 2.1.26 Mon, 17 Dec 2012 12:15:00 +0000 Features
    • Translations! (almost complete)
    • Proxy settings - you can set them from the advanced settings tab and the first step of first run wizard
    • Text should now look nicer on Retina screens

    Bug fixes

    • iPod scrobbling - fixed a problem where non-local tracks (iTunes Match) would not get their playcounts synced and lead to duplicates scrobbles
    • Media keys are now ignored if you last listened to audio through another app
    • Don't divide by zero (crash) for users that are using the app on the same day as they registered
    • Fixed restarting the app in Mac, still not working on Windows.
    • Radio now goes into the stopped state correctly
    • Device scrobble Window is now a child of the main window (it appears positioned on top of it nicely now)
    • Fixed a double delete crash when the fingerprinter failed
    ]]>
    Last.fm Scrobbler 2.1.25 Thr, 25 Oct 2012 13:20:00 +0000 Features
    • Now called "Last.fm Scrobbler"
    • iPod scrobbling has been reworked to allow for WiFi sync - Scrobble your device before updating so you don't lose scrobbles
    • The app is now partially translated with translations from the old app. A fully translated version will be coming soon

    Bug fixes

    • Some CPU usage fixes
    • Fingerprinting backend has changed, fixing a rare crash
    • Other minor bugs
    ]]>
    Last.fm Scrobbler 2.1.21 Mon, 10 Sep 2012 10:26:00 +0000 Features
    • Added option to hide app icon from the dock (also has to hide app's menu bar, and icon in tab switcher)
    • We now scrobble 'album artist' as well as 'artist' for cleaner album pages on the website (all plugins have been updated)
    • Fingerprinting local tracks has been added (can be turned off in preferences)
    • "Scrobbling is off" message has been added to the status bar

    Bug fixes

    • Better album images in the scrobble list
    • We now only ask you to install plugins for apps we've detected you've installed
    • We now make sure you close your media players before plugins are installed
    • We now make sure you close WMP or Winamp before bootstrapping (won't bootstrap if they are left open)
    • Profile widget now refreshed when you switch to it so data doesn't get as out of date
    • Fixed some bugs in the preferences dialog
    ]]>
    Last.fm Scrobbler 2.1.20 Fri, 01 Jun 2012 14:20:00 +0000 Bug fixes
    • Fixed some radio playback issues introduced with the switch to VLC
    ]]>
    Last.fm Scrobbler 2.1.19 Wed, 30 May 2012 14:50:00 +0000 Features
    • Media keys on your keyboard now control the radio - play/pause and skip
    • More scrobble tab design improvements

    Bug fixes

    • Fixed radio playback issue - some users had trouble skipping between songs
      • Updated Phonon library from 4.4 to 4.6
      • Switched to VLC as the audio backend
      • Note: for users that didn't have this problem, they should notice some general playback performace improvements
      • Also note: we made these changes for Windows too - there wasn't this specific bug, but you may still notice a performance improvement
    ]]>
    Last.fm Scrobbler 2.1.18 Thu, 26 Apr 2012 19:20:00 +0000 Features
    • Scrobble tab design improvements
    • Can now delete scrobbles from the list through the right click menu
    • Added a licenses dialog for third party software
    • About box now has the Last.fm logo

    Bug fixes

    • Quit dialog now asks a question
    • Scrobble repeated tracks
    • Now restores same size and position when reopening
    • Initial window size adjusts for very small/low resolution screens
    • Doesn't filter other user's personal stations from your recent stations anymore
    • Windows installer now has updated images
    • Doesn't need to fetch track info when refreshing the scrobbles list anymore
    • Should now run on Mac OSX 10.5.x
    ]]>
    Last.fm Scrobbler 2.1.17 Tue, 10 Apr 2012 12:00:00 +0000 Features
    • Can now set the language - the app itself isn't translated yet, but this affects artist bios, etc
    • Scrobbles list rewritten and improved
      • Corrections asterisk with hover over now shown
      • Displays if the scrobble is cached and yet to be submitted
      • Displays if their was an error when submitting the scrobble - i.e. bad metadata
    • Profile, Friends, and Radio tab design improvements

    Bug fixes

    • Always start on the scrobble list when switching to that tab, not the previous track info view
    • Less calls made to track.getInfo
    • If app is closed to tray and notifications turned off, the only web services called are for scrobbling
    ]]>
    Last.fm Scrobbler 2.1.16 Thu, 15 Mar 2012 12:20:00 +0000 Features
    • Now has a max width of 800px
    • Use app icon for all radio stations

    Bug fixes

    • Fixed the now playing display bug
    • Fixed a crash that manifested itself in several places (cancelling getting info for a track)
    ]]>
    Last.fm Scrobbler Beta Version 2.1.15 Tue, 28 Feb 2012 16:00:00 +0000 Features
    • Radio controls are now in the sys tray too
    • Buy links now in the scrobble list view
    • There's now a diagnostics dialog - only one tab with the ability to force a check for iPod scrobbles and see the log so far

    Bug fixes

    • Dialogs, like preferences, are now brought to the front if reopened
    • Many copy changes around the app
    • Doesn't erroneously display two now playing tracks anymore
    • iPod scrobbles are submitted immeditaly instead of just cached
    • Doesn't fetch track info as much
    • The loved state of the current track is now reliable
    • Don't ask where Spotify is, if it isn't installed
    • Extra dock icons no longer appear
    ]]>
    Last.fm Scrobbler Beta Version 2.1.14 Tue, 14 Feb 2012 17:10:00 +0000 This is the first public beta version!!!

    Bug fixes

    • Updates to the wizard copy
    • Buy links now work for uk, us, and de users (was only ever doing uk links)
    • Other very minor fixes
    ]]>
    Last.fm Scrobbler Alpha Version 2.0.13 Fri, 10 Feb 2012 17:10:00 +0000 Features
    • Changed the API key. Everyone will have to go through the wizard again
    • Now opens lastfm:// radio urls from the web site
    • Upgraded to Qt 4.8.0
    • Now tells you to close iTunes when there is a new plugin to install
    • Message bar now styles nicely

    Bug fixes

    • Doesn't show two now playing tracks in the scrobble list any more
    • Fixed quite a few bugs in the frist run wizard
    • Fixed some bugs with the account management settings
    • Friend list trackpad scrolling is now smooth (Qt 4.8.0)
    ]]>
    Last.fm Scrobbler Version 2.0.12 Thu, 02 Feb 2012 17:10:00 +0000 Features
    • Now branded as a beta version!
    • Tag dialog now implemented to similar standard as the share dialog
    • Only fetches track metadata if the window is visable
    • Scrobble toggle now in preferences
    • 'Play' menu option is now not checkable, but has 'play', 'pause' and 'resume' states
    • Progress bar now reports the state correctly for podcasts and videos
    • Don't display metadata for Spotify ads

    Bug fixes

    • iTunes plugin now handles device names with ' and \ in them correctly
    • Device names are now interpreted as utf-8 by the app
    ]]>
    Last.fm Scrobbler Version 2.0.11 Wed, 25 Jan 2012 19:46:00 +0000 Bug fixes 2.0.11
    • Incorrectly thought some radio tracks were podcasts or videos

    Features - 2.0.10

    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes 2.0.10

    • Some user setting fixes, you will have to go through the wizard again
    • Fixed iTunes plugin crashing iTunes for some Snow Leopard people
    • Fixed the menubar not showing sometimes at startup
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.10 Wed, 25 Jan 2012 16:20:00 +0000 Features
    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes

    • Some user setting fixes, you will have to go through the wizard again
    • Fixed iTunes plugin crashing iTunes for some Snow Leopard people
    • Fixed the menubar not showing sometimes at startup
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.9 Mon, 16 Jan 2012 10:20:00 +0000 Bug fixes
    • iPod scrobble device associations were getting corrupted
    • Cleaned up and fixed some bugs in the apps user settings
    ]]>
    Last.fm Scrobbler Version 2.0.8 Thu, 12 Jan 2012 10:55:00 +0000 Features/Improvements
    • Settings toolbar now looks better and has assets
    • Crash Reporting implemented in client, but disabled. (needs oops server set up by ops)

    Bug fixes

    • Stops from iTunes are picked up - end of a playlist, quit iTunes, etc
    • Resume iTunes track after playing and pausing radio is now picked up
    • Spacebar now works again as a global shortcut
    ]]>
    Last.fm Scrobbler Version 2.0.7 Wed, 04 Jan 2012 12:20:00 +0000
  • Fixed a major bug with batch scrobbling
  • Fixed a crash for unkown length tracks (iTunes radio streams)
  • ]]>
    Last.fm Scrobbler Version 2.0.6 Fri, 23 Dec 2011 17:00:00 +0000
  • New friends list
  • Now installs media player plugins on Windows
  • Bootstrapping for all media players now works
  • Many other bug fixes and improvements
  • ]]>
    Last.fm Scrobbler Version 2.0.5 Thu, 17 Nov 2011 16:57:00 +0000
  • Lots of UI changes - welcome screen changed, metadata view improved, etc etc
  • Supports updates with libsparkle!
  • Uses the Growl framework to show notifications with album art
  • Airfoil metadata support
  • Scriptable with AppleScript (see the dictionary)
  • Campaign tracking when linking to the website
  • Radio quick start widget now shows artist and tag suggestions
  • Big play buttons now resume the last radio
  • Friends list now sorted by most recently listened
  • Switching tabs now has a slide animation
  • ]]>
    Last.fm Scrobbler Version 2.0.4 Tue, 08 Nov 2011 15:30:00 +0000 Embedded release notes! ... ]]>
    ================================================ FILE: admin/dist/updates/updates.mac.xml ================================================ Last.fm Scrobbler Updates https://cdn.last.fm/client/Mac/updates.xml Last.fm Scrobbler Updates - Mac en Last.fm Scrobbler 2.1.39 Wed, 18 Sep 2019 16:26:00 +0100 Features
    • This update ensures that scrobbling continues to work with Mac OS Catalina. It is now compatible with the Music app, which was formerly known as iTunes.

    Bug Fixes

    • The link to activate scrobbling in Spotify has been fixed. You can add scrobbling for Spotify through your Last.fm settings page.
    ]]>
    Last.fm Scrobbler 2.1.37 Tue, 22 Mar 2016 15:31:00 +0000 Features
    • This is mainly a security update for OSX El Capitan to allow loading of all future updates over HTTPS.
    • This update also removes some unused functions of the client.

    Bug Fixes

    • Some users were experiencing crashes on sleep/wake. This should be fixed.
    ]]>
    Last.fm Scrobbler 2.1.36 Tue, 03 Sep 2013 12:14:00 +0000 Features
    • New playback controls and progress bar design!

    Bug fixes

    • iPod scrobbles with multiple plays are now submitted at 1 second intervals instead of all at the same time
    • Fingerprinting is fixed and back - it's now also in a different process so fingerprinting problems don't bring down the whole app (just a precaution)
    • Fixed some memory leaks
    • Scrobble count on profile tab now updated correctly (unless you scrobble elsewhere)
    • Do not display corrections in the client (it wasn't respecting the website setting and was causing a few issues)
    • 'Similar Artist' header in the track metadata view is now a link to more similar artists on the site
    • There is now a warning/explanation in the iPod scrobble confirmation dialog when you have red/invalid iPod scrobbles
    • Now shows the correct modifier keys on Windows for the raise/hide Scrobbler option
    • Mute now works from the system tray and menu option
    • The last page of the wizard now doesn't say we've imported your listening history, if we haven't
    • Fixed an issue where the tray icon would be invisible if the first run wizard tour was skipped
    • Less logging (there was an issue with log file sizes getting very big)
    ]]>
    Last.fm Scrobbler 2.1.35 Wed, 06 Mar 2013 10:43:00 +0000 Features
    • Exclude directories! You can now exclude music in certain directories from being scrobbled
    • Systray/menu bar icon is greyed out when scrobbling is disabled
    • Users can now hide both the dock icon and the menu bar icon (restart only required when hiding dock icon on 10.6)
    • Binary delta updates will be supported from the release after this one
    • [Experimental] Added the ability to use SSL for all web services calls

    Bug fixes

    • Fingerprinting was causing crashes so has been temporarily disabled until we find a proper fix
    • Fixed an issue where it was possible to get two running versions of the applications after an update. Please note that as this fix was to the update mechanism it will only be corrected in the update after this one so please make sure you don't have two versions of the app running after this update.
    • Device scrobbling improvements
      • We now force that a compatible iTunes plugin version is installed before trying to device scrobble
      • Prompt to install the new plugin has been improved
    • Don't crash if Airfoil is connected during the first run wizard
    • Tracks now get their scrobble and loved status updated when the scrobble list is refreshed
    • Fixed scrobbling problems associated with setting the scrobble point to 100%
      • Fixed double scrobble and missed scrobbles
      • There's now a setting to turn of the maximum scrobble point being 4 minutes
      • Scrobble point and max scrobble turn off now take effect during the current track

  • Users that only authenticated with 2.1.32 will be asked to authenticate again. Sorry!
  • First run wizard's continue button no longer gets stuck
  • Bug fixes from 2.1.32

    • All users should now be able to authenticate - changed to a simpler authentication mechanism
    • Added a beta updates option - warning: only for the brave!
    • Fixed some minor account bootstrap issues
    • Open 'media player' links now work on Windows
    Full changelog ]]>
    Last.fm Scrobbler 2.1.30 Mon, 14 Jan 2013 16:20:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    • A temporary workaround for an device scrobbling error
    ]]>
    Last.fm Scrobbler 2.1.29 Mon, 14 Jan 2013 12:00:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enableing/disabling device scrobbling
    • Fixed a crash when switching user accounts
    ]]>
    Last.fm Scrobbler 2.1.28 Tue, 08 Jan 2013 11:30:00 +0000 Changes
    • Side bar images updated slightly

    Bug fixes

    • Restore the main window size and position correctly after restart
    • Fixed a crash caused by VLC when monitors plugged in, etc
    • Fixed spurious device scrobbles caused by a race condition
    • Fixed triple scrobbling when scrobbling offline cached scrobbles
    • Don't run the device scrobble checker so often - it was running almost all the time on Windows
    • Device scrobble performance improvements
    • Minimize and Zoom menu options now work
    ]]>
    Last.fm Scrobbler 2.1.27 Fri, 21 Dec 2012 15:30:00 +0000 Bug fixes
    • Fixed a crash on startup for new users
    • Fixed iTunes plays being picked up as device scrobbles Windows (may improve Mac too)
    ]]>
    Last.fm Scrobbler 2.1.26 Mon, 17 Dec 2012 12:15:00 +0000 Features
    • Translations! (almost complete)
    • Proxy settings - you can set them from the advanced settings tab and the first step of first run wizard
    • Text should now look nicer on Retina screens

    Bug fixes

    • iPod scrobbling - fixed a problem where non-local tracks (iTunes Match) would not get their playcounts synced and lead to duplicates scrobbles
    • Media keys are now ignored if you last listened to audio through another app
    • Don't divide by zero (crash) for users that are using the app on the same day as they registered
    • Fixed restarting the app in Mac, still not working on Windows.
    • Radio now goes into the stopped state correctly
    • Device scrobble Window is now a child of the main window (it appears positioned on top of it nicely now)
    • Fixed a double delete crash when the fingerprinter failed
    ]]>
    Last.fm Scrobbler 2.1.25 Thr, 25 Oct 2012 13:20:00 +0000 Features
    • Now called "Last.fm Scrobbler"
    • iPod scrobbling has been reworked to allow for WiFi sync - Scrobble your device before updating so you don't lose scrobbles
    • The app is now partially translated with translations from the old app. A fully translated version will be coming soon

    Bug fixes

    • Some CPU usage fixes
    • Fingerprinting backend has changed, fixing a rare crash
    • Other minor bugs
    ]]>
    Last.fm Scrobbler 2.1.21 Mon, 10 Sep 2012 10:26:00 +0000 Features
    • Added option to hide app icon from the dock (also has to hide app's menu bar, and icon in tab switcher)
    • We now scrobble 'album artist' as well as 'artist' for cleaner album pages on the website (all plugins have been updated)
    • Fingerprinting local tracks has been added (can be turned off in preferences)
    • "Scrobbling is off" message has been added to the status bar

    Bug fixes

    • Better album images in the scrobble list
    • We now only ask you to install plugins for apps we've detected you've installed
    • We now make sure you close your media players before plugins are installed
    • We now make sure you close WMP or Winamp before bootstrapping (won't bootstrap if they are left open)
    • Profile widget now refreshed when you switch to it so data doesn't get as out of date
    • Fixed some bugs in the preferences dialog
    ]]>
    Last.fm Scrobbler 2.1.20 Fri, 01 Jun 2012 14:20:00 +0000 Bug fixes
    • Fixed some radio playback issues introduced with the switch to VLC
    ]]>
    Last.fm Scrobbler 2.1.19 Wed, 30 May 2012 14:50:00 +0000 Features
    • Media keys on your keyboard now control the radio - play/pause and skip
    • More scrobble tab design improvements

    Bug fixes

    • Fixed radio playback issue - some users had trouble skipping between songs
      • Updated Phonon library from 4.4 to 4.6
      • Switched to VLC as the audio backend
      • Note: for users that didn't have this problem, they should notice some general playback performace improvements
      • Also note: we made these changes for Windows too - there wasn't this specific bug, but you may still notice a performance improvement
    ]]>
    Last.fm Scrobbler 2.1.18 Thu, 26 Apr 2012 19:20:00 +0000 Features
    • Scrobble tab design improvements
    • Can now delete scrobbles from the list through the right click menu
    • Added a licenses dialog for third party software
    • About box now has the Last.fm logo

    Bug fixes

    • Quit dialog now asks a question
    • Scrobble repeated tracks
    • Now restores same size and position when reopening
    • Initial window size adjusts for very small/low resolution screens
    • Doesn't filter other user's personal stations from your recent stations anymore
    • Windows installer now has updated images
    • Doesn't need to fetch track info when refreshing the scrobbles list anymore
    • Should now run on Mac OSX 10.5.x
    ]]>
    Last.fm Scrobbler 2.1.17 Tue, 10 Apr 2012 12:00:00 +0000 Features
    • Can now set the language - the app itself isn't translated yet, but this affects artist bios, etc
    • Scrobbles list rewritten and improved
      • Corrections asterisk with hover over now shown
      • Displays if the scrobble is cached and yet to be submitted
      • Displays if their was an error when submitting the scrobble - i.e. bad metadata
    • Profile, Friends, and Radio tab design improvements

    Bug fixes

    • Always start on the scrobble list when switching to that tab, not the previous track info view
    • Less calls made to track.getInfo
    • If app is closed to tray and notifications turned off, the only web services called are for scrobbling
    ]]>
    Last.fm Scrobbler 2.1.16 Thu, 15 Mar 2012 12:20:00 +0000 Features
    • Now has a max width of 800px
    • Use app icon for all radio stations

    Bug fixes

    • Fixed the now playing display bug
    • Fixed a crash that manifested itself in several places (cancelling getting info for a track)
    ]]>
    Last.fm Scrobbler Beta Version 2.1.15 Tue, 28 Feb 2012 16:00:00 +0000 Features
    • Radio controls are now in the sys tray too
    • Buy links now in the scrobble list view
    • There's now a diagnostics dialog - only one tab with the ability to force a check for iPod scrobbles and see the log so far

    Bug fixes

    • Dialogs, like preferences, are now brought to the front if reopened
    • Many copy changes around the app
    • Doesn't erroneously display two now playing tracks anymore
    • iPod scrobbles are submitted immeditaly instead of just cached
    • Doesn't fetch track info as much
    • The loved state of the current track is now reliable
    • Don't ask where Spotify is, if it isn't installed
    • Extra dock icons no longer appear
    ]]>
    Last.fm Scrobbler Beta Version 2.1.14 Tue, 14 Feb 2012 17:10:00 +0000 This is the first public beta version!!!

    Bug fixes

    • Updates to the wizard copy
    • Buy links now work for uk, us, and de users (was only ever doing uk links)
    • Other very minor fixes
    ]]>
    Last.fm Scrobbler Alpha Version 2.0.13 Fri, 10 Feb 2012 17:10:00 +0000 Features
    • Changed the API key. Everyone will have to go through the wizard again
    • Now opens lastfm:// radio urls from the web site
    • Upgraded to Qt 4.8.0
    • Now tells you to close iTunes when there is a new plugin to install
    • Message bar now styles nicely

    Bug fixes

    • Doesn't show two now playing tracks in the scrobble list any more
    • Fixed quite a few bugs in the frist run wizard
    • Fixed some bugs with the account management settings
    • Friend list trackpad scrolling is now smooth (Qt 4.8.0)
    ]]>
    Last.fm Scrobbler Version 2.0.12 Thu, 02 Feb 2012 17:10:00 +0000 Features
    • Now branded as a beta version!
    • Tag dialog now implemented to similar standard as the share dialog
    • Only fetches track metadata if the window is visable
    • Scrobble toggle now in preferences
    • 'Play' menu option is now not checkable, but has 'play', 'pause' and 'resume' states
    • Progress bar now reports the state correctly for podcasts and videos
    • Don't display metadata for Spotify ads

    Bug fixes

    • iTunes plugin now handles device names with ' and \ in them correctly
    • Device names are now interpreted as utf-8 by the app
    ]]>
    Last.fm Scrobbler Version 2.0.11 Wed, 25 Jan 2012 19:46:00 +0000 Bug fixes 2.0.11
    • Incorrectly thought some radio tracks were podcasts or videos

    Features - 2.0.10

    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes 2.0.10

    • Some user setting fixes, you will have to go through the wizard again
    • Fixed iTunes plugin crashing iTunes for some Snow Leopard people
    • Fixed the menubar not showing sometimes at startup
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.10 Wed, 25 Jan 2012 16:20:00 +0000 Features
    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes

    • Some user setting fixes, you will have to go through the wizard again
    • Fixed iTunes plugin crashing iTunes for some Snow Leopard people
    • Fixed the menubar not showing sometimes at startup
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.9 Mon, 16 Jan 2012 10:20:00 +0000 Bug fixes
    • iPod scrobble device associations were getting corrupted
    • Cleaned up and fixed some bugs in the apps user settings
    ]]>
    Last.fm Scrobbler Version 2.0.8 Thu, 12 Jan 2012 10:55:00 +0000 Features/Improvements
    • Settings toolbar now looks better and has assets
    • Crash Reporting implemented in client, but disabled. (needs oops server set up by ops)

    Bug fixes

    • Stops from iTunes are picked up - end of a playlist, quit iTunes, etc
    • Resume iTunes track after playing and pausing radio is now picked up
    • Spacebar now works again as a global shortcut
    ]]>
    Last.fm Scrobbler Version 2.0.7 Wed, 04 Jan 2012 12:20:00 +0000
  • Fixed a major bug with batch scrobbling
  • Fixed a crash for unkown length tracks (iTunes radio streams)
  • ]]>
    Last.fm Scrobbler Version 2.0.6 Fri, 23 Dec 2011 17:00:00 +0000
  • New friends list
  • Now installs media player plugins on Windows
  • Bootstrapping for all media players now works
  • Many other bug fixes and improvements
  • ]]>
    Last.fm Scrobbler Version 2.0.5 Thu, 17 Nov 2011 16:57:00 +0000
  • Lots of UI changes - welcome screen changed, metadata view improved, etc etc
  • Supports updates with libsparkle!
  • Uses the Growl framework to show notifications with album art
  • Airfoil metadata support
  • Scriptable with AppleScript (see the dictionary)
  • Campaign tracking when linking to the website
  • Radio quick start widget now shows artist and tag suggestions
  • Big play buttons now resume the last radio
  • Friends list now sorted by most recently listened
  • Switching tabs now has a slide animation
  • ]]>
    Last.fm Scrobbler Version 2.0.4 Tue, 08 Nov 2011 15:30:00 +0000 Embedded release notes! ... ]]>
    ================================================ FILE: admin/dist/updates/updates.win.beta.xml ================================================ Last.fm Scrobbler Updates http://cdn.last.fm/client/updates/updates.win.beta.xml Last.fm Scrobbler Updates - Windows Beta en Last.fm Scrobbler 2.1.36 Tue, 03 Sep 2013 12:14:00 +0000 Features
    • New playback controls and progress bar design!

    Bug fixes

    • iPod scrobbles with multiple plays are now submitted at 1 second intervals instead of all at the same time
    • Fingerprinting is fixed and back - it's now also in a different process so fingerprinting problems don't bring down the whole app (just a precaution)
    • Fixed some memory leaks
    • Scrobble count on profile tab now updated correctly (unless you scrobble elsewhere)
    • Do not display corrections in the client (it wasn't respecting the website setting and was causing a few issues)
    • 'Similar Artist' header in the track metadata view is now a link to more similar artists on the site
    • There is now a warning/explanation in the iPod scrobble confirmation dialog when you have red/invalid iPod scrobbles
    • Now shows the correct modifier keys on Windows for the raise/hide Scrobbler option
    • Mute now works from the system tray and menu option
    • The last page of the wizard now doesn't say we've imported your listening history, if we haven't
    • Fixed an issue where the tray icon would be invisible if the first run wizard tour was skipped
    • Less logging (there was an issue with log file sizes getting very big)
    ]]>
    Last.fm Scrobbler 2.1.35 Wed, 06 Mar 2013 11:29:00 +0000 Features
    • Exclude directories! You can now exclude music in certain directories from being scrobbled
    • Systray/menu bar icon is greyed out when scrobbling is disabled
    • New updating framework - currently just looks better and is smaller, but it's the first step in better Windows updating
    • [Experimental] Added the ability to use SSL for all web services calls

    Bug fixes

    • Fingerprinting was causing crashes so has been temporarily disabled until we find a proper fix
    • Device scrobbling improvements
      • We now force that a compatible iTunes plugin version is installed before trying to device scrobble
      • Prompt to install the new plugin has been improved
      • Tracks are now identified by persistent id rather then filename - fixes a few problems and should be more robust
    • Windows Media Player scrobbles now include 'album artist' (just like other media players already do)
    • Only show 'Open iTunes' if it has been installed
    • Settings dialog resizing improved
    • Correctly display track metatdata that has characters such as < in it
    • The prompt to restart now actually restarts the app
    • When installing a plugin from the file menu the full installer is shown - user can select install location, if not default, etc
    • Tracks now get their scrobble and loved status updated when the scrobble list is refreshed
    • Fixed scrobbling problems associated with setting the scrobble point to 100%
      • Fixed double scrobble and missed scrobbles
      • There's now a setting to turn of the maximum scrobble point being 4 minutes
      • Scrobble point and max scrobble turn off now take effect during the current track
    ]]>
    Last.fm Scrobbler 2.1.33 Thr, 24 Jan 2013 11:00:00 +0000
  • First run wizard's continue button no longer gets stuck
  • iTunes plugin update for XP scrobbling
  • Full changelog ]]>
    Last.fm Scrobbler 2.1.32 Tue, 22 Jan 2013 13:48:00 +0000 Bug fixes
    • All users should now be able to authenticate - changed to a simpler authentication mechanism
    • Now runs on Windows XP
    • Added a beta updates option - warning: only for the brave!
    • Fixed some minor account bootstrap issues
    • Open 'media player' links now work on Windows
    ]]>
    Last.fm Scrobbler 2.1.30 (First full release!) Mon, 14 Jan 2013 16:20:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    • A temporary workaround for a device scrobbling error
    ]]>
    Last.fm Scrobbler 2.1.29 Mon, 14 Jan 2013 12:00:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    ]]>
    Last.fm Scrobbler 2.1.28 Tue, 08 Jan 2013 11:30:00 +0000 Changes
    • Side bar images updated slightly

    Bug fixes

    • Restore the main window size and position correctly after restart
    • Fixed a crash when fingerprinting some audio files
    • Fixed spurious device scrobbles caused by a race condition
    • Fixed triple scrobbling when scrobbling offline cached scrobbles
    • Don't run the device scrobble checker so often - it was running almost all the time on Windows
    • Device scrobble performance improvements
    • Minimize and Zoom menu options now work
    ]]>
    Last.fm Scrobbler 2.1.27 Fri, 21 Dec 2012 15:30:00 +0000 Bug fixes
    • Fixed a crash on startup for new users
    • Fixed iTunes plays being picked up as device scrobbles Windows (may improve Mac too)
    ]]>
    Last.fm Scrobbler 2.1.26 Mon, 17 Dec 2012 12:15:00 +0000 Features
    • Translations! (almost complete)
    • Proxy settings - you can set them from the advanced settings tab and the first step of first run wizard
    • iPod scrobbling has been reworked to allow for WiFi sync

    Bug fixes

    • iPod scrobbling
      • iTunes plugin and app now both use the same playcounts db (they didn't before!?)
      • Tracks that end within 20 seconds of starting (user skipped to near the end) now get their playcounts synced (was leading to duplicate scrobbles)
      • iTunes no longer kept open by us not releasing a handle to it
      • Fixed a problem where the iTunes plugin would stop communicating with the app so scrobbling stopped (this also caused the plays to be picked up as iTunes device scrobbles)
      • Fixed a problem where the last track of a playlist would not get it's playcount synced leading to it being picked up as an iTunes device scrobble
    • Don't divide by zero (crash) for users that are using the app on the same day as they registered
    • Fixed restarting the app in Mac, still not working on Windows.
    • Radio now goes into the stopped state correctly
    • Device scrobble Window is now a child of the main window (it appears positioned on top of it nicely now)
    • Fixed a double delete crash when the fingerprinter failed
    ]]>
    Last.fm Scrobbler 2.1.24 Wed, 26 Sep 2012 11:50:00 +0000 2.1.24

    Windows only bug fix release

    • Fixed an issue where iTunes devices (iPod/iPhone) wouldn't scrobble if the app was not installed to the default location
    • Fixed a crash with iTunes device scrobbling introduced with the 'album artist' scrobble changes
    • Fixed a crash caused by fingerprinting .flac files
    • Fixed an issue where the volume would reset to maximum at the start of every radio track
    • Fixed a CPU usage issue
    ]]>
    Last.fm Scrobbler 2.1.21 Tue, 04 Sep 2012 14:20:00 +0000 2.1.21

    Features

    • We now scrobble 'album artist' as well as 'artist' for cleaner album pages on the website (all plugins have been updated)
    • Fingerprinting local tracks has been added (can be turned off in preferences)
    • "Scrobbling is off" message has been added to the status bar

    Bug fixes

    • Better album images in the scrobble list
    • We now only ask you to install plugins for apps we've detcted you've installed
    • We now make sure you close your media players before plugins are installed
    • We now make sure you close WMP or Winamp before bootstrapping (won't bootstrap if they are left open)
    • Profile widget now refreshed when you switch to it so data doesn't get as out of date
    • Fixed some bugs in the preferences dialog
    ]]>
    Last.fm Scrobbler 2.1.20 Fri, 01 Jun 2012 16:30:00 +0000 Bug fixes
    • Fixed some radio playback issues introduced with the switch to VLC
    • Fixed iPod scrobbling (was broken in 2.1.19)
    ]]>
    Last.fm Scrobbler 2.1.19 Wed, 30 May 2012 14:50:00 +0000 Features
    • More scrobble tab design improvements
    ]]>
    Last.fm Scrobbler 2.1.18 Thr, 26 Apr 2012 16:30:00 +0000 Features
    • Scrobble tab design improvements
    • Can now delete scrobbles from the list through the right click menu
    • Added a licenses dialog for third party software
    • About box now has the Last.fm logo

    Bug fixes

    • Quit dialog now asks a question
    • Scrobble repeated tracks
    • Now restores same size and position when reopening
    • Initial window size adjusts for very small/low resolution screens
    • Doesn't filter other user's personal stations from your recent stations anymore
    • Windows installer now has updated images
    • Doesn't need to fetch track info when refreshing the scrobbles list anymore
    • Should now run on Mac OSX 10.5.x
    ]]>
    Last.fm Scrobbler 2.1.17 Mon, 14 Mar 2012 18:30:00 +0000 Features
    • Can now set the language - the app itself isn't translated yet, but this affects artist bios, etc
    • Scrobbles list rewritten and improved
      • Corrections asterisk with hover over now shown
      • Displays if the scrobble is cached and yet to be submitted
      • Displays if their was an error when submitting the scrobble - i.e. bad metadata
    • Profile, Friends, and Radio tab design improvements

    Bug fixes

    • Always start on the scrobble list when switching to that tab, not the previous track info view
    • Less calls made to track.getInfo
    • If app is closed to tray and notifications turned off, the only web services called are for scrobbling
    ]]>
    Last.fm Scrobbler 2.1.16 Thr, 15 Mar 2012 11:10:00 +0000 Features
    • Now has a max width of 800px
    • Use app icon for all radio stations

    Bug fixes

    • Fixed the now playing display bug
    • Fixed a crash that manifested itself in several places (cancelling getting info for a track)
    ]]>
    Last.fm Scrobbler Beta Version 2.1.15 Tue, 28 Feb 2012 16:00:00 +0000 Features
    • Radio controls are now in the sys tray too
    • Buy links now in the scrobble list view
    • There's now a diagnostics dialog - only one tab with the ability to force a check for iPod scrobbles and see the log so far

    Bug fixes

    • Dialogs, like preferences, are now brought to the front if reopened
    • Many copy changes around the app
    • Doesn't erroneously display two now playing tracks anymore
    • iPod scrobbles are submitted immeditaly instead of just cached
    • Doesn't fetch track info as much
    • The loved state of the current track is now reliable
    • iPod scrobbling should now work
    • 'Close' and 'Apply' buttons now work correctly
    ]]>
    Last.fm Scrobbler Beta Version 2.1.14 Tue, 14 Feb 2012 13:30:00 +0000 This is the first public beta version

    Bug fixes

    • Updates to the wizard copy
    • Buy links now work for uk, us, and de users (was only ever doing uk links)
    • Other very minor fixes
    ]]>
    Last.fm Scrobbler Alpha Version 2.0.13 Fri, 10 Feb 2012 18:50:00 +0000 Features
    • Changed the API key. Everyone will have to go through the wizard again
    • Message bar now styles nicely

    Bug fixes

    • Doesn't show two now playing tracks in the scrobble list any more
    • Fixed quite a few bugs in the frist run wizard
    • Fixed some bugs with the account management settings
    ]]>
    Last.fm Scrobbler Version 2.0.12 Thr, 02 Feb 2012 18:20:00 +0000 Features
    • Now branded as a beta version!
    • Tag dialog now implemented to similar standard as the share dialog
    • Only fetches track metadata if the window is visable
    • Scrobble toggle now in preferences
    • 'Play' menu option is now not checkable, but has 'play', 'pause' and 'resume' states
    • Progress bar now reports the state correctly for podcasts and videos
    • Don't display metadata for Spotify ads

    Bug fixes

    • iPod scrobbling should now work (as well as it does on mac)! The installer was incorrect
    • iTunes plugin now handles device names with ' and \ in them correctly
    • Device names are now interpreted as utf-8 by the app
    ]]>
    Last.fm Scrobbler Version 2.0.11 Wed, 25 Jan 2012 20:10:00 +0000 Bug fixes 2.0.11
    • Incorrectly though some radio tracks were podcasts or videos

    Features - 2.0.10

    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes 2.0.10

    • Some user setting fixes, you will have to go through the wizard again
    • Fixes to iTunes plugin communication problems (named pipe type incompatibilities)
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.10 Wed, 25 Jan 2012 17:30:00 +0000 Features
    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes

    • Some user setting fixes, you will have to go through the wizard again
    • Fixes to iTunes plugin communication problems (named pipe type incompatibilities)
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.9 Mon, 16 Jan 2012 11:20:00 +0000 Bug fixes
    • New iTunes plugin was not working due to a change in Qt
    • iPod scrobble device associations were getting corrupted
    • Cleaned up and fixed some bugs in the apps user settings
    ]]>
    Last.fm Scrobbler Version 2.0.8 Thu, 12 Jan 2012 11:45:00 +0000 Features/Improvements
    • Settings toolbar now looks better and has assets
    • Crash Reporting implemented in client, but disabled. (needs oops server set up by ops)

    Bug fixes

    • Spacebar now works again as a global shortcut
    ]]>
    Last.fm Scrobbler Version 2.0.7 Wed, 04 Jan 2012 12:45:00 +0000
  • Fixed a major bug with batch scrobbling
  • Fixed a crash for unkown length tracks (iTunes radio streams)
  • ]]>
    Last.fm Scrobbler Version 2.0.6 Fri, 23 Dec 2011 17:00:00 +0000 Last.fm Scrobbler Version 2.0.1 Wed, 02 Nov 2011 11:11:11 +0000
    ================================================ FILE: admin/dist/updates/updates.win.xml ================================================ Last.fm Scrobbler Updates http://cdn.last.fm/client/updates/updates.win.xml Last.fm Scrobbler Updates - Windows en Last.fm Scrobbler 2.1.36 Tue, 03 Sep 2013 12:14:00 +0000 Features
    • New playback controls and progress bar design!

    Bug fixes

    • iPod scrobbles with multiple plays are now submitted at 1 second intervals instead of all at the same time
    • Fingerprinting is fixed and back - it's now also in a different process so fingerprinting problems don't bring down the whole app (just a precaution)
    • Fixed some memory leaks
    • Scrobble count on profile tab now updated correctly (unless you scrobble elsewhere)
    • Do not display corrections in the client (it wasn't respecting the website setting and was causing a few issues)
    • 'Similar Artist' header in the track metadata view is now a link to more similar artists on the site
    • There is now a warning/explanation in the iPod scrobble confirmation dialog when you have red/invalid iPod scrobbles
    • Now shows the correct modifier keys on Windows for the raise/hide Scrobbler option
    • Mute now works from the system tray and menu option
    • The last page of the wizard now doesn't say we've imported your listening history, if we haven't
    • Fixed an issue where the tray icon would be invisible if the first run wizard tour was skipped
    • Less logging (there was an issue with log file sizes getting very big)
    ]]>
    Last.fm Scrobbler 2.1.35 Wed, 06 Mar 2013 11:29:00 +0000 Features
    • Exclude directories! You can now exclude music in certain directories from being scrobbled
    • Systray/menu bar icon is greyed out when scrobbling is disabled
    • New updating framework - currently just looks better and is smaller, but it's the first step in better Windows updating
    • [Experimental] Added the ability to use SSL for all web services calls

    Bug fixes

    • Fingerprinting was causing crashes so has been temporarily disabled until we find a proper fix
    • Device scrobbling improvements
      • We now force that a compatible iTunes plugin version is installed before trying to device scrobble
      • Prompt to install the new plugin has been improved
      • Tracks are now identified by persistent id rather then filename - fixes a few problems and should be more robust
    • Windows Media Player scrobbles now include 'album artist' (just like other media players already do)
    • Only show 'Open iTunes' if it has been installed
    • Settings dialog resizing improved
    • Correctly display track metatdata that has characters such as < in it
    • The prompt to restart now actually restarts the app
    • When installing a plugin from the file menu the full installer is shown - user can select install location, if not default, etc
    • Tracks now get their scrobble and loved status updated when the scrobble list is refreshed
    • Fixed scrobbling problems associated with setting the scrobble point to 100%
      • Fixed double scrobble and missed scrobbles
      • There's now a setting to turn of the maximum scrobble point being 4 minutes
      • Scrobble point and max scrobble turn off now take effect during the current track
    ]]>
    Last.fm Scrobbler 2.1.33 Thr, 24 Jan 2013 11:00:00 +0000
  • First run wizard's continue button no longer gets stuck
  • iTunes plugin update for XP scrobbling
  • Full changelog ]]>
    Last.fm Scrobbler 2.1.32 Tue, 22 Jan 2013 13:48:00 +0000 Bug fixes
    • All users should now be able to authenticate - changed to a simpler authentication mechanism
    • Now runs on Windows XP
    • Added a beta updates option - warning: only for the brave!
    • Fixed some minor account bootstrap issues
    • Open 'media player' links now work on Windows
    ]]>
    Last.fm Scrobbler 2.1.30 (First full release!) Mon, 14 Jan 2013 16:20:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    • A temporary workaround for a device scrobbling error
    ]]>
    Last.fm Scrobbler 2.1.29 Mon, 14 Jan 2013 12:00:00 +0000 Bug fixes
    • Translations updated and complete
    • Don't fingerprint files from audio CDs
    • Fixed some problems upgrading from The Scrobbler 1.x
    • Fixed a crash when enabling/disabling device scrobbling
    • Fixed a crash when switching user accounts
    ]]>
    Last.fm Scrobbler 2.1.28 Tue, 08 Jan 2013 11:30:00 +0000 Changes
    • Side bar images updated slightly

    Bug fixes

    • Restore the main window size and position correctly after restart
    • Fixed a crash when fingerprinting some audio files
    • Fixed spurious device scrobbles caused by a race condition
    • Fixed triple scrobbling when scrobbling offline cached scrobbles
    • Don't run the device scrobble checker so often - it was running almost all the time on Windows
    • Device scrobble performance improvements
    • Minimize and Zoom menu options now work
    ]]>
    Last.fm Scrobbler 2.1.27 Fri, 21 Dec 2012 15:30:00 +0000 Bug fixes
    • Fixed a crash on startup for new users
    • Fixed iTunes plays being picked up as device scrobbles Windows (may improve Mac too)
    ]]>
    Last.fm Scrobbler 2.1.26 Mon, 17 Dec 2012 12:15:00 +0000 Features
    • Translations! (almost complete)
    • Proxy settings - you can set them from the advanced settings tab and the first step of first run wizard
    • iPod scrobbling has been reworked to allow for WiFi sync

    Bug fixes

    • iPod scrobbling
      • iTunes plugin and app now both use the same playcounts db (they didn't before!?)
      • Tracks that end within 20 seconds of starting (user skipped to near the end) now get their playcounts synced (was leading to duplicate scrobbles)
      • iTunes no longer kept open by us not releasing a handle to it
      • Fixed a problem where the iTunes plugin would stop communicating with the app so scrobbling stopped (this also caused the plays to be picked up as iTunes device scrobbles)
      • Fixed a problem where the last track of a playlist would not get it's playcount synced leading to it being picked up as an iTunes device scrobble
    • Don't divide by zero (crash) for users that are using the app on the same day as they registered
    • Fixed restarting the app in Mac, still not working on Windows.
    • Radio now goes into the stopped state correctly
    • Device scrobble Window is now a child of the main window (it appears positioned on top of it nicely now)
    • Fixed a double delete crash when the fingerprinter failed
    ]]>
    Last.fm Scrobbler 2.1.24 Wed, 26 Sep 2012 11:50:00 +0000 2.1.24

    Windows only bug fix release

    • Fixed an issue where iTunes devices (iPod/iPhone) wouldn't scrobble if the app was not installed to the default location
    • Fixed a crash with iTunes device scrobbling introduced with the 'album artist' scrobble changes
    • Fixed a crash caused by fingerprinting .flac files
    • Fixed an issue where the volume would reset to maximum at the start of every radio track
    • Fixed a CPU usage issue
    ]]>
    Last.fm Scrobbler 2.1.21 Tue, 04 Sep 2012 14:20:00 +0000 2.1.21

    Features

    • We now scrobble 'album artist' as well as 'artist' for cleaner album pages on the website (all plugins have been updated)
    • Fingerprinting local tracks has been added (can be turned off in preferences)
    • "Scrobbling is off" message has been added to the status bar

    Bug fixes

    • Better album images in the scrobble list
    • We now only ask you to install plugins for apps we've detcted you've installed
    • We now make sure you close your media players before plugins are installed
    • We now make sure you close WMP or Winamp before bootstrapping (won't bootstrap if they are left open)
    • Profile widget now refreshed when you switch to it so data doesn't get as out of date
    • Fixed some bugs in the preferences dialog
    ]]>
    Last.fm Scrobbler 2.1.20 Fri, 01 Jun 2012 16:30:00 +0000 Bug fixes
    • Fixed some radio playback issues introduced with the switch to VLC
    • Fixed iPod scrobbling (was broken in 2.1.19)
    ]]>
    Last.fm Scrobbler 2.1.19 Wed, 30 May 2012 14:50:00 +0000 Features
    • More scrobble tab design improvements
    ]]>
    Last.fm Scrobbler 2.1.18 Thr, 26 Apr 2012 16:30:00 +0000 Features
    • Scrobble tab design improvements
    • Can now delete scrobbles from the list through the right click menu
    • Added a licenses dialog for third party software
    • About box now has the Last.fm logo

    Bug fixes

    • Quit dialog now asks a question
    • Scrobble repeated tracks
    • Now restores same size and position when reopening
    • Initial window size adjusts for very small/low resolution screens
    • Doesn't filter other user's personal stations from your recent stations anymore
    • Windows installer now has updated images
    • Doesn't need to fetch track info when refreshing the scrobbles list anymore
    • Should now run on Mac OSX 10.5.x
    ]]>
    Last.fm Scrobbler 2.1.17 Mon, 14 Mar 2012 18:30:00 +0000 Features
    • Can now set the language - the app itself isn't translated yet, but this affects artist bios, etc
    • Scrobbles list rewritten and improved
      • Corrections asterisk with hover over now shown
      • Displays if the scrobble is cached and yet to be submitted
      • Displays if their was an error when submitting the scrobble - i.e. bad metadata
    • Profile, Friends, and Radio tab design improvements

    Bug fixes

    • Always start on the scrobble list when switching to that tab, not the previous track info view
    • Less calls made to track.getInfo
    • If app is closed to tray and notifications turned off, the only web services called are for scrobbling
    ]]>
    Last.fm Scrobbler 2.1.16 Thr, 15 Mar 2012 11:10:00 +0000 Features
    • Now has a max width of 800px
    • Use app icon for all radio stations

    Bug fixes

    • Fixed the now playing display bug
    • Fixed a crash that manifested itself in several places (cancelling getting info for a track)
    ]]>
    Last.fm Scrobbler Beta Version 2.1.15 Tue, 28 Feb 2012 16:00:00 +0000 Features
    • Radio controls are now in the sys tray too
    • Buy links now in the scrobble list view
    • There's now a diagnostics dialog - only one tab with the ability to force a check for iPod scrobbles and see the log so far

    Bug fixes

    • Dialogs, like preferences, are now brought to the front if reopened
    • Many copy changes around the app
    • Doesn't erroneously display two now playing tracks anymore
    • iPod scrobbles are submitted immeditaly instead of just cached
    • Doesn't fetch track info as much
    • The loved state of the current track is now reliable
    • iPod scrobbling should now work
    • 'Close' and 'Apply' buttons now work correctly
    ]]>
    Last.fm Scrobbler Beta Version 2.1.14 Tue, 14 Feb 2012 13:30:00 +0000 This is the first public beta version

    Bug fixes

    • Updates to the wizard copy
    • Buy links now work for uk, us, and de users (was only ever doing uk links)
    • Other very minor fixes
    ]]>
    Last.fm Scrobbler Alpha Version 2.0.13 Fri, 10 Feb 2012 18:50:00 +0000 Features
    • Changed the API key. Everyone will have to go through the wizard again
    • Message bar now styles nicely

    Bug fixes

    • Doesn't show two now playing tracks in the scrobble list any more
    • Fixed quite a few bugs in the frist run wizard
    • Fixed some bugs with the account management settings
    ]]>
    Last.fm Scrobbler Version 2.0.12 Thr, 02 Feb 2012 18:20:00 +0000 Features
    • Now branded as a beta version!
    • Tag dialog now implemented to similar standard as the share dialog
    • Only fetches track metadata if the window is visable
    • Scrobble toggle now in preferences
    • 'Play' menu option is now not checkable, but has 'play', 'pause' and 'resume' states
    • Progress bar now reports the state correctly for podcasts and videos
    • Don't display metadata for Spotify ads

    Bug fixes

    • iPod scrobbling should now work (as well as it does on mac)! The installer was incorrect
    • iTunes plugin now handles device names with ' and \ in them correctly
    • Device names are now interpreted as utf-8 by the app
    ]]>
    Last.fm Scrobbler Version 2.0.11 Wed, 25 Jan 2012 20:10:00 +0000 Bug fixes 2.0.11
    • Incorrectly though some radio tracks were podcasts or videos

    Features - 2.0.10

    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes 2.0.10

    • Some user setting fixes, you will have to go through the wizard again
    • Fixes to iTunes plugin communication problems (named pipe type incompatibilities)
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.10 Wed, 25 Jan 2012 17:30:00 +0000 Features
    • Added option to turn off podcast scrobbling
    • Now doesn't scrobble videos from iTunes, that aren't music videos
    • Added option to disable desktop notifications
    • Added option to disable launching the app with media players

    Bug fixes

    • Some user setting fixes, you will have to go through the wizard again
    • Fixes to iTunes plugin communication problems (named pipe type incompatibilities)
    • If the app is started with --tray option (i.e. from a media player) it now doesn't show the main window
    ]]>
    Last.fm Scrobbler Version 2.0.9 Mon, 16 Jan 2012 11:20:00 +0000 Bug fixes
    • New iTunes plugin was not working due to a change in Qt
    • iPod scrobble device associations were getting corrupted
    • Cleaned up and fixed some bugs in the apps user settings
    ]]>
    Last.fm Scrobbler Version 2.0.8 Thu, 12 Jan 2012 11:45:00 +0000 Features/Improvements
    • Settings toolbar now looks better and has assets
    • Crash Reporting implemented in client, but disabled. (needs oops server set up by ops)

    Bug fixes

    • Spacebar now works again as a global shortcut
    ]]>
    Last.fm Scrobbler Version 2.0.7 Wed, 04 Jan 2012 12:45:00 +0000
  • Fixed a major bug with batch scrobbling
  • Fixed a crash for unkown length tracks (iTunes radio streams)
  • ]]>
    Last.fm Scrobbler Version 2.0.6 Fri, 23 Dec 2011 17:00:00 +0000 Last.fm Scrobbler Version 2.0.1 Wed, 02 Nov 2011 11:11:11 +0000
    ================================================ FILE: admin/dist/win/Last.fm.iss ================================================ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! [CustomMessages] Version=2.1.36 [Setup] OutputBaseFilename=Last.fm-2.1.36 VersionInfoVersion=2.1.36 VersionInfoTextVersion=2.1.36 AppName="Last.fm Scrobbler" AppVerName="Last.fm Scrobbler {cm:Version}" VersionInfoDescription=Last.fm Installer AppPublisher=Last.fm AppPublisherURL=http://www.last.fm AppSupportURL=http://www.last.fm AppUpdatesURL=http://www.last.fm AppCopyright=Copyright 2012 Last.fm Ltd. (C) DefaultDirName={pf}\Last.fm UsePreviousAppDir=yes DefaultGroupName=Last.fm OutputDir=. AllowNoIcons=yes Compression=lzma SolidCompression=yes DisableReadyPage=yes DirExistsWarning=no DisableFinishedPage=no ShowLanguageDialog=yes WizardImageFile=wizard.bmp WizardSmallImageFile=wizard_small.bmp SetupIconFile=installer.ico UninstallDisplayIcon="{app}\Last.fm Scrobbler.exe" AppModifyPath="{app}\UninsHs.exe" /m0=LastFM WizardImageBackColor=$ffffff WizardImageStretch=no AppMutex=Lastfm-F396D8C8-9595-4f48-A319-48DCB827AD8F, Audioscrobbler-7BC5FBA0-A70A-406e-A50B-235D5AFE67FB ; This should stay the same across versions for the installer to treat it as the same program. ; It will then work to install however many updates and then run the uninstall script for ; the first version. AppId=LastFM [Languages] ; The first string is an internal code that we can set to whatever we feel like Name: "en"; MessagesFile: "compiler:Default.isl" ;Name: "fr"; MessagesFile: "compiler:French.isl" ;Name: "it"; MessagesFile: "compiler:Italian.isl" ;Name: "de"; MessagesFile: "compiler:Deutsch.isl" ;Name: "es"; MessagesFile: "compiler:Spanish.isl" ;Name: "pt"; MessagesFile: "compiler:Portuguese - Brasil.isl" ;Name: "pl"; MessagesFile: "compiler:Polish.isl" ;Name: "ru"; MessagesFile: "compiler:Russian.isl" ;Name: "jp"; MessagesFile: "compiler:Japanese.isl" ;Name: "chs"; MessagesFile: "compiler:Simplified Chinese.isl" ;Name: "tr"; MessagesFile: "compiler:Turkish.isl" ;Name: "se"; MessagesFile: "compiler:Swedish.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" ; The OnlyBelowVersion flag disables this on Vista as an admin-run installer can't install a quick launch ; icon to the standard user's folder location. Sucks. Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0, 6; [Dirs] Name: "{localappdata}\Last.fm\Client" [Files] ; Main files Source: "..\..\..\_bin\Last.fm Scrobbler.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\iPodScrobbler.exe"; DestDir: "{app}"; Flags: ignoreversion ;libraries Source: "..\..\..\_bin\lastfm.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\lastfm_fingerprint.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\unicorn.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\listener.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\logger.dll"; DestDir: "{app}"; Flags: ignoreversion ;Visual Studio 2010 redistributable packages Source: "%VSDIR%\VC\redist\x86\Microsoft.VC100.CRT\*"; DestDir: "{app}"; Flags: ignoreversion Source: "%VSDIR%\VC\redist\x86\Microsoft.VC100.ATL\*"; DestDir: "{app}"; Flags: ignoreversion ;Qt binaries Source: "%QTDIR%\bin\QtCore4.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "%QTDIR%\bin\QtGui4.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "%QTDIR%\bin\QtNetwork4.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "%QTDIR%\bin\QtXml4.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "%QTDIR%\bin\QtSql4.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "%QTDIR%\bin\QtWebKit4.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\phonon.dll"; DestDir: "{app}"; Flags: ignoreversion ;image formats plugins Source: "%QTDIR%\plugins\imageformats\qjpeg4.dll"; DestDir: "{app}\plugins\imageformats"; Flags: ignoreversion Source: "%QTDIR%\plugins\imageformats\qgif4.dll"; DestDir: "{app}\plugins\imageformats"; Flags: ignoreversion Source: "%QTDIR%\plugins\imageformats\qmng4.dll"; DestDir: "{app}\plugins\imageformats"; Flags: ignoreversion ;phonon backend plugin Source: "..\..\..\_bin\plugins\phonon_backend\phonon_vlc.dll"; DestDir: "{app}\plugins\phonon_backend"; Flags: ignoreversion ;sqlite plugin Source: "%QTDIR%\plugins\sqldrivers\qsqlite4.dll"; DestDir: "{app}\plugins\sqldrivers"; Flags: ignoreversion ; vlc libraries Source: "..\..\..\_bin\libvlccore.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\libvlc.dll"; DestDir: "{app}"; Flags: ignoreversion ; vlc plugins Source: "..\..\..\_bin\plugins\access\libaccess_http_plugin.dll"; DestDir: "{app}\plugins\access"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\liba52tofloat32_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\liba52tospdif_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libaudio_format_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libconverter_fixed_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libdolby_surround_decoder_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libdtstofloat32_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libdtstospdif_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libmpgatofixed32_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libsamplerate_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libscaletempo_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libsimple_channel_mixer_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libspeex_resampler_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libtrivial_channel_mixer_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_filter\libugly_resampler_plugin.dll"; DestDir: "{app}\plugins\audio_filter"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_mixer\libfloat32_mixer_plugin.dll"; DestDir: "{app}\plugins\audio_mixer"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\audio_output\libaout_directx_plugin.dll"; DestDir: "{app}\plugins\audio_output"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\codec\libmpeg_audio_plugin.dll"; DestDir: "{app}\plugins\codec"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\demux\libes_plugin.dll"; DestDir: "{app}\plugins\demux"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\misc\liblogger_plugin.dll"; DestDir: "{app}\plugins\misc"; Flags: ignoreversion ;media player plugin installers Source: "..\..\..\_bin\plugins\FooPlugin0.9.4Setup_2.3.1.3.exe"; DestDir: "{app}\plugins"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\iTunesPluginWinSetup_6.0.5.4.exe"; DestDir: "{app}\plugins"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\WinampPluginSetup_2.1.0.11.exe"; DestDir: "{app}\plugins"; Flags: ignoreversion Source: "..\..\..\_bin\plugins\WmpPluginSetup_2.1.0.8.exe"; DestDir: "{app}\plugins"; Flags: ignoreversion ;3rd party Source: "..\..\..\_bin\libfftw3f-3.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\libsamplerate-0.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\avcodec-54.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\avformat-54.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\avutil-52.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\swresample-0.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\libeay32.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\..\..\_bin\ssleay32.dll"; DestDir: "{app}"; Flags: ignoreversion ;The stylesheets Source: "..\..\..\app\client\Last.fm Scrobbler.css"; DestDir: "{app}"; Flags: ignoreversion ;The translations Source: "..\..\..\i18n\*.qm"; DestDir: "{app}\i18n"; Flags: ignoreversion Source: "%QTDIR%\translations\qt_de.qm"; DestDir: "{app}\i18n"; Flags: ignoreversion Source: "%QTDIR%\translations\qt_es.qm"; DestDir: "{app}\i18n"; Flags: ignoreversion Source: "%QTDIR%\translations\qt_fr.qm"; DestDir: "{app}\i18n"; Flags: ignoreversion Source: "%QTDIR%\translations\qt_ja.qm"; DestDir: "{app}\i18n"; Flags: ignoreversion Source: "%QTDIR%\translations\qt_pl.qm"; DestDir: "{app}\i18n"; Flags: ignoreversion Source: "%QTDIR%\translations\qt_pt.qm"; DestDir: "{app}\i18n"; Flags: ignoreversion Source: "%QTDIR%\translations\qt_ru.qm"; DestDir: "{app}\i18n"; Flags: ignoreversion Source: "%QTDIR%\translations\qt_sv.qm"; DestDir: "{app}\i18n"; Flags: ignoreversion Source: "%QTDIR%\translations\qt_zh_CN.qm"; DestDir: "{app}\i18n"; Flags: ignoreversion ;The add/modify/remove file Source: "UninsHs.exe"; DestDir: "{app}"; Flags: onlyifdoesntexist ;Text files? [Registry] ; The Path is looked for in both places by plugins Root: HKCU; Subkey: "Software\Last.fm\Client"; ValueType: string; ValueName: "Version"; ValueData: "{cm:Version}"; Flags: uninsdeletekey Root: HKCU; Subkey: "Software\Last.fm\Client"; ValueType: string; ValueName: "Path"; ValueData: "{app}\Last.fm Scrobbler.exe"; Flags: uninsdeletekey Root: HKCU; Subkey: "Software\Last.fm\Last.fm"; ValueType: string; ValueName: "Version"; ValueData: "{cm:Version}"; Flags: uninsdeletekey Root: HKCU; Subkey: "Software\Last.fm\Last.fm"; ValueType: string; ValueName: "Path"; ValueData: "{app}\Last.fm Scrobbler.exe"; Flags: uninsdeletekey ; Register last.fm protocol only if it isn't already Root: HKCR; Subkey: "lastfm"; ValueType: string; ValueName: ""; ValueData: "URL:lastfm"; Flags: uninsdeletekey Root: HKCR; Subkey: "lastfm"; ValueType: string; ValueName: "URL Protocol"; ValueData: ""; Flags: uninsdeletekey Root: HKCR; Subkey: "lastfm\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\Last.fm Scrobbler.exe"" ""%1"""; Flags: uninsdeletekey ; qtsparkle - This stop it asking the user if they want to check for updates on first run. Root: HKCU; Subkey: "Software\Last.fm\Last.fm Scrobbler\QtSparkle"; ValueType: string; ValueName: "asked_permission"; ValueData: "true"; Flags: uninsdeletekey Root: HKCU; Subkey: "Software\Last.fm\Last.fm Scrobbler\QtSparkle"; ValueType: string; ValueName: "check_automatically"; ValueData: "true"; Flags: uninsdeletekey createvalueifdoesntexist Root: HKCU; Subkey: "Software\Last.fm\Last.fm Scrobbler\QtSparkle"; ValueType: string; ValueName: "first_boot"; ValueData: "false"; Flags: uninsdeletekey ; This is just for deleting keys at uninstall Root: HKCU; Subkey: "Software\Last.fm"; Flags: dontcreatekey uninsdeletekey Root: HKLM; Subkey: "Software\Last.fm"; Flags: dontcreatekey uninsdeletekey [Icons] Name: "{group}\Last.fm Scrobbler"; Filename: "{app}\Last.fm Scrobbler.exe" Name: "{commondesktop}\Last.fm Scrobbler"; Filename: "{app}\Last.fm Scrobbler.exe"; Tasks: desktopicon ;Uninstall Name: "{group}\Uninstall Last.fm Scrobbler"; Filename: "{app}\UninsHs.exe"; Parameters: "/u0=LastFM" ; The OnlyBelowVersion flag disables this on Vista as an admin-run installer can't install a quick launch ; icon to the standard user's folder location. Sucks. Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\Last.fm Scrobbler"; Filename: "{app}\Last.fm Scrobbler.exe"; OnlyBelowVersion: 0,6; Tasks: quicklaunchicon [Run] Filename: "{app}\Last.fm Scrobbler.exe"; Description: "Start Last.fm Scrobbler now?"; Flags: nowait postinstall Filename: "{app}\UninsHs.exe"; Parameters: "/r0=LastFM,{language},{srcexe},{app}\Installer.exe"; Flags: runminimized runhidden nowait [InstallDelete] ;All the files that are not in fixed components (so the Radio compontent is actually removed on modify) Type: Files; Name: "{app}\libFLAC_dynamic.dll" Type: Files; Name: "{app}\libogg.dll" Type: Files; Name: "{app}\libvorbis.dll" Type: Files; Name: "{app}\libvorbisfile.dll" Type: Files; Name: "{app}\WinSparkle.dll" Type: Files; Name: "{app}\avutil-51.dll"; Type: Files; Name: "{app}\Last.fm.exe" Type: Files; Name: "{app}\Last.fm Scrobbler.exe" Type: Files; Name: "{app}\phonon4.dll" Type: Files; Name: "{app}\phonon.dll" Type: Files; Name: "{app}\Last.fm.css" Type: filesandordirs; Name: "{app}\phonon_backend" Type: filesandordirs; Name: "{app}\sqldrivers" Type: dirifempty; Name: "{app}\imageformats" Type: dirifempty; Name: "{app}\phonon_backend" Type: dirifempty; Name: "{app}\sqldrivers" Type: filesandordirs; Name: "{app}\plugins" ;Visual Studio 2008 redistributable packages Type: Files; Name: "{app}\msvcp90.dll" Type: Files; Name: "{app}\msvcm90.dll" Type: Files; Name: "{app}\Microsoft.VC90.CRT.manifest" Type: Files; Name: "{app}\Microsoft.VC90.ATL.manifest" Type: Files; Name: "{app}\atl90.dll" Type: Files; Name: "{app}\Last.fm.css" Type: Files; Name: "{app}\Last.fm Scrobbler.css" Type: Files; Name: "{commondesktop}\Last.fm" Type: Files; Name: "{commondesktop}\Last.fm.lnk" Type: Files; Name: "{commondesktop}\Last.fm Scrobbler" Type: Files; Name: "{commondesktop}\Last.fm Scrobbler.lnk" Type: Files; Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\Last.fm" Type: Files; Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\Last.fm Scrobbler" Type: Files; Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\Last.fm.lnk" Type: Files; Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\Last.fm Scrobbler.lnk" Type: filesandordirs; Name: "{group}" ;Files from the olde app Type: Files; Name: "{app}\Cleaner.exe" Type: Files; Name: "{app}\CrashReporter.exe" Type: Files; Name: "{app}\killer.exe" Type: Files; Name: "{app}\LastFM.exe" Type: Files; Name: "{app}\LastFM.exe.config" Type: Files; Name: "{app}\Updater.exe" Type: Files; Name: "{app}\ChangeLog.txt" Type: Files; Name: "{app}\Moose1.dll" Type: Files; Name: "{app}\LastFmFingerprint1.dll" Type: Files; Name: "{app}\LastFmTools1.dll" Type: Files; Name: "{app}\breakpad.dll" Type: Files; Name: "{app}\srv_httpinput.dll" Type: Files; Name: "{app}\srv_madtranscode.dll" Type: Files; Name: "{app}\srv_rtaudioplayback.dll" Type: Files; Name: "{app}\unins000.dat" Type: Files; Name: "{app}\unins000.exe" Type: Files; Name: "{app}\zlibwapi.dll" Type: Files; Name: "{app}\VistaLib32.dll" Type: Files; Name: "{app}\VistaLib64.dll" Type: filesandordirs; Name: "{app}\data" Type: filesandordirs; Name: "{app}\imageformats" Type: filesandordirs; Name: "{app}\Microsoft.VC80.CRT" ; This is the LAST step of uninstallation [UninstallDelete] ;remove folders, if they are empty Type: files; Name: "{app}\Installer.exe" Type: dirifempty; Name: "{app}" Type: filesandordirs; Name: "{localappdata}\Last.fm" ; This should be possible to delete as we're waiting until all the plugin uninstallers have been run. Type: files; Name: "{commonappdata}\Last.fm\Client\uninst.bat" Type: files; Name: "{commonappdata}\Last.fm\Client\uninst2.bat" Type: filesandordirs; Name: "{commonappdata}\Last.fm\Client" Type: dirifempty; Name: "{commonappdata}\Last.fm" ; This is the FIRST step of uninstallation [UninstallRun] Filename: "{app}\Last.fm.exe"; Parameters: "--exit"; Flags: skipifdoesntexist Filename: "{app}\Last.fm Scrobbler.exe"; Parameters: "--exit"; Flags: skipifdoesntexist [Code] // Global variables var g_firstRun: Boolean; // This must be called before the install and its value stored function IsUpgrade(): Boolean; var sPrevPath: String; begin sPrevPath := ''; if not RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\LastFM_is1', 'UninstallString', sPrevpath) then RegQueryStringValue(HKCU, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\LastFM_is1', 'UninstallString', sPrevpath); Result := (sPrevPath <> ''); end; function ShouldSkipPage(PageID: Integer): Boolean; begin if PageID = wpFinished then begin // We skip the final screen if it's first run and go straight into config wizard if g_firstRun then Result := TRUE end; // This skip page code is for add/modify/remove if Pos('/SP-', UpperCase(GetCmdTail)) > 0 then case PageID of wpLicense, wpPassword, wpInfoBefore, wpUserInfo, wpSelectDir, wpSelectProgramGroup, wpInfoAfter: Result := True; end; end; procedure ExitApp(FileName: String); var processExitCode: Integer; begin if ExecAsOriginalUser( ExpandConstant(FileName), '--exit', '', SW_SHOW, ewWaitUntilTerminated, processExitCode) then begin // *high five* end else begin //MsgBox( 'Failed to stop ' + ExpandConstant(FileName), mbInformation, MB_OK ); end; end; procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssInstall then begin ExitApp( '{app}\Last.fm.exe' ); ExitApp( '{app}\Last.fm Scrobbler.exe' ); end; end; function InitializeSetup(): Boolean; begin // Need to evaluate and store this before any installation has been done g_firstRun := not IsUpgrade(); // Run setup Result := TRUE; end; ================================================ FILE: admin/dist/win/build-release-win.pl ================================================ #!/usr/bin/perl # # brief: This script builds a release build from scratch and packages it # into an installer. You need %QTDIR% and %VSDIR% set in your environment # # Must be run from trunk. # # pre-requisites: perl, svn, tar, bzip2, # Inno Setup 5 (install to default location or set %ISDIR%) # use File::Path; use File::Copy; use File::Find; ######################################################################## # copy the input file to a temp location and use that instead copy('Last.fm.iss', 'Last.fm.tmp.iss') or die; my $ISSFILE = 'Last.fm.tmp.iss'; my $QTDIR = quotemeta( $ENV{'QTDIR'} or die $! ); my $VSDIR = quotemeta( $ENV{'VSDIR'} or die $! ); #$QTDIR =~ s/\\/\\\\/g; #double escape \s for the shell :( #$VSDIR =~ s/\\/\\\\/g; ######################################################################## header( "Building release and installer for Last.fm-$VERSION" ); ######################################################################## header( "Substituting strings in various files" ); #sub findVersionFiles() #{ # if ($_ =~ /\.rc$/i || $_ =~ /\.manifest$/i || $_ =~ /\.iss$/i) # { # push( @versionFiles, $File::Find::name ); # } #} #find( \&findVersionFiles, "." ); push( @versionFiles, $ISSFILE ); system( 'perl -pi".bak" -e "s/\%QTDIR\%/' . $QTDIR . '/g" ' . $ISSFILE ); system( 'perl -pi".bak" -e "s/\%VSDIR\%/' . $VSDIR . '/g" ' . $ISSFILE ); header( "Translations" ); system( "lrelease -removeidentical ../../../Last.fm.pro" ); system( "lrelease -removeidentical ../../../i18n/lastfm_de.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_en.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_es.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_fr.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_it.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_ja.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_pl.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_pt.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_ru.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_sv.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_tr.ts" ); system( "lrelease -removeidentical ../../../i18n/lastfm_zh_CN.ts" ); header( "Installer" ); #my $ISDIR = $ENV{'ISDIR'} or "c:\\Program Files\\Inno Setup 5"; #$ISDIR =~ s/\\/\//g; #run( "$ISDIR\\iscc.exe", "$ISSFILE" ) or die $!; my $output = qx/"c:\/Program Files (x86)\/Inno Setup 5\/iscc.exe" $ISSFILE/ or die $!; print $output; my @lines = split(/\n/, $output); my $installer = @lines[-1..-1]; print $installer; #header( "Building symbolstore" ); # dumpSyms( "bin" ); # dumpSyms( "$ENV{QTDIR}/lib" ); # chdir( "build/syms" ); # run( "tar", "cjf", "../../dist/Last.fm-$VERSION-win.symbols.tar.bz2", "." ); header( "Signing" ); system( 'signtool sign /a /d "Last.fm Scrobbler" /du http://www.last.fm/download ' . $installer ); header( "done!" ); print "To upload the symbols, issue the following command:\n"; print " perl dist\\build-release-win.pl --upload"; ######################################################################## sub header { print "\n==> $_[0]\n"; } sub run { return system( @_ ) == 0; } ######################################################################## ================================================ FILE: admin/dist/win/isspp ================================================ use File::Copy; open FILE, '_build/build_parameters.pl.h' or die $!; while ($line = ) { $str .= $line; } eval $str; close FILE; $root_dir = `cygpath -m '$ROOT_DIR'`; $qt_dir = `cygpath -m '$QMAKE_LIBDIR_QT\\..'`; copy( $ARGV[0], $ARGV[1] ) or die $!; open FILE, $ARGV[1] or die $!; while (<>) { s/@VERSION@/$VERSION-$REVISION/g; s/@SHORT_VERSION@/$VERSION/g; s/@ROOT_DIR@/$ROOT_DIR/g; s/@QT_DIR@/$qt_dir/g; s/@BIN_DIR@/$root_dir\\_bin/g; } close FILE; ================================================ FILE: admin/dist/win/wix/boffin.wxs ================================================ ================================================ FILE: admin/dist/win/wix/client.wxs ================================================ NOT NEWERVERSIONDETECTED OR Installed ================================================ FILE: admin/dist/win/wix/qt.conf ================================================ ================================================ FILE: admin/dist/win/wix/wixboff.cmd ================================================ rem @echo off rem Requires Wix 3.0.x rem Run with current directory same as .cmd location rem set VCREDISTDIR=C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\BootStrapper\Packages\vcredist_x86\ set VCREDISTDIR=C:\Program Files\Common Files\Merge Modules\ echo WIX = %WIX% echo QTDIR = %QTDIR% echo VCREDISTDIR = %VCREDISTDIR% "%WIX%"\bin\candle boffin.wxs if ERRORLEVEL 1 goto ERROR "%WIX%"\bin\light -ext WixUIExtension boffin.wixobj -b ..\..\..\..\_bin\ -b %QTDIR%\bin -b %QTDIR%\plugins -b "%VCREDISTDIR%\" if ERRORLEVEL 1 goto ERROR goto END :ERROR echo "***Fail" :END ================================================ FILE: admin/dist/win/wix/wixclient.cmd ================================================ @echo off rem Requires Wix 3.0.x rem Run with current directory same as .cmd location rem set VCREDISTDIR=C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\BootStrapper\Packages\vcredist_x86\ set VCREDISTDIR=C:\Program Files\Common Files\Merge Modules\ echo WIX = %WIX% echo QTDIR = %QTDIR% echo VCREDISTDIR = %VCREDISTDIR% "%WIX%"\bin\candle client.wxs if ERRORLEVEL 1 goto ERROR "%WIX%"\bin\light -ext WixUIExtension client.wixobj -b ..\..\..\..\ -b ..\..\..\..\_bin\ -b %QTDIR%\bin -b %QTDIR%\plugins -b "%VCREDISTDIR%\" if ERRORLEVEL 1 goto ERROR goto END :ERROR echo "***Fail" :END ================================================ FILE: admin/include.qmake ================================================ ROOT_DIR = $$PWD/.. macx-xcode { BUILD_DIR = _build } else { # With Xcode we generate a bundle BUILD_DIR = _build DESTDIR = $$ROOT_DIR/_bin LIBS += -L$$DESTDIR } OBJECTS_DIR = $$BUILD_DIR MOC_DIR = $$BUILD_DIR UI_DIR = $$BUILD_DIR RCC_DIR = $$BUILD_DIR INCLUDEPATH = $$ROOT_DIR win32:DEFINES += _CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN unix:!mac:QMAKE_CXXFLAGS += -fno-operator-names macx* { QMAKE_PKGINFO_TYPEINFO = last QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.6 } include( qmake/debug.pro.inc ) include( qmake/QtOverride.pro.inc ) include( qmake/3rdparty.pro.inc ) include( qmake/1stparty.pro.inc ) unix:!mac { CONFIG += link_pkgconfig # use KDE phonon if installed in preference contains( QT, phonon ):LIBS += -L$$system( kde4-config --prefix 2> /dev/null )/lib isEmpty(PREFIX) { PREFIX = /usr/local } DEFINES += PREFIX=\\\"$$PREFIX\\\" BINDIR = $$PREFIX/bin DATADIR = $$PREFIX/share } #default install target target.path = $$INSTALL_DIR/lib CONFIG += silent macx:CONFIG( app_bundle ): contains( TEMPLATE, app ) { DISTDIR = $$ROOT_DIR/admin/dist/mac BUNDLE_DIR = $$DESTDIR/$$TARGET$$quote(.app) ## copy any css file if it exists CSS_PATH=$$_PRO_FILE_PWD_/$$TARGET$$quote(.css) exists( $$CSS_PATH ) { # if we are in debug mode just symlink it so that we can edit in place and refresh the stylesheet CONFIG( debug, debug|release ) { QMAKE_POST_LINK += echo Symlinking css file..; \ rm \"$$BUNDLE_DIR/Contents/Resources/$$TARGET$$quote(.css)\"; \ ln -s \"$$CSS_PATH\" \"$$BUNDLE_DIR/Contents/Resources\"; } else { QMAKE_POST_LINK += echo Copying css file..; \ rm \"$$BUNDLE_DIR/Contents/Resources/$$TARGET$$quote(.css)\"; \ cp -f \"$$CSS_PATH\" \"$$BUNDLE_DIR/Contents/Resources\"; } } QMAKE_POST_LINK += echo Symlink olde exe to new; \ rm \"$${DESTDIR}/$${TARGET}.app/Contents/MacOS/Last.fm\"; \ ln -s \"$${TARGET}\" \"$${DESTDIR}/$${TARGET}.app/Contents/MacOS/Last.fm\"; QMAKE_POST_LINK += echo Symlink olde .icns to new; rm \"$$BUNDLE_DIR/Contents/Resources/lastfm.icns\"; \ ln -s \"audioscrobbler.icns\" \"$$BUNDLE_DIR/Contents/Resources/lastfm.icns\"; QMAKE_POST_LINK += echo Breakpad dump sys \"$${DESTDIR}/$${TARGET}.app/Contents/MacOS/$${TARGET}\".; \ \"$$ROOT_DIR/admin/dist/mac/dump_syms\" -a x86_64 \"$${DESTDIR}/$${TARGET}.app/Contents/MacOS/$${TARGET}\" > \"$${TARGET} x86_64.breakpad\"; ## copy the public key for updates QMAKE_POST_LINK += echo Copying dsa_pub.pem file..; \ cp \"$$ROOT_DIR/admin/dist/mac/dsa_pub.pem\" \"$$BUNDLE_DIR/Contents/Resources\"; ## copy the growl registration file QMAKE_POST_LINK += echo Copying growl registration file..; \ cp \"$$ROOT_DIR/admin/dist/mac/Growl Registration Ticket.growlRegDict\" \"$$BUNDLE_DIR/Contents/Resources\"; ## copy the apple script suite definition QMAKE_POST_LINK += echo Copying apple script suite definition file..; \ cp \"$$ROOT_DIR/admin/dist/mac/AppleScriptSuite.sdef\" \"$$BUNDLE_DIR/Contents/Resources\"; QMAKE_POST_LINK += echo Copying airfoil remote file..; \ cp \"$$ROOT_DIR/admin/dist/mac/dacp.fm.last.Scrobbler.scpt\" \"$$BUNDLE_DIR/Contents/Resources\"; QMAKE_POST_LINK += echo Copying airfoil tracks file..; \ cp \"$$ROOT_DIR/admin/dist/mac/fm.last.Scrobbler.scpt\" \"$$BUNDLE_DIR/Contents/Resources\"; ## add the translations QMAKE_POST_LINK += mkdir -p \"$${BUNDLE_DIR}/Contents/Resources/qm/\"; \ mv -f \"$${ROOT_DIR}/i18n/\"*.qm \"$${BUNDLE_DIR}/Contents/Resources/qm/\"; ## install_name_tool / copy frameworks, dylibs etc QMAKE_POST_LINK += $$DISTDIR/bundleFrameworks.sh \'$$BUNDLE_DIR\'; QMAKE_POST_LINK += find \'$$BUNDLE_DIR\' -iname \\*Headers -print0| xargs -0 rm -rf; \ find \'$$BUNDLE_DIR\' -iname \\*_debug -print0|xargs -0 rm QMAKE_INFO_PLIST = $$DISTDIR/Standard.plist } defineTest( generateBuildParameters ) { system( echo \'$DESTDIR = \"$$DESTDIR\";\' > _build_parameters.pl.h ) system( echo \'$VERSION = \"$$VERSION\";\' >> _build_parameters.pl.h ) system( echo \'$QT_FRAMEWORKS_DIR = \"$$QMAKE_LIBDIR_QT\";\' >> _build_parameters.pl.h ) system( echo \'$QMAKE_LIBDIR_QT = \"$$QMAKE_LIBDIR_QT\";\' >> _build_parameters.pl.h ) system( echo \'$ROOT_DIR = \"$$ROOT_DIR\";\' >> _build_parameters.pl.h ) system( echo \'$QT = \"$$QT\";\' >> _build_parameters.pl.h ) system( echo \'$REVISION = \"\'`svn info | grep \"Last Changed Rev\" | cut -d\' \' -f4`\'\";\' >> _build_parameters.pl.h ) } ================================================ FILE: admin/qmake/1stparty.pro.inc ================================================ CONFIG( unicorn ) { LIBS += -lunicorn QT += gui CONFIG += lastfm } CONFIG( listener ) { LIBS += -llistener CONFIG += lastfm } CONFIG( fingerprint ) { LIBS += -llastfm_fingerprint CONFIG += lastfm } CONFIG( logger ) { LIBS += -llogger CONFIG += lastfm } CONFIG( lastfm ) { QT += core network xml LIBS += -llastfm win32 { CONFIG += link_pkgconfig CONFIG(debug, debug|release) { PKGCONFIG += lastfm-debug } else { PKGCONFIG += lastfm-release } } mac{ CONFIG( notifications ) { MAC_SDK = $$system(xcode-select --print-path)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk if( !exists( $$MAC_SDK) ) { message("You do not have the MacOSX10.8 SDK installed. User Notifications will not be built.") } else { macx:QMAKE_MAC_SDK = $$MAC_SDK DEFINES += LASTFM_USER_NOTIFICATIONS } } LIBS += -weak_framework Carbon -weak_framework CoreFoundation -weak_framework Foundation } } ================================================ FILE: admin/qmake/3rdparty.pro.inc ================================================ mac { LIBS += -L/usr/local/lib INCLUDEPATH += /usr/local/include } CONFIG( taglib ) { CONFIG += link_pkgconfig PKGCONFIG += taglib win32:debug:LIBS += tagd.lib # tagd.lib will precede PKGCONFIG added libs } CONFIG( ffmpeg ) { mac { LIBS += /usr/local/lib/libavcodec.dylib LIBS += /usr/local/lib/libavformat.dylib LIBS += /usr/local/lib/libavutil.dylib LIBS += /usr/local/lib/libswresample.dylib DEFINES += HAVE_SWRESAMPLE } else { CONFIG += link_pkgconfig PKGCONFIG += libavformat libavcodec libavutil packagesExist(libswresample) { PKGCONFIG += libswresample DEFINES += HAVE_SWRESAMPLE } packagesExist(libavresample) { PKGCONFIG += libavresample DEFINES += HAVE_AVRESAMPLE } } } CONFIG( analytics ) { QT += webkit DEFINES += LASTFM_ANALYTICS } CONFIG( boost ) { mac:CONFIG(app_bundle){ LIBS += /usr/local/lib/libboost_thread-mt.a } else:unix { LIBS += -lboost_thread-mt } } CONFIG( break ) { win32{ CONFIG += link_pkgconfig PKGCONFIG += breakpad LIBS += wininet.lib } else:mac{ LIBS += -framework Breakpad } } CONFIG( growl ) { mac { INCLUDEPATH += -F/Library/Frameworks LIBS += -F/Library/Frameworks -framework Growl } } CONFIG( sparkle ) { win32{ CONFIG += link_pkgconfig CONFIG(debug, debug|release) { PKGCONFIG += qtsparkle-debug } else { PKGCONFIG += qtsparkle-release } } else:mac{ INCLUDEPATH += -F/Library/Frameworks LIBS += -F/Library/Frameworks -framework Sparkle -Wl -rpath @loader_path/../Frameworks } } CONFIG( sqlite3 ) { win32 { CONFIG += link_pkgconfig PKGCONFIG += sqlite3 } else:LIBS += -lsqlite3 } CONFIG( yajl ) { win32 { CONFIG += link_pkgconfig debug:PKGCONFIG += yajl-debug release:PKGCONFIG += yajl-release } else:CONFIG(app_bundle) { LIBS += /usr/local/lib/libyajl_s.a } else:LIBS += -lyajl } ================================================ FILE: admin/qmake/QtOverride.pro.inc ================================================ !CONFIG( no_override ) { win32:QMAKE_INCDIR_QT = $$ROOT_DIR/common/qt/override $$QMAKE_INCDIR_QT else:!macx-xcode::QMAKE_CXX = $$QMAKE_CXX -I$$ROOT_DIR/common/qt/override } ================================================ FILE: admin/qmake/debug.pro.inc ================================================ CONFIG( debug, debug|release ) { mac* { #speeds up debug builds by only compiling x86 CONFIG -= ppc } unix:!mac { QMAKE_CXXFLAGS_DEBUG = -ggdb } VERSION_UPDATE_PATTERN = *.*.*.* } else { CONFIG += warn_off DEFINES += NDEBUG #macx*:CONFIG += ppc x86 } ================================================ FILE: admin/tests/QtTest_to_JUnit.xslt ================================================ Standard Unexpected Pass --> ================================================ FILE: admin/tests/log_test.sh ================================================ #!/bin/bash # author # meant to be run from the /tests dir RUNDIR=`pwd` mkdir $RUNDIR/output TOTERRORS=0 pushd ../_bin > /dev/null for i in $* do BINDIR=`dirname $i` LEN=${#BINDIR} let LEN=$LEN+1 OUTPUTDIR=${i:$LEN} OUTPUTFILE=$RUNDIR/output/$OUTPUTDIR.output $i -xml > "$OUTPUTFILE" # check for program errors RETURNCODE=$? if [[ RETURNCODE -gt 0 ]]; then echo "Bailing out - bad return code: $RETURNCODE" echo "$i" exit fi # count number of test failures NUMERRORS=`grep -c \"fail\" $OUTPUTFILE` XPASS=`grep -c \"xpass\" $OUTPUTFILE` let NUMERRORS=$NUMERRORS+$XPASS let TOTERRORS=TOTERRORS+NUMERRORS if [[ NUMERRORS -gt 0 ]]; then echo "$NUMERRORS errors where encountered in $i" fi # transform outputted xml into junit-xml xsltproc $RUNDIR/QtTest_to_JUnit.xslt $OUTPUTFILE > "$OUTPUTFILE.junit.xml" done popd > /dev/null if [[ TOTERRORS -gt 0 ]]; then echo "Total: $TOTERRORS errors" fi ================================================ FILE: admin/tests/run_tests.sh ================================================ #!/bin/bash # @author # expects to located in _bin, otherwise it doesn't work d=`dirname $0` source $d/../admin/utils.bash case `uname` in Darwin) export DYLD_LIBRARY_PATH=$d:DYLD_LIBRARY_PATH;; Linux) export LD_LIBRARY_PATH=$d:$LD_LIBRARY_PATH;; esac # reverse sorted because test_client takes 3 minutes and it is boring to wait for x in `find . -type f -perm +1 -name test_\* | sort -r` do header $x $x done ================================================ FILE: app/boffin/App.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/UnicornSettings.h" #include "App.h" #include "MainWindow.h" #include "MediaPipeline.h" #include "PickDirsDialog.h" #include "ScanProgressWidget.h" #include "ScrobSocket.h" #include "TrackSource.h" #include "Shuffler.h" #include "LocalCollectionScanner.h" #include "playdar/PlaydarConnection.h" #include "PlaydarTagCloudModel.h" #include "Playlist.h" #include "XspfDialog.h" #define OUTPUT_DEVICE_KEY "OutputDevice" #define PLAYDAR_AUTHTOKEN_KEY "PlaydarAuth" #define PLAYDAR_URLBASE_KEY "PlaydarUrlBase" App::App( int& argc, char** argv ) : unicorn::Application( argc, argv ) , m_mainwindow( 0 ) , m_tagcloud( 0 ) , m_scrobsocket( 0 ) , m_pipe( 0 ) , m_playlist( 0 ) , m_audioOutput( 0 ) , m_playing( false ) , m_req( 0 ) , m_api( unicorn::UserSettings().value(PLAYDAR_URLBASE_KEY, "http://localhost:8888").toString(), unicorn::UserSettings().value(PLAYDAR_AUTHTOKEN_KEY, "").toString() ) { m_wam = new lastfm::NetworkAccessManager( this ); m_playdar = new PlaydarConnection(m_wam, m_api); connect(m_playdar, SIGNAL(authed(QString)), SLOT(onPlaydarAuth(QString))); m_shuffler = new Shuffler(this); m_tracksource = new TrackSource(m_shuffler, this); } App::~App() { cleanup(); if (m_audioOutput) QSettings().setValue( OUTPUT_DEVICE_KEY, m_audioOutput->outputDevice().name() ); delete m_pipe; } void App::cleanup() { } void App::init( MainWindow* window ) throw( int /*exitcode*/ ) { m_mainwindow = window; window->ui.play->setEnabled( false ); window->ui.pause->setEnabled( false ); window->ui.skip->setEnabled( false ); window->ui.wordle->setEnabled( false ); ////// radio QString const name = QSettings().value( OUTPUT_DEVICE_KEY ).toString(); m_audioOutput = new Phonon::AudioOutput( Phonon::MusicCategory, this ); QActionGroup* actiongroup = new QActionGroup( window->ui.outputdevice ); foreach (Phonon::AudioOutputDevice d, Phonon::BackendCapabilities::availableAudioOutputDevices()) { QAction* a = window->ui.outputdevice->addAction( d.name() ); a->setCheckable( true ); if (name == d.name()) m_audioOutput->setOutputDevice( d ); if (m_audioOutput->outputDevice().name() == d.name()) a->setChecked( true ); actiongroup->addAction( a ); } m_audioOutput->setVolume( 1.0 /* Settings().volume() */ ); connect( actiongroup, SIGNAL(triggered( QAction* )), SLOT(onOutputDeviceActionTriggered( QAction* )) ); m_pipe = new MediaPipeline( m_audioOutput, this ); connect( m_pipe, SIGNAL(preparing()), SLOT(onPreparing()) ); connect( m_pipe, SIGNAL(started( Track )), SLOT(onStarted( Track )) ); connect( m_pipe, SIGNAL(paused()), SLOT(onPaused()) ); connect( m_pipe, SIGNAL(resumed()), SLOT(onResumed()) ); connect( m_pipe, SIGNAL(stopped()), SLOT(onStopped()) ); connect( m_pipe, SIGNAL(error( QString )), SLOT(onPlaybackError( QString )) ); m_scrobsocket = new ScrobSocket( this ); connect( m_pipe, SIGNAL(started( Track )), m_scrobsocket, SLOT(start( Track )) ); connect( m_pipe, SIGNAL(paused()), m_scrobsocket, SLOT(pause()) ); connect( m_pipe, SIGNAL(resumed()), m_scrobsocket, SLOT(resume()) ); connect( m_pipe, SIGNAL(stopped()), m_scrobsocket, SLOT(stop()) ); /// parts of the scanning stuff //m_trackTagUpdater = TrackTagUpdater::create( // "http://musiclookup.last.fm/trackresolve", // 100, // number of days track tags are good // 5); // 5 minute delay between web requests /// connect connect( window->ui.play, SIGNAL(triggered()), SLOT(play()) ); connect( window->ui.pause, SIGNAL(toggled( bool )), m_pipe, SLOT(setPaused( bool )) ); connect( window->ui.skip, SIGNAL(triggered()), m_pipe, SLOT(skip()) ); connect( window->ui.rescan, SIGNAL(triggered()), SLOT(onRescan()) ); connect( window->ui.xspf, SIGNAL(triggered()), SLOT(xspf()) ); connect( m_mainwindow->ui.wordle, SIGNAL( triggered()), SLOT( onWordle())); QShortcut* cut = new QShortcut( Qt::Key_Space, window ); connect( cut, SIGNAL(activated()), SLOT(playPause()) ); cut->setContext( Qt::ApplicationShortcut ); m_mainwindow->ui.play->setEnabled( true ); m_mainwindow->ui.pause->setEnabled( false ); m_mainwindow->ui.skip->setEnabled( false ); m_mainwindow->ui.rescan->setEnabled( true ); m_mainwindow->ui.wordle->setEnabled( true ); connect(m_playdar->hostsModel(), SIGNAL(modelReset()), m_mainwindow, SLOT(onSourcesReset())); connect(m_playdar, SIGNAL(changed(QString)), m_mainwindow->ui.playdarStatus, SLOT(setText(QString))); connect(m_playdar, SIGNAL(connected()), SLOT(newTagcloud())); m_playdar->start(); } void App::onOutputDeviceActionTriggered( QAction* a ) { //FIXME for some reason setOutputDevice just returns false! :( QString const name = a->text(); foreach (Phonon::AudioOutputDevice d, Phonon::BackendCapabilities::availableAudioOutputDevices()) if (d.name() == name) { qDebug() << m_audioOutput->setOutputDevice( d ); qDebug() << m_audioOutput->outputDevice().name(); return; } } #include "TagBrowserWidget.h" //#include "TagCloudView.h" //#include "TagDelegate.h" //#include "PlaydarTagCloudModel.h" //#include "PlaydarStatRequest.h" //#include "PlaydarConnection.h" void App::onScanningFinished() { if (sender()) disconnect( sender(), 0, this, 0 ); //only once pls } void App::newTagcloud() { delete m_tagcloud; m_tagcloud = new TagBrowserWidget( m_playdar ); connect( m_tagcloud, SIGNAL( selectionChanged()), SLOT( tagsChanged() )); m_mainwindow->setCentralWidget( m_tagcloud ); } void App::play() { delete m_req; if (m_tagcloud) { m_req = m_playdar->boffinRql(m_tagcloud->rql()); if (m_req) { m_shuffler->clear(); connect(m_req, SIGNAL(playableItem(BoffinPlayableItem)), m_shuffler, SLOT(receivePlayableItem(BoffinPlayableItem))); connect(m_req, SIGNAL(playableItem(BoffinPlayableItem)), SLOT(onPlaydarTracksReady(BoffinPlayableItem))); onPreparing(); } } } void App::tagsChanged() { } void App::onPlaydarTracksReady( BoffinPlayableItem ) { // just interested in the first one disconnect(m_req, SIGNAL(playableItem(BoffinPlayableItem)), this, SLOT(onPlaydarTracksReady(BoffinPlayableItem))); // then delay for a little bit (to let a few more tracks dribble in) before playing QTimer::singleShot(500, this, SLOT(onReadyToPlay())); } void App::onReadyToPlay() { m_pipe->play(m_tracksource); } void App::playPause() { if (m_playing) m_mainwindow->ui.pause->toggle(); else play(); } void App::xspf() { QString path = QFileDialog::getOpenFileName( m_mainwindow, "Open XSPF File", "*.xspf" ); if (path.size()) { XspfDialog *pDlg = new XspfDialog(path, m_playdar, m_mainwindow); pDlg->show(); // m_mainwindow->QMainWindow::setWindowTitle( "Resolving XSPF..." ); // m_pipe->playXspf( path ); } } void App::onPreparing() //MediaPipeline is preparing to play a new station { m_mainwindow->QMainWindow::setWindowTitle( "Boffing up..." ); QAction* a = m_mainwindow->ui.play; a->setIcon( QPixmap(":/stop.png") ); disconnect( a, SIGNAL(triggered()), this, 0 ); connect( a, SIGNAL(triggered()), m_pipe, SLOT(stop()) ); } void App::onStarted( const Track& t ) { m_playing = true; // because phonon is shit and we can't rely on its state m_mainwindow->setWindowTitle( t ); m_mainwindow->ui.play->blockSignals( true ); m_mainwindow->ui.play->setChecked( true ); m_mainwindow->ui.play->blockSignals( false ); m_mainwindow->ui.pause->blockSignals( true ); m_mainwindow->ui.pause->setChecked( false ); m_mainwindow->ui.pause->setEnabled( true ); m_mainwindow->ui.pause->blockSignals( false ); m_mainwindow->ui.skip->setEnabled( true ); } void App::onPaused() { m_mainwindow->ui.pause->blockSignals( true ); m_mainwindow->ui.pause->setChecked( true ); m_mainwindow->ui.pause->blockSignals( false ); } void App::onResumed() { m_mainwindow->ui.pause->blockSignals( true ); m_mainwindow->ui.pause->setChecked( false ); m_mainwindow->ui.pause->blockSignals( false ); } void App::onStopped() { m_mainwindow->setWindowTitle( Track() ); m_mainwindow->ui.play->blockSignals( true ); m_mainwindow->ui.play->setChecked( false ); m_mainwindow->ui.play->blockSignals( false ); m_mainwindow->ui.pause->blockSignals( true ); m_mainwindow->ui.pause->setChecked( false ); m_mainwindow->ui.pause->setEnabled( false ); m_mainwindow->ui.pause->blockSignals( false ); m_mainwindow->ui.skip->setEnabled( false ); QAction* a = m_mainwindow->ui.play; a->setIcon( QPixmap(":/play.png") ); disconnect( a, SIGNAL(triggered()), m_pipe, 0 ); connect( a, SIGNAL(triggered()), SLOT(play()) ); m_playing = false; } void App::onPlaybackError( const QString& msg ) { //TODO: need to make this more like client 2's subtle yellow box. QMessageBoxBuilder( m_mainwindow ) .setTitle( "Playback Error" ) .setText( msg ) .exec(); } #include "WordleDialog.h" void App::onWordle() { static OneDialogPointer d; if(!d) { d = new WordleDialog( m_mainwindow ); QString output; //TagCloudModel model( this, 0 ); //for(int i = 0; i < model.rowCount(); ++i) { // QModelIndex index = model.index( i, 0 ); // QString weight = index.data( TagCloudModel::WeightRole ).toString(); // QString tag = index.data().toString().trimmed().simplified().replace( ' ', '~' ); // output += tag + ':' + weight + '\n'; //} d->setText( output ); } d.show(); } void App::onPlaydarAuth(const QString& auth) { unicorn::UserSettings().setValue(PLAYDAR_AUTHTOKEN_KEY, auth); } void App::onRescan() { PickDirsDialog* dlg = new PickDirsDialog(m_mainwindow); if (QDialog::Accepted == dlg->exec()) { QStringList directories = dlg->dirs(); if (directories.size()) { LocalCollectionScanner *scanner = new LocalCollectionScanner(this); m_scanWidget = new ScanProgressWidget(); connect(scanner, SIGNAL(track(Track)), m_scanWidget, SLOT(onNewTrack(Track))); connect(scanner, SIGNAL(directory(QString)), m_scanWidget, SLOT(onNewDirectory(QString))); connect(scanner, SIGNAL(finished()), m_scanWidget, SLOT(onFinished())); connect(scanner, SIGNAL(finished()), SLOT(newTagcloud())); connect(m_scanWidget, SIGNAL(statusMessage(QString)), m_mainwindow->statusBar(), SLOT(showMessage(QString))); // TODO: fix hard coded paths here! scanner->run( QDir("c:\\cygwin\\home\\doug\\src\\playdar\\win32\\debug\\bin\\"), "c:\\cygwin\\home\\doug\\src\\playdar\\win32\\collection.db", directories); m_mainwindow->setCentralWidget(m_scanWidget); } } } ================================================ FILE: app/boffin/App.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef APP_H #define APP_H #include "playdar/PlaydarApi.h" #include "playdar/BoffinPlayableItem.h" #include "lib/unicorn/UnicornApplication.h" #include #include #include namespace Phonon { class AudioOutput; } class TagCloudView; class QItemSelection; class PlaydarTagCloudModel; class Shuffler; class TrackSource; class ScanProgressWidget; namespace lastfm{ class Track; } class App : public unicorn::Application { Q_OBJECT public: App( int& argc, char* argv[] ); ~App(); void init( class MainWindow* ) throw( int /*exitcode*/ ); void play( class TrackSource *); public slots: void play(); void xspf(); //prompts to choose a xspf to resolve void playPause(); private slots: void onOutputDeviceActionTriggered( class QAction* ); void tagsChanged(); void onReadyToPlay(); void onPlaydarTracksReady( BoffinPlayableItem ); void onPlaydarAuth(const QString&); void onPreparing(); void onStarted( const Track& ); void onResumed(); void onPaused(); void onStopped(); void onRescan(); void newTagcloud(); void onScanningFinished(); void onPlaybackError( const QString& ); void onWordle(); private: void cleanup(); class MainWindow* m_mainwindow; class TagBrowserWidget* m_tagcloud; class ScanProgressWidget* m_scanWidget; class ScrobSocket* m_scrobsocket; class MediaPipeline* m_pipe; // pipe pulls from tracksource class TrackSource* m_tracksource; // tracksource pulls from shuffler class Shuffler* m_shuffler; // shuffler is fed from class PlaydarConnection* m_playdar; class Playlist* m_playlist; class BoffinRqlRequest* m_req; // current boffin rql request Phonon::AudioOutput* m_audioOutput; bool m_playing; PlaydarApi m_api; lastfm::NetworkAccessManager* m_wam; }; #endif //APP_H ================================================ FILE: app/boffin/HistoryWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "HistoryWidget.h" HistoryWidget::HistoryWidget(QWidget* parent) : QWidget(parent) { m_layout = new QHBoxLayout(this); m_layout->setAlignment( Qt::AlignLeft ); } HistoryWidget::~HistoryWidget() {} bool HistoryWidget::pop() { if (m_labels.count()) { QPushButton* label = m_labels.pop(); m_layout->removeWidget(label); delete label; return true; } return false; } void HistoryWidget::newItem(const QString& text) { QPushButton* label = new QPushButton(text, this); m_layout->addWidget(label, 0, Qt::AlignLeft); m_labels.push(label); connect(label, SIGNAL(clicked()), SLOT(onItemClicked())); } void HistoryWidget::onItemClicked() { QPushButton* label = static_cast(sender()); for (int i = 0; i < m_labels.count(); i++) { if (m_labels[i] == label) { emit clicked(i, label->text()); return; } } } ================================================ FILE: app/boffin/HistoryWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef HISTORY_WIDGET_H #define HISTORY_WIDGET_H #include #include class QPushButton; class QHBoxLayout; class HistoryWidget : public QWidget { Q_OBJECT public: HistoryWidget(QWidget* parent = 0); ~HistoryWidget(); bool pop(); signals: void clicked(int position, const QString& text); public slots: void newItem(const QString& text); private slots: void onItemClicked(); private: QHBoxLayout* m_layout; QStack m_labels; }; #endif ================================================ FILE: app/boffin/Info.plist.in ================================================ CFBundleIconFile @ICON@ CFBundlePackageType APPL CFBundleSignature last CFBundleExecutable @EXECUTABLE@ CFBundleDevelopmentRegion English CFBundleIdentifier fm.last.Boffin CFBundleInfoDictionaryVersion 6.0 CFBundleVersion @VERSION@ CFBundleShortVersionString @SHORT_VERSION@ CFBundleName Last.fm Boffin LSMinimumSystemVersion 10.4.0 CFBundleDocumentTypes CFBundleTypeExtensions xspf CFBundleTypeIconFile client.icns CFBundleTypeMIMETypes application/xspf+xml CFBundleTypeName XSPF Playlist CFBundleTypeRole Viewer ================================================ FILE: app/boffin/LocalCollectionScanner.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "LocalCollectionScanner.h" #include using namespace std; LocalCollectionScanner::LocalCollectionScanner(QObject* parent) : QObject(parent) { } void LocalCollectionScanner::run(QDir playdarBinDir, QString collectionDbFilename, QStringList directories) { QStringList args; args << collectionDbFilename; args << directories; m_proc = new QProcess(this); connect(m_proc, SIGNAL(readyReadStandardOutput()), SLOT(onReadyReadStandardOutput())); connect(m_proc, SIGNAL(readyReadStandardError()), SLOT(onReadyReadStandardError())); connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(onFinished(int, QProcess::ExitStatus))); connect(m_proc, SIGNAL(error(QProcess::ProcessError)), SLOT(onError(QProcess::ProcessError))); m_proc->start(playdarBinDir.filePath("scanner.exe"), args); } void LocalCollectionScanner::onReadyReadStandardOutput() { QByteArray ba = m_proc->readAllStandardOutput(); m_buffer.write(ba.constData(), ba.size()); while (true) { string line; getline(m_buffer, line); if (m_buffer.fail()) { // the line is incomplete (for now) m_buffer.clear(); // clears the failbit return; } LocalCollectionScanner::line(line); } } void LocalCollectionScanner::onReadyReadStandardError() { } void LocalCollectionScanner::onFinished(int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/) { emit finished(); } void LocalCollectionScanner::onError(QProcess::ProcessError) { emit finished(); } void LocalCollectionScanner::line(const string& s) { QString line = QString::fromUtf8(s.data(), s.length()).trimmed(); QStringList fields = line.split(QChar('\t'), QString::KeepEmptyParts, Qt::CaseInsensitive); if (fields.size()) { if (fields[0] == "DIR:") { if (fields.size() > 1) { emit directory(fields[1]); } } else if (fields[0] == "TRACK:") { if (fields.size() > 4) { lastfm::Track t; lastfm::MutableTrack mt(t); mt.setArtist(fields[1]); mt.setAlbum(fields[2]); mt.setTitle(fields[3]); mt.setUrl(fields[4]); emit track(t); } } } } ================================================ FILE: app/boffin/LocalCollectionScanner.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef LOCAL_COLLECTION_SCANNER_H #define LOCAL_COLLECTION_SCANNER_H #include #include #include #include #include class LocalCollectionScanner : public QObject { Q_OBJECT; public: LocalCollectionScanner(QObject* parent); void run(QDir playdarBinDir, QString collectionDbFilename, QStringList directories); signals: void track(Track); void directory(QString); void finished(); private slots: void onReadyReadStandardOutput(); void onReadyReadStandardError(); void onFinished(int, QProcess::ExitStatus); void onError(QProcess::ProcessError); private: void line(const std::string& line); QProcess* m_proc; QString m_collectionDbFilename; QDir m_playdarBinDir; std::stringstream m_buffer; }; #endif ================================================ FILE: app/boffin/MainWindow.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "MainWindow.h" #include #include #include #include #include #include #include #include "PlaydarHostsModel.h" #include "playdar/PlaydarConnection.h" MainWindow::MainWindow() { ui.outputdevice = menuBar()->addMenu( tr("Output Device") ); QMenu* tools = menuBar()->addMenu( tr("Tools") ); ui.xspf = tools->addAction( "Resolve XSPF" ); ui.rescan = tools->addAction( tr("&Scan Music Again") ); tools->addAction( tr("Show &Log"), this, SLOT(openLog()) ); ui.wordle = tools->addAction( tr("Wordlize")); QToolBar* toolbar = new QToolBar; toolbar->setIconSize( QSize( 41, 41 ) ); ui.play = toolbar->addAction( tr("Play") ); ui.play->setIcon( QPixmap(":/play.png") ); ui.pause = toolbar->addAction( tr("Pause") ); ui.pause->setIcon( QPixmap(":/pause.png") ); ui.pause->setCheckable( true ); ui.skip = toolbar->addAction( tr("Skip") ); ui.skip->setIcon( QPixmap(":/skip.png") ); addToolBar( toolbar ); setWindowTitle( Track() ); setUnifiedTitleAndToolBarOnMac( true ); resize( 750, 550 ); QStatusBar* status = new QStatusBar(); ui.sourcesMenu = new QMenu(); ui.sourcesButton = new QPushButton("Sources"); ui.sourcesButton->setMenu(ui.sourcesMenu); ui.playdarStatus = new QLabel(); status->addPermanentWidget(ui.playdarStatus); status->addPermanentWidget(ui.sourcesButton); status->setStyleSheet( "QStatusBar::item{ border: none; }" ); setStatusBar(status); finishUi(); } void MainWindow::onSourcesReset() { QStringList sources = ((QStringListModel*)sender())->stringList(); sources.sort(); ui.sourcesMenu->clear(); foreach(const QString& s, sources) { QAction* action = ui.sourcesMenu->addAction(s); action->setCheckable(true); action->setChecked(true); } } void MainWindow::setWindowTitle( const Track& t ) { if (t.isNull()) QMainWindow::setWindowTitle( tr("Last.fm Boffin") ); else QMainWindow::setWindowTitle( t.toString() ); } ================================================ FILE: app/boffin/MainWindow.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "lib/unicorn/UnicornMainWindow.h" class MainWindow : public unicorn::MainWindow { Q_OBJECT friend class App; struct Ui { QMenu* outputdevice; QAction* play; QAction* pause; QAction* skip; QAction* xspf; QAction* rescan; QAction* wordle; class QPushButton* sourcesButton; class QMenu* sourcesMenu; class QLabel* playdarStatus; } ui; public: MainWindow(); void setWindowTitle( const Track& ); public slots: void onSourcesReset(); }; ================================================ FILE: app/boffin/MediaPipeline.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include "MediaPipeline.h" #include "TrackSource.h" MediaPipeline::MediaPipeline( Phonon::AudioOutput* ao, QObject* parent ) : QObject( parent ) , mo( 0 ) , ao( ao ) , m_source( 0 ) , m_errorRecover( false ) , m_phonon_sucks( false ) { mo = new Phonon::MediaObject; connect( mo, SIGNAL(stateChanged( Phonon::State, Phonon::State )), SLOT(onPhononStateChanged( Phonon::State, Phonon::State )) ); connect( mo, SIGNAL(aboutToFinish()), SLOT(enqueue()) ); // fires just before track finishes connect( mo, SIGNAL(currentSourceChanged( Phonon::MediaSource )), SLOT(onPhononSourceChanged( Phonon::MediaSource )) ); Phonon::createPath( mo, ao ); } MediaPipeline::~MediaPipeline() { // I'm not confident about the sleep code on Windows --mxcl #ifndef WIN32 if (mo->state() != Phonon::PlayingState) return; qreal starting_volume = ao->volume(); //sigmoid curve for (int x = 18; x >= -60; --x) { qreal y = x; y /= 10; y = qreal(1) / (qreal(1) + std::exp( -y )); y *= starting_volume; ao->setVolume( y ); struct Thread : QThread { using QThread::msleep; }; Thread::msleep( 7 ); } #endif //delete mo; //don't as crashes often } void MediaPipeline::play( TrackSource* trackSource ) { // delete m_source; m_source = trackSource; enqueue(); } void MediaPipeline::setPaused( bool b ) { if (b) mo->pause(); else if (mo->state() == Phonon::PausedState) mo->play(); } void MediaPipeline::skip() { using namespace Phonon; enqueue(); QList q = mo->queue(); if (q.isEmpty()) { stop(); return; } // oh well :( enqueue() fails m_phonon_sucks = true; // phonon is broken hack mo->setCurrentSource( q.takeFirst() ); Q_ASSERT( q.isEmpty() ); mo->setQueue( q ); //now empty mo->play(); } void MediaPipeline::stop() { using namespace Phonon; qDebug() << mo->state(); m_tracks.clear(); if (mo->state() != Phonon::StoppedState) { // lol @ Phonon's shit API. We're 99% sure we're using it right mo->stop(); mo->setCurrentSource( MediaSource() ); mo->setQueue( QList() ); if (mo->state() == Phonon::LoadingState) emit stopped(); //phonon is broken and shit } else if (m_source) { // otherwise we have a source and it is doing something, but it could be // slow. Slow enough that the user wants to push the stop() button. So // the user did push the stop button. So tell the GUI that we stopped. emit stopped(); m_source->clear(); } } void MediaPipeline::onPhononStateChanged( Phonon::State newstate, Phonon::State oldstate ) { using namespace Phonon; // this is a HACK because Phonon docs lie that after you setCurrentSource // and call play() it will go to PlayingState. Actually it ALWAYS goes to // StoppedState. Phonon is absolute shit. With a shit API. And a bunch of // shit like the inability to play all mp3s and support all sound cards and // not hang the soding GUI whenever we call a function on MediaObject. // We're fucking unimpressed. if (m_phonon_sucks) switch (newstate) { case PlayingState: case ErrorState: case PausedState: m_phonon_sucks = false; break; default: return; } qDebug() << newstate << "was" << oldstate; switch (newstate) { case StoppedState: if (m_errorRecover) { m_errorRecover = false; skip(); } else { m_tracks.clear(); emit stopped(); } break; case ErrorState: qWarning() << mo->errorString(); // need to request a stop to clear the error state before we trying to play the next track m_errorRecover = true; mo->stop(); emit error( "There was an error during playback." ); break; case PausedState: emit paused(); break; case PlayingState: if (oldstate == PausedState) emit resumed(); else enqueue(); break; default: break; } } void MediaPipeline::onPhononSourceChanged( const Phonon::MediaSource& source ) { emit started( m_tracks.value( source.url() ) ); } void MediaPipeline::enqueue() { if (mo->queue().size() || !m_source) // queue is already full (or no source) return; // Loop until we get a null url or a valid url. for (;;) { // consume next track from the track source. a null track // response means wait until the trackAvailable signal if( !m_source) { qDebug() << "source is null"; break; } Track t = m_source->takeNextTrack(); if (t.isNull()) { qDebug() << "tracksource empty"; break; } // Invalid urls won't trigger the correct phonon // state changes, so we must prefilter them. if (!t.url().isValid()) { qDebug() << "invalid url (" << t << ") skipped"; continue; } m_tracks[t.url()] = t; // if we are playing a track now, enqueue, otherwise start now! if (mo->currentSource().url().isValid()) { qDebug() << "enqueuing " << t; mo->enqueue( Phonon::MediaSource( t.url() ) ); } else { qDebug() << "starting " << t; m_phonon_sucks = true; //Phonon is shit and broken mo->setCurrentSource( Phonon::MediaSource( t.url() ) ); mo->play(); } break; } } void MediaPipeline::onSourceError( lastfm::ws::Error e ) { qCritical() << e; emit error( "There was an error generating the playlist." ); } Phonon::State MediaPipeline::state() const { return mo->state(); } ================================================ FILE: app/boffin/MediaPipeline.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include namespace Phonon { class MediaObject; class AudioOutput; class MediaSource; } class MediaPipeline : public QObject { Q_OBJECT public: MediaPipeline( Phonon::AudioOutput*, QObject* parent ); ~MediaPipeline(); Phonon::State state() const; void play( class TrackSource* ); public slots: void setPaused( bool ); void stop(); void skip(); signals: void started( const Track& ); void paused(); void resumed(); void stopped(); void error( const QString& ); private slots: void onPhononSourceChanged( const Phonon::MediaSource& ); void onPhononStateChanged( Phonon::State, Phonon::State ); void onSourceError( lastfm::ws::Error ); void enqueue(); private: Phonon::MediaObject* mo; Phonon::AudioOutput* ao; TrackSource* m_source; QMap m_tracks; bool m_errorRecover; bool m_phonon_sucks; }; ================================================ FILE: app/boffin/PickDirsDialog.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "PickDirsDialog.h" #include #define kBlurb "Boffin creates Last.fm radio from the music on your computer." PickDirsDialog::PickDirsDialog( QWidget* parent ) : QDialog( parent, Qt::Sheet ) { ui.group = new QGroupBox( tr("Where is your music?") ); ui.buttons = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel ); ui.add = new QPushButton( tr("Add Another Folder") ); ui.add->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed ); QLabel* blurb; QVBoxLayout* v = new QVBoxLayout( ui.group ); v->addStretch(); v->setMargin( 10 ); v = new QVBoxLayout( this ); v->addWidget( blurb = new QLabel( kBlurb ) ); v->addSpacing( 18 ); v->addWidget( ui.group ); v->addSpacing( 8 ); v->addWidget( ui.add ); v->setAlignment( ui.add, Qt::AlignLeft ); v->addSpacing( 25 ); v->addWidget( ui.buttons ); v->setSpacing( 0 ); connect( ui.buttons, SIGNAL(rejected()), SLOT(reject()) ); connect( ui.buttons, SIGNAL(accepted()), SLOT(accept()) ); connect( ui.add, SIGNAL(clicked()), SLOT(prompt()) ); ui.group->setMinimumHeight( 78 ); ////// setMinimumWidth( 400 ); #ifdef Q_OS_MAC ui.group->setTitle( "" ); QLabel* label; v->insertSpacing( 2, 6 ); v->insertWidget( 2, label = new QLabel( "Where is your music?") ); v->insertSpacing( 0, 12 ); label->setAttribute( Qt::WA_MacSmallSize ); #endif } void PickDirsDialog::add( const QString& path ) { if (path.isEmpty()) return; QCheckBox* check = new QCheckBox( QDir::toNativeSeparators( path ) ); check->setChecked( true ); connect( check, SIGNAL(toggled( bool )), SLOT(enableDisableOk()) ); QVBoxLayout* v = (QVBoxLayout*)ui.group->layout(); v->insertWidget( v->count()-1, check ); enableDisableOk(); } void PickDirsDialog::prompt() { add( QFileDialog::getExistingDirectory( this ) ); } QStringList PickDirsDialog::dirs() const { QStringList dirs; foreach (QCheckBox* check, ui.group->findChildren()) if (check->isChecked()) dirs += check->text(); dirs.removeAll( "" ); return dirs; } void PickDirsDialog::setDirs(QStringList dirs) { qDebug() << dirs; if (dirs.isEmpty()) #ifdef __APPLE__ dirs << QDir::home().filePath( "Music" ); #else dirs << QDir::homePath(); #endif foreach (QString dir, dirs) add( dir ); } void PickDirsDialog::enableDisableOk() { QPushButton* ok = ui.buttons->button( QDialogButtonBox::Ok ); ok->setEnabled( true ); foreach (QCheckBox* check, ui.group->findChildren()) if (check->isChecked()) return; ok->setEnabled( false ); } ================================================ FILE: app/boffin/PickDirsDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include class PickDirsDialog : public QDialog { Q_OBJECT struct Ui { class QDialogButtonBox* buttons; class QGroupBox* group; class QPushButton* add; } ui; void add( const QString& path ); private slots: void prompt(); void enableDisableOk(); public: PickDirsDialog( QWidget* parent ); QStringList dirs() const; void setDirs(QStringList dirs); }; ================================================ FILE: app/boffin/PlaydarHostsModel.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "PlaydarHostsModel.h" //virtual QVariant PlaydarHostsModel::data(const QModelIndex &index, int /*role = Qt::DisplayRole */) const { return (index.row() < m_hosts.size()) ? m_hosts[index.row()] : QVariant(); } //virtual int PlaydarHostsModel::rowCount(const QModelIndex& /*parent = QModelIndex() */) const { return m_hosts.size(); } void PlaydarHostsModel::onHosts(const QStringList& hosts) { m_hosts = hosts; reset(); } ================================================ FILE: app/boffin/PlaydarHostsModel.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYDAR_HOSTS_MODEL_H #define PLAYDAR_HOSTS_MODEL_H #include #include class PlaydarHostsModel : public QAbstractListModel { Q_OBJECT public: virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; public slots: void onHosts(const QStringList& hosts); private: QStringList m_hosts; }; #endif ================================================ FILE: app/boffin/PlaydarTagCloudModel.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "PlaydarTagCloudModel.h" #include "playdar/PlaydarConnection.h" #include PlaydarTagCloudModel::PlaydarTagCloudModel(PlaydarConnection* playdar) :m_playdar(playdar) ,m_loadingTimer( 0 ) { } PlaydarTagCloudModel::~PlaydarTagCloudModel() { } void PlaydarTagCloudModel::startGetTags(const QString& rql) { m_hosts.clear(); m_tagListBuffer.clear(); m_tagList.clear(); m_maxWeight = 0; m_maxLogCount = FLT_MIN; m_minLogCount = FLT_MAX; m_maxTrackCount = 0; m_totalTracks = 0; m_totalDuration = 0; BoffinTagRequest* req = m_playdar->boffinTagcloud(rql); connect(req, SIGNAL(tagItem(BoffinTagItem)), SLOT(onTag(BoffinTagItem))); connect(req, SIGNAL(tagItem(BoffinTagItem)), SIGNAL(tagItem(BoffinTagItem))); connect(req, SIGNAL(error()), this, SLOT(onTagError())); if( m_loadingTimer ) { delete( m_loadingTimer ); m_loadingTimer = 0; } qDebug() << "Fetching rql: " << rql; } void PlaydarTagCloudModel::onTag(BoffinTagItem tag) { if( m_loadingTimer ) m_loadingTimer->stop(); // check if the host is being filtered. if (!m_hostFilter.contains(tag.m_host)) { m_hosts.insert(tag.m_host); if( int i = m_tagListBuffer.indexOf( tag ) >= 0 ) { // merge into existing tag m_tagListBuffer[ i ].m_weight += tag.m_weight; m_tagListBuffer[ i ].m_count += tag.m_count; m_tagListBuffer[ i ].m_logCount = log( (float) m_tagListBuffer[i].m_count ); m_maxWeight = qMax( m_tagListBuffer[i].m_weight, m_maxWeight); m_maxLogCount = qMax( m_tagListBuffer[i].m_logCount, m_maxLogCount); } else { // new tag tag.m_logCount = log( (float) tag.m_count ); m_tagListBuffer << tag; m_maxTrackCount = qMax( tag.m_count, m_maxTrackCount ); m_maxWeight = qMax( tag.m_weight, m_maxWeight ); m_maxLogCount = qMax( m_maxLogCount, tag.m_logCount ); m_minLogCount = qMin( m_minLogCount, tag.m_logCount ); } } // fire onFetchedTags after 1 second of idle-ness if( !m_loadingTimer ) { m_loadingTimer = new QTimer(); m_loadingTimer->setSingleShot( true ); connect( m_loadingTimer, SIGNAL( timeout()), SLOT(onFetchedTags())); } m_loadingTimer->start( 1000 ); } void PlaydarTagCloudModel::onFetchedTags() { m_loadingTimer->deleteLater(); m_loadingTimer = 0; // beginInsertRows( QModelIndex(), m_tagList.size(), m_tagListBuffer.size() ); m_tagList = m_tagListBuffer; qSort( m_tagList.begin(), m_tagList.end(), qGreater() ); // endInsertRows(); reset(); emit fetchedTags(); } void PlaydarTagCloudModel::onTagError() { } // virtual QVariant PlaydarTagCloudModel::data( const QModelIndex& index, int role ) const { if( index.row() >= m_tagList.count() ) return QVariant(); QList< BoffinTagItem >::const_iterator i = m_tagList.constBegin(); i += index.row(); switch( role ) { case Qt::DisplayRole: return i->m_name; case PlaydarTagCloudModel::WeightRole: return QVariant::fromValue((i->m_weight / m_maxWeight)); case PlaydarTagCloudModel::LinearWeightRole: if (m_maxLogCount == m_minLogCount) { return 1; } return QVariant::fromValue( ( i->m_logCount - m_minLogCount ) / (m_maxLogCount - m_minLogCount)); case PlaydarTagCloudModel::CountRole: return QVariant::fromValue(i->m_count); case PlaydarTagCloudModel::SecondsRole: return QVariant::fromValue(i->m_seconds); default: return QVariant(); } } // virtual QModelIndex PlaydarTagCloudModel::index( int row, int column, const QModelIndex& parent /*= QModelIndex()*/) const { return parent.isValid() || row > m_tagList.count() ? QModelIndex() : createIndex( row, column ); } QModelIndex PlaydarTagCloudModel::indexOf( const BoffinTagItem& tag ) { return createIndex( m_tagList.indexOf( tag ), 0 ); } //virtual QModelIndex PlaydarTagCloudModel::parent( const QModelIndex& ) const { return QModelIndex(); } //virtual int PlaydarTagCloudModel::rowCount( const QModelIndex& p ) const { if( p.isValid()) return 0; return m_tagList.count(); } //virtual int PlaydarTagCloudModel::columnCount( const QModelIndex& p ) const { if( p.isValid() ) return 0; return 1; } void PlaydarTagCloudModel::setHostFilter(QSet hosts) { m_hostFilter = hosts; // onTags(m_tags); // recalc things... } void PlaydarTagCloudModel::addToHostFilter(const QString& hostname) { m_hostFilter.insert(hostname); // onTags(m_tags); } void PlaydarTagCloudModel::setTagMapping(QMap tagMap) { // m_tagMap = tagMap; // onTags(m_tags); } int PlaydarTagCloudModel::maxTrackCount() const { return m_maxTrackCount; } ================================================ FILE: app/boffin/PlaydarTagCloudModel.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYDAR_TAG_CLOUD_MODEL_H #define PLAYDAR_TAG_CLOUD_MODEL_H #include "playdar/BoffinTagRequest.h" #include "playdar/PlaydarApi.h" #include #include #include #include #include #include #include class PlaydarConnection; class PlaydarTagCloudModel : public QAbstractTableModel { Q_OBJECT public: enum CustomRoles { WeightRole = Qt::UserRole, LinearWeightRole, CountRole, SecondsRole, RelevanceRole }; PlaydarTagCloudModel(PlaydarConnection *playdar); ~PlaydarTagCloudModel(void); void startGetTags(const QString& rql = QString()); void setHostFilter(QSet hosts); // exclude hosts from tagcloud void addToHostFilter(const QString& hostname); void setTagMapping(QMap tagMap); // map tagname -> preferred tagname int maxTrackCount() const; virtual QModelIndex index( int row, int column, const QModelIndex& parent = QModelIndex()) const; QModelIndex indexOf( const BoffinTagItem& t ); virtual QModelIndex parent( const QModelIndex& ) const; virtual int rowCount( const QModelIndex& = QModelIndex() ) const; virtual int columnCount( const QModelIndex& = QModelIndex() ) const; virtual QVariant data( const QModelIndex&, int role = Qt::DisplayRole ) const; virtual bool hasChildren( const QModelIndex& = QModelIndex()) const{ return false; } signals: void fetchedTags(); void tagItem( const BoffinTagItem& ); private slots: void onTag(BoffinTagItem tag); void onTagError(); void onFetchedTags(); private: PlaydarConnection* m_playdar; QSet m_hostFilter; // hosts to filter from this tag cloud QSet m_hosts; // hosts contributing to this tag cloud QList< BoffinTagItem > m_tagListBuffer; QList< BoffinTagItem > m_tagList; BoffinTagItem m_tag; // the last tag provided via onTags int m_maxTrackCount; int m_totalTracks; int m_totalDuration; float m_maxWeight; float m_maxLogCount; float m_minLogCount; // float m_minLogWeight, m_maxLogWeight; class QTimer* m_loadingTimer; }; #endif ================================================ FILE: app/boffin/Playlist.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include "Playlist.h" #include "playdar/PlaydarConnection.h" #include "sample/SampleFromDistribution.h" Playlist::Playlist(QObject *parent /* = 0 */) : QObject(parent) { } void Playlist::startRequest(const QString& rql, PlaydarConnection* pc) { BoffinRqlRequest* req = pc->boffinRql(rql); if (req) { connect(req, SIGNAL(playableItem(BoffinPlayableItem)), SLOT(onItem(BoffinPlayableItem))); } } void Playlist::onItem(const BoffinPlayableItem& /*item*/) { } void Playlist::onAfterDelay() { } ================================================ FILE: app/boffin/Playlist.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAY_LIST_H #define PLAY_LIST_H #include #include #include #include "playdar/BoffinRqlRequest.h" class PlaydarConnection; typedef QList ItemList; class Playlist : public QObject { Q_OBJECT public: Playlist(QObject *parent = 0); void startRequest(const QString& rql, PlaydarConnection* pc); private slots: void onItem(const BoffinPlayableItem& item); void onAfterDelay(); private: int m_lastSize; ItemList m_playlist; // shuffled into here }; #endif ================================================ FILE: app/boffin/PlaylistModel.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "PlaylistModel.h" PlaylistModel::PlaylistModel( QObject* p ) :QAbstractItemModel( p ), m_tracks() {} int PlaylistModel::columnCount( const QModelIndex& parent ) const { if( parent.isValid()) return 0; else return 4; } int PlaylistModel::rowCount( const QModelIndex& parent ) const { if( parent.isValid() ) return 0; else return m_tracks.size(); } QVariant PlaylistModel::headerData( int section, Qt::Orientation, int role ) const { if( role != Qt::DisplayRole ) return QVariant(); switch( section ) { case 1: return tr( "Artist" ); case 2: return tr( "Title" ); case 3: return tr( "Url" ); } return QVariant(); } QVariant PlaylistModel::data( const QModelIndex& index, int role ) const { if( index.row() >= m_tracks.size() || index.column() >= columnCount( index.parent())) return QVariant(); Track t = m_tracks[ index.row() ]; if( role == UrlRole ) return t.url(); if( role == Qt::DisplayRole ) { switch( index.column() ) { case 1: return QString(t.artist()); case 2: return t.title(); case 3: return t.url().toString(); } } return QVariant(); } QModelIndex PlaylistModel::index( int row, int column, const QModelIndex& p) const { if ( row < m_tracks.size() && column < columnCount(p) && !p.isValid() ) { return createIndex( row, column ); } else { return QModelIndex(); } } void PlaylistModel::addTracks( QList< Track > tracks ) { if( !tracks.isEmpty() ) { beginInsertRows( QModelIndex(), m_tracks.size(), m_tracks.size() + tracks.size() -1 ); m_tracks << tracks; endInsertRows(); } } void PlaylistModel::addTrack( Track t ) { QList< Track > tl; tl << t; addTracks( tl ); } void PlaylistModel::clear() { if (m_tracks.size()) { beginRemoveRows( QModelIndex(), 0, m_tracks.size() -1 ); m_tracks.clear(); endRemoveRows(); } } ================================================ FILE: app/boffin/PlaylistModel.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYLIST_MODEL_H_ #define PLAYLIST_MODEL_H_ #include #include class PlaylistModel : public QAbstractItemModel { Q_OBJECT public: enum ItemDataRole{ UrlRole = Qt::UserRole }; PlaylistModel( QObject* p = 0 ); virtual int columnCount( const QModelIndex& parent = QModelIndex()) const; virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const; virtual QVariant headerData( int section, Qt::Orientation, int role = Qt::DisplayRole ) const; virtual QVariant data( const QModelIndex& index, int role ) const; virtual QModelIndex index( int row, int column, const QModelIndex& p = QModelIndex()) const; virtual QModelIndex parent( const QModelIndex& ) const { return QModelIndex(); } public slots: void addTracks( QList< Track > tracks ); void addTrack( Track t ); void clear(); private: QList< Track > m_tracks; }; #endif //PLAYLIST_MODEL_H_ ================================================ FILE: app/boffin/PlaylistWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYLIST_WIDGET_H_ #define PLAYLIST_WIDGET_H_ #include #include "PlaylistModel.h" #include "playdar/PlaydarConnection.h" #include "playdar/BoffinTagRequest.h" #include "TrackSource.h" class PlaylistWidget: public QTreeView { Q_OBJECT public: PlaylistWidget(PlaydarConnection* playdar, QWidget* p = 0) : QTreeView(p), m_playdar(playdar) { setModel(&m_model); setAlternatingRowColors(true); connect(this, SIGNAL( doubleClicked(QModelIndex)), SLOT( onDoubleClicked(QModelIndex))); } public slots: void loadFromRql(QString rql) { } signals: void play(const QUrl&); private slots: void onDoubleClicked(const QModelIndex& index) { qDebug() << "Play: " << index.data(PlaylistModel::UrlRole).toUrl().toString(); emit play(index.data(PlaylistModel::UrlRole).toUrl()); } private: PlaydarConnection* m_playdar; PlaylistModel m_model; }; #endif //PLAYLIST_WIDGET_H_ ================================================ FILE: app/boffin/ScanProgressWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "ScanProgressWidget.h" #include #include #include #include /** all quite messy, apologies --mxcl */ #define MAX_IMAGE_COUNT 15 ScanProgressWidget::ScanProgressWidget() { m_done = false; m_artist_count = 0; m_track_count = 0; (new QBasicTimer)->start( 20, this ); setBackgroundRole( QPalette::Base ); setAutoFillBackground( true ); setAttribute( Qt::WA_MacShowFocusRect, false ); setAttribute( Qt::WA_MacSmallSize ); } void ScanProgressWidget::onNewDirectory( const QString& directory ) { paths += directory; if (paths.size() > 60) paths.pop_front(); update(); } void ScanProgressWidget::onNewTrack( const Track& t ) { int& i = count( t.artist() ); i++; m_artist_count = track_counts.size(); m_track_count++; updateStatusMessage(); if (t.url().isValid()) { paths += t.url().path(); // so this is a time saving way to keep the list the size of the screen // it goes over a bit almost certainly, but it's ok if (paths.size() > 60) paths.pop_front(); } if (i == 1 && images.size() < MAX_IMAGE_COUNT) { QObject* o = new ImageFucker( t.artist() ); connect( o, SIGNAL(fucked()), SLOT(onImageFucked()) ); o->setParent( this ); } } void ScanProgressWidget::onFinished() { m_done = true; updateStatusMessage(); repaint(); } void ScanProgressWidget::paintEvent( QPaintEvent* ) { QPainter p( this ); int y = height() - 6; p.setPen( Qt::lightGray ); foreach (QString const path, paths) { y -= 18; p.drawText( 6, y, path ); } for (int i = 0; i < images.count(); ++i) { p.setRenderHint( QPainter::Antialiasing ); p.setRenderHint( QPainter::SmoothPixmapTransform ); int y = (images[i]->y * (height() - images[i]->pixmap.height())) / 1000; QPointF pt( width() - images[i]->x, y ); p.setOpacity( images[i]->opacity ); p.drawImage( pt, images[i]->pixmap ); QRectF rect( pt.x(), y + (images[i]->pixmap.height() * 0.75) + 4, images[i]->pixmap.width(), height() ); QString text = images[i]->artist; text = p.fontMetrics().elidedText( text, Qt::ElideRight, rect.width() ); int const n = count( images[i]->artist ); text += "\n" + (n == 1 ? tr("1 track") : tr( "%L1 tracks" ).arg( n )); p.setPen( Qt::black ); p.drawText( rect, Qt::AlignTop | Qt::AlignHCenter, text ); } } void ScanProgressWidget::timerEvent( QTimerEvent* ) { QList remove; for (int i = 0; i < images.count(); ++i) { int steps = images[i]->steps; images[i]->opacity = float(steps > 100 ? 200 - steps : steps) / 100.0f; #ifdef Q_WS_WIN // 0.5 pixel movements chug on windows images[i]->x += 1; #else // 0.5 pixel movements look great on mac images[i]->x += 0.5; #endif images[i]->steps++; if (steps == 200) remove.prepend( i ); } bool needUpdate = images.count(); foreach (int i, remove) images.takeAt( i )->deleteLater(); if (needUpdate) update(); } void ScanProgressWidget::onImageFucked() { ImageFucker* fucker = (ImageFucker*)sender(); fucker->x = rand() % width(); images += fucker; } void ScanProgressWidget::updateStatusMessage() { QString text; if (m_artist_count != 0 && m_track_count != 0) { text = tr("Found %L1 artists and %L2 tracks").arg( m_artist_count ).arg( m_track_count ); if (m_done) text.prepend( tr("Scanning complete. ") ); } else text = tr("Scanning..."); emit statusMessage(text); //p.drawText( 6, height() - 6, text ); } void ImageFucker::onArtistGotInfo() { QUrl url = Artist::getInfo( (QNetworkReply*)sender() ).imageUrl(); QNetworkReply* reply = lastfm::nam()->get( QNetworkRequest(url) ); connect( reply, SIGNAL(finished()), SLOT(onImageDownloaded()) ); } static inline QImage reflect0rize( const QImage& in ) { const int H = in.height() / 3; QImage r = QImage( in.width(), in.height() + H, QImage::Format_ARGB32_Premultiplied ); QImage in2 = in; in2.convertToFormat( QImage::Format_ARGB32_Premultiplied ); QImage reflection = in.copy( 0, in.height() - H, in.width(), H ); reflection = reflection.mirrored( false, true /*vertical only*/ ); QPainter p( &r ); p.drawImage( 0, 0, in2 ); p.drawImage( 0, in.height(), reflection ); QLinearGradient g( QPointF( 0, 0 ), QPointF( 0, 1 ) ); g.setCoordinateMode( QGradient::ObjectBoundingMode ); g.setColorAt( 0, QColor(0, 0, 0, 100) ); g.setColorAt( 1, Qt::transparent ); p.setRenderHint( QPainter::Antialiasing ); p.setRenderHint( QPainter::SmoothPixmapTransform ); p.setCompositionMode( QPainter::CompositionMode_DestinationIn ); p.fillRect( QRectF( QPointF( 0, in.height() ), QSizeF( r.width(), H ) ), g ); return r; } void ImageFucker::onImageDownloaded() { QNetworkReply* reply = static_cast(sender()); QByteArray const data = reply->readAll(); QImage in; in.loadFromData( data ); height = in.height(); pixmap = reflect0rize( in ); y = rand() % 1000; emit fucked(); } ================================================ FILE: app/boffin/ScanProgressWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include /** @author Max Howell */ class ImageFucker : public QObject { Q_OBJECT public: ImageFucker( const Artist& artist ) { this->steps = 0; this->height = 0; this->x = this->opacity = 0; this->artist = artist; connect( (QObject*)artist.getInfo(), SIGNAL(finished()), SLOT(onArtistGotInfo()) ); } QImage pixmap; QString artist; qreal x; qreal opacity; uint height; int y; int steps; signals: void fucked(); private slots: void onArtistGotInfo(); void onImageDownloaded(); }; class ScanProgressWidget : #ifdef Q_WS_WIN public QWidget #else public QGLWidget #endif { Q_OBJECT bool m_done; uint m_artist_count; uint m_track_count; QList images; QStringList paths; QHash track_counts; int& count( const Artist& artist ) { return track_counts[ artist.name().toLower() ]; } public: ScanProgressWidget(); virtual void paintEvent( QPaintEvent* ); virtual void timerEvent( QTimerEvent* ); signals: void statusMessage( QString ); public slots: void onNewDirectory( const QString& ); void onNewTrack( const Track& ); void onFinished(); private slots: void onImageFucked(); private: void updateStatusMessage(); }; ================================================ FILE: app/boffin/ScrobSocket.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "ScrobSocket.h" #include #include #include #include static int const kDefaultPort = 33367; ScrobSocket::ScrobSocket( QObject* parent ) : QTcpSocket( parent ) { connect( this, SIGNAL(readyRead()), SLOT(onReadyRead()) ); connect( this, SIGNAL(error( QAbstractSocket::SocketError )), SLOT(onError( QAbstractSocket::SocketError )) ); connect( this, SIGNAL(connected()), SLOT(onConnected()) ); connect( this, SIGNAL(disconnected()), SLOT(onDisconnected()) ); transmit( "INIT c=bof\n" ); } ScrobSocket::~ScrobSocket() { if (!m_track.isNull()) stop(); } void ScrobSocket::transmit( const QString& data ) { m_msgQueue.enqueue( data ); qDebug() << "Connection state == " << state(); if( state() == QAbstractSocket::UnconnectedState ) connectToHost( QHostAddress::LocalHost, kDefaultPort ); } void ScrobSocket::onConnected() { if( !m_msgQueue.empty() ) { qDebug() << m_msgQueue.head().trimmed(); write( m_msgQueue.takeFirst().toUtf8()); } } void ScrobSocket::onDisconnected() { if( !m_msgQueue.empty()) connectToHost( QHostAddress::LocalHost, kDefaultPort ); } void ScrobSocket::onError( SocketError error ) { switch (error) { case SocketTimeoutError: // TODO look, really we should store at least one start message forever // then if last time we didn't connect and this time it's a pause we // send the start first m_msgQueue.clear(); break; case RemoteHostClosedError: // expected break; default: // may as well qDebug() << lastfm::qMetaEnumString( error, "SocketError" ); case ConnectionRefusedError: // happens if client isn't running break; } } static inline QString encodeAmp( QString data ) { return data.replace( '&', "&&" ); } void ScrobSocket::start( const Track& t ) { m_track = t; transmit( "START c=bof" "&" "a=" + encodeAmp( t.artist() ) + "&" "t=" + encodeAmp( t.title() ) + "&" "b=" + encodeAmp( t.album() ) + "&" "l=" + QString::number( t.duration() ) + "&" "p=" + encodeAmp( t.url().path() ) + '\n' ); } void ScrobSocket::pause() { transmit( "PAUSE c=bof\n" ); } void ScrobSocket::resume() { transmit( "RESUME c=bof\n" ); } void ScrobSocket::stop() { transmit( "STOP c=bof\n" ); } void ScrobSocket::onReadyRead() { QByteArray bytes = readAll(); if (bytes != "OK\n") qWarning() << bytes.trimmed(); disconnectFromHost(); } ================================================ FILE: app/boffin/ScrobSocket.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SCROB_SOCKET_H #define SCROB_SOCKET_H #include #include #include #include /** @author Christian Muehlhaeuser * @contributor Erik Jaelevik * @rewrite Max Howell */ class ScrobSocket : public QTcpSocket { Q_OBJECT public: ScrobSocket( QObject* parent ); ~ScrobSocket(); public slots: void start( const Track& ); void pause(); void resume(); void stop(); private slots: void transmit( const QString& data ); void onError( QAbstractSocket::SocketError ); void onReadyRead(); void onConnected(); void onDisconnected(); private: Track m_track; QQueue m_msgQueue; }; #endif ================================================ FILE: app/boffin/Shuffler.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "Shuffler.h" //////////////////////////////////////////////////////////////////////// // lifted from playdar int levenshtein(const QString& source, const QString& target) { // Step 1 const int n = source.length(); const int m = target.length(); if (n == 0) { return m; } if (m == 0) { return n; } // Good form to declare a TYPEDEF typedef std::vector< std::vector > Tmatrix; Tmatrix matrix(n+1); // Size the vectors in the 2.nd dimension. Unfortunately C++ doesn't // allow for allocation on declaration of 2.nd dimension of vec of vec for (int i = 0; i <= n; i++) { matrix[i].resize(m+1); } // Step 2 for (int i = 0; i <= n; i++) { matrix[i][0]=i; } for (int j = 0; j <= m; j++) { matrix[0][j]=j; } // Step 3 for (int i = 1; i <= n; i++) { const QChar s_i = source[i-1]; // Step 4 for (int j = 1; j <= m; j++) { const QChar t_j = target[j-1]; // Step 5 int cost; if (s_i == t_j) { cost = 0; } else { cost = 1; } // Step 6 const int above = matrix[i-1][j]; const int left = matrix[i][j-1]; const int diag = matrix[i-1][j-1]; //int cell = min( above + 1, min(left + 1, diag + cost)); int cell = (((left+1)>(diag+cost))?diag+cost:left+1); if(above+1 < cell) cell = above+1; // Step 6A: Cover transposition, in addition to deletion, // insertion and substitution. This step is taken from: // Berghel, Hal ; Roach, David : "An Extension of Ukkonen's // Enhanced Dynamic Programming ASM Algorithm" // (http://www.acm.org/~hlb/publications/asm/asm.html) if (i>2 && j>2) { int trans=matrix[i-2][j-2]+1; if (source[i-2]!=t_j) trans++; if (s_i!=target[j-2]) trans++; if (cell>trans) cell=trans; } matrix[i][j]=cell; } } // Step 7 return matrix[n][m]; } float normalisedLevenshtein(const BoffinPlayableItem& a, const BoffinPlayableItem& b) { // logic lifted from playdar's Resolver::calculate_score // not yet comparing album titles using namespace boost; // original names from the query: QString o_art = a.artist().simplified().toLower(); QString o_trk = a.track().simplified().toLower(); // names from candidate result: QString art = b.artist().simplified().toLower(); QString trk = b.track().simplified().toLower(); // short-circuit for exact match if (o_art == art && o_trk == trk) return 1.0; // the real deal, with edit distances: int trked = levenshtein(trk, o_trk); int arted = levenshtein(art, o_art); // tolerances: const float tol_art = 1.5; const float tol_trk = 1.5; // names less than this many chars aren't dismissed based on % edit-dist: const int grace_len = 6; // if % edit distance is greater than tolerance, fail them outright: if( o_art.length() > grace_len && arted > o_art.length()/tol_art ) return 0.0; if( o_trk.length() > grace_len && trked > o_trk.length()/tol_trk ) return 0.0; // if edit distance longer than original name, fail them outright: if( arted >= o_art.length() ) return 0.0; if( trked >= o_trk.length() ) return 0.0; // combine the edit distance of artist & track into a final score: float artdist_pc = (o_art.length()-arted) / (float) o_art.length(); float trkdist_pc = (o_trk.length()-trked) / (float) o_trk.length(); return artdist_pc * trkdist_pc; } //////////////////////////////////////////////////////////////////////// Shuffler::Shuffler(QObject* parent /* = 0 */) : QObject(parent) , m_artistHistorySize(4) // to mix up the artists , m_songHistorySize(100) // to suppress dup songs { } BoffinPlayableItem Shuffler::sampleOne() { BoffinPlayableItem result = sample(); if (result.isValid()) { // artist memory m_artistHistory.push_back(result.artist()); while (m_artistHistory.size() > m_artistHistorySize) { m_artistHistory.pop_front(); } // track memory m_songHistory.push_back(result); while (m_songHistory.size() > m_songHistorySize) { m_songHistory.pop_front(); } } return result; } const Shuffler::ItemList& Shuffler::items() { return m_items; } void Shuffler::setArtistHistorySize(unsigned size) { m_artistHistorySize = size; } void Shuffler::clear() { m_items.clear(); } void Shuffler::clearHistory() { m_artistHistory.clear(); m_songHistory.clear(); } bool orderByWorkingWeightDesc(const BoffinPlayableItem& a, const BoffinPlayableItem& b) { return a.workingweight() > b.workingweight(); } // pull out a single item BoffinPlayableItem Shuffler::sample() { BoffinPlayableItem result; if (m_items.size()) { ItemList::iterator begin = m_items.begin(); ItemList::iterator end = m_items.end(); ItemList::iterator it; // reweight: float totalWeight = 0; for (it = begin; it != end; it++) { it->workingweight() = it->weight() * pushdown(*it); totalWeight += it->workingweight(); } // normalise weights, sum to 1 for (it = begin; it != end; it++) { it->workingweight() /= totalWeight; } qSort(begin, end, orderByWorkingWeightDesc); ItemList::iterator sample = m_sampler.singleSample(begin, end, true); result = *sample; m_items.removeAt(sample - begin); } return result; } float Shuffler::pushdown(const BoffinPlayableItem& item) { return pushdownSong(item) * (m_artistHistory.contains(item.artist(), Qt::CaseInsensitive) ? 0.00001 : 1.0); } float Shuffler::pushdownSong(const BoffinPlayableItem& item) { int i = 1; float result = 1.0; foreach(const BoffinPlayableItem& historicItem, m_songHistory) { float nl = normalisedLevenshtein(item, historicItem); // levenshtein values not very reliable when less than 0.5 if (nl > 0.5) { float score = 0.1 * (nl * i / (float) m_songHistorySize); if (score < result) { result = score; } } i++; } return result; } void Shuffler::receivePlayableItem(BoffinPlayableItem item) { m_items.push_back(item); } ================================================ FILE: app/boffin/Shuffler.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SHUFFLER_H #define SHUFFLER_H #include #include "playdar/BoffinPlayableItem.h" #include "sample/SampleFromDistribution.h" class Shuffler : public QObject { Q_OBJECT public: typedef QList ItemList; Shuffler(QObject* parent = 0); BoffinPlayableItem sampleOne(); const ItemList& items(); void setArtistHistorySize(unsigned size); void setSongHistorySize(unsigned size); void clear(); void clearHistory(); public slots: void receivePlayableItem(BoffinPlayableItem item); private: BoffinPlayableItem sample(); void result(const BoffinPlayableItem& item); float pushdown(const BoffinPlayableItem& item); float pushdownSong(const BoffinPlayableItem& item); struct AccessPolicy { float operator()(const QList::const_iterator& el) const { return el->workingweight(); } }; struct CopyPolicy { template BoffinPlayableItem operator()(IT& it) const { return *it; } template const BoffinPlayableItem operator()(const IT& it) const { return *it; } }; fm::last::algo::ListBasedSampler m_sampler; ItemList m_items; // items arrive here QStringList m_artistHistory; int m_artistHistorySize; QList m_songHistory; int m_songHistorySize; }; #endif ================================================ FILE: app/boffin/TagBrowserWidget.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include "TagBrowserWidget.h" #include "TagCloudView.h" #include "PlaydarTagCloudModel.h" #include "TagDelegate.h" #include "HistoryWidget.h" #include "PlaylistWidget.h" #include "PlaylistModel.h" TagBrowserWidget::TagBrowserWidget(PlaydarConnection* playdar, QWidget* parent) : QWidget(parent), m_playdar(playdar) { m_tagCloudModel = new PlaydarTagCloudModel(playdar); m_filter = new RelevanceFilter(); m_filter->setSourceModel(m_tagCloudModel); QVBoxLayout* vlayout = new QVBoxLayout(this); QWidget* w = new QWidget(this); m_rqlSentence = new QLabel( this ); vlayout->addWidget( m_rqlSentence ); m_view = new TagCloudView(w); m_view->setModel(m_filter); connect(m_tagCloudModel, SIGNAL(tagItem(BoffinTagItem)), m_view, SLOT(onTag(BoffinTagItem))); connect(m_tagCloudModel, SIGNAL(fetchedTags()), m_view, SLOT(onFetchedTags())); m_view->setItemDelegate(new TagDelegate); connect(m_view->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(onSelectionChanged(QItemSelection, QItemSelection))); vlayout->addWidget(m_view); m_tagCloudModel->startGetTags(); this->setLayout(vlayout); } QStringList TagBrowserWidget::selectedTags() const { QStringList tags; foreach(const QModelIndex& i, m_view->selectionModel()->selectedIndexes()) { tags << i.data().toString(); } return tags; } QString TagBrowserWidget::rql() const { QString r; foreach( QString s, m_rql ) { if( s == "and" || s == "or" ) { r += " " + s + " "; continue; } r += "tag:\"" + s + "\""; } return r; } QString TagBrowserWidget::human() const { return m_rql.join( " " ); } void TagBrowserWidget::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { if( !selected.isEmpty() ) { //New Tag Selected const QModelIndex selectedIndex = selected.last().topLeft(); if( !m_rql.isEmpty() ) { if( qFuzzyCompare( 0.0f, selectedIndex.data( PlaydarTagCloudModel::RelevanceRole ).value() ) ) { m_rql << "or"; } else { m_rql << "and"; } } m_rql << selectedIndex.data().toString(); } else if( !deselected.isEmpty() ) { //Tag DeSelected const QModelIndex deSelectedIndex = deselected.last().topLeft(); int i = m_rql.indexOf( deSelectedIndex.data().toString() ); if( i == 0 ) { m_rql.removeAt( 0 ); m_rql.removeAt( 0 ); } else { m_rql.removeAt( i ); m_rql.removeAt( i-1 ); } qDebug() << "Deselecting: " << deSelectedIndex.data().toString(); } qDebug() << "filtering: " << rql(); m_filter->setRqlFilter(m_playdar, rql(), m_view->selectionModel()->selectedIndexes()); m_rqlSentence->setText( human() ); // ((PlaylistModel*)m_playlistWidget->model())->clear(); // m_playlistWidget->loadFromRql(rql); emit selectionChanged(); } void TagBrowserWidget::onFilterClicked() { // toggle the filter: m_filter->showRelevant(m_filter->showingAll()); } void TagBrowserWidget::onSliderChanged( int val ) { m_filter->setMinimumTrackCountFilter( val ); } ================================================ FILE: app/boffin/TagBrowserWidget.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TAG_BROWSER_WIDGET_H #define TAG_BROWSER_WIDGET_H #include #include #include #include #include class QItemSelection; class TagCloudView; class PlaydarTagCloudModel; class SideBySideLayout; class HistoryWidget; class PlaydarConnection; class PlaylistModel; class PlaylistWidget; #include "playdar/PlaydarConnection.h" #include "PlaydarTagCloudModel.h" #include #include #include class RelevanceFilter : public QSortFilterProxyModel { Q_OBJECT; public: RelevanceFilter() :m_req(0) ,m_showAll( true ) ,m_minimumTrackCountFilter( 0 ) ,m_maxTrackCount( 0 ) { } void resetFilter() { m_logCountMap.clear(); m_showAll = true; invalidateFilter(); } void setRqlFilter(PlaydarConnection* playdar, QString rql, QModelIndexList selected) { if (rql.length() == 0) { resetFilter(); return; } m_countMap.clear(); m_logCountMap.clear(); m_minTrackCount = FLT_MAX; m_maxTrackCount = FLT_MIN; if (m_req) { m_req->disconnect(this); } m_req = playdar->boffinTagcloud( rql ); connect(m_req, SIGNAL(tagItem(BoffinTagItem)), SLOT(onTagItem(BoffinTagItem))); invalidateFilter(); } void setMinimumTrackCountFilter( int i = 0 ) { m_minimumTrackCountFilter = i; invalidateFilter(); } void showRelevant(bool bShowRelevant) { if (bShowRelevant == m_showAll) { m_showAll = !m_showAll; invalidateFilter(); } } bool showingRelevant() { return !m_showAll; } bool showingAll() { return m_showAll; } protected: virtual bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const { if( m_logCountMap.isEmpty() && sourceModel()->index(source_row, 0, source_parent) .data( PlaydarTagCloudModel::CountRole ).toInt() < m_minimumTrackCountFilter ) return false; if( !m_logCountMap.isEmpty() && m_logCountMap[sourceModel()->index(source_row, 0, source_parent) .data().toString()] < m_minimumTrackCountFilter ) return false; if (m_showAll) return true; QModelIndex i = sourceModel()->index(source_row, 0, source_parent); const QString tagname = i.data().toString(); return m_logCountMap.contains(tagname); } // we own the RelevanceRole. :) virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const { if (role == PlaydarTagCloudModel::RelevanceRole) { if( m_logCountMap.isEmpty()) return 0.0; const QString tagname = mapToSource(index).data().toString(); QMap::const_iterator i = m_logCountMap.find(tagname); qreal result; // A relevance weight will be returned with a value of min < result < max // or 0.0 if there is absolutely no relevance. const float min = 0.2f; const float max = 0.8f; //TODO: relevance = 0 where trackCount >= current selected trackCount if (i == m_logCountMap.end()) { result = 0; } else if (m_maxTrackCount == m_minTrackCount) { result = max; } else { const qreal lc = i.value(); result = min + (( lc - m_minTrackCount ) / ( m_maxTrackCount - m_minTrackCount ) * (max - min)); } return QVariant::fromValue(result); } else if( role == Qt::ToolTipRole ) { int count = 0; int duration = 0; QModelIndex srcindex( mapToSource(index) ); //if no tags are selected then display the total track count per tag if( m_logCountMap.isEmpty() ) { count = srcindex.data( PlaydarTagCloudModel::CountRole ).toInt(); duration = srcindex.data( PlaydarTagCloudModel::SecondsRole ).toInt(); } //otherwise calculate the resultant track count based on currently selected tags const QString tagname = mapToSource(index).data().toString(); QMap::const_iterator i = m_countMap.find(tagname); if( i != m_countMap.end()) { count = i.value(); } return count > 0 ? tr( "%L1 tracks" ).arg( count ) + formatDuration(duration) : tr( "no tracks" ); } return QSortFilterProxyModel::data(index, role); } static QString formatDuration(int seconds) { if (seconds == 0) return QString(); int mins = seconds / 60; int hours = mins / 60; if (hours >= 48) { return QString(" : %1 days %2:%3:%4") .arg(hours / 24) .arg(hours % 24) .arg(mins % 60, 2, 10, QChar('0')) .arg(seconds % 60, 2, 10, QChar('0')); } else if (hours >= 24) { return QString(" : 1 day %1:%2:%3") .arg(hours % 24) .arg(mins % 60, 2, 10, QChar('0')) .arg(seconds % 60, 2, 10, QChar('0')); } else if (hours >= 1) { return QString(" : %1:%2:%3") .arg(hours) .arg(mins % 60, 2, 10, QChar('0')) .arg(seconds % 60, 2, 10, QChar('0')); } return QString(" : %1:%2") .arg(mins) .arg(seconds % 60, 2, 10, QChar('0')); } private slots: void onTagItem(const BoffinTagItem& tagitem) { const QString& tag = tagitem.m_name; if( tagitem.m_count == 0 ) return; if (insertTag(tag, tagitem.m_count)) { invalidateFilter(); } // potentially, all our data (ie: the RelevanceRole) has changed emit dataChanged( this->mapFromSource(sourceModel()->index(0, 0)), this->mapFromSource(sourceModel()->index(sourceModel()->rowCount(), 0))); } // returns true if the tag is new, false if the tag already existed bool insertTag(const QString& tag, int count) { bool result; if (m_logCountMap.contains(tag)) { m_countMap[tag] += count; //log is not a distributive function so need to log total count m_logCountMap[tag] = log( (float) m_countMap[tag] ); result = false; } else { m_logCountMap[tag] = log( (float) count ); m_countMap[ tag ] = count; result = true; } m_minTrackCount = qMin( m_minTrackCount, m_logCountMap[ tag ] ); m_maxTrackCount = qMax( m_maxTrackCount, m_logCountMap[ tag ] ); return result; } private: BoffinTagRequest* m_req; bool m_showAll; int m_minimumTrackCountFilter; qreal m_maxTrackCount; qreal m_minTrackCount; QMap m_countMap; QMap m_logCountMap; }; class TagBrowserWidget : public QWidget { Q_OBJECT public: TagBrowserWidget(PlaydarConnection*, QWidget* parent = 0); QString human() const; QString rql() const; QStringList selectedTags() const; signals: void selectionChanged(); private slots: void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void onFilterClicked(); void onSliderChanged( int ); private: class QStringList m_rql; class QLabel* m_rqlSentence; TagCloudView* m_view; class QSlider* m_trackCountSlider; RelevanceFilter* m_filter; // sits between model and view PlaydarTagCloudModel* m_tagCloudModel; PlaylistWidget* m_playlistWidget; PlaylistModel* m_playlistModel; QStringList m_tags; PlaydarConnection* m_playdar; }; #endif ================================================ FILE: app/boffin/TagCloudView.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include "TagCloudView.h" #include "PlaydarTagCloudModel.h" #include #include #include #include #include TagCloudView::TagCloudView( QWidget* parent ) : QAbstractItemView( parent ) , m_fetched( false ) { QFont f = font(); #ifdef Q_WS_WIN f.setPointSize( 10 ); #else f.setPointSize( 14 ); #endif setFont( f ); viewport()->setMouseTracking( true ); setSelectionMode( QAbstractItemView::MultiSelection ); setSelectionBehavior( QAbstractItemView::SelectItems ); } void TagCloudView::onTag( const BoffinTagItem& tag ) { if( m_fetched ) return; m_loadedTag = tag.m_name; viewport()->update(); } void TagCloudView::setSelection( const QRect& rect, QItemSelectionModel::SelectionFlags f ) { if (state() == DragSelectingState) return; QRect r = rect.translated( 0, verticalScrollBar()->value()); RectsConstIt it = m_rects.constBegin(); for (int c = 0; it != m_rects.constEnd(); it++, c++) { if (it->intersects(r)) { selectionModel()->select(model()->index(c, 0), f); } } } QStringList TagCloudView::currentTags() const { QStringList ret; foreach( QModelIndex i, selectionModel()->selectedIndexes() ) ret << i.data( Qt::DisplayRole ).toString(); return ret; } void TagCloudView::paintEvent( QPaintEvent* e ) { QPainter p( viewport() ); p.setClipRect( e->rect()); if (!m_fetched) { p.drawText( viewport()->rect(), Qt::AlignCenter, "Fetching tags.." + m_loadedTag ); return; } if (m_dirty) { updateGeometries(); m_dirty = false; } if (m_rects.isEmpty()) { p.drawText( viewport()->rect(), Qt::AlignCenter, "No tags have been found!" ); return; } QStyleOptionViewItem opt = viewOptions(); RectsConstIt it = m_rects.constBegin(); for (int c = 0; it != m_rects.constEnd(); it++, c++) { opt.rect = it->translated( 0, -verticalScrollBar()->value() ); if( e->rect().intersects( opt.rect )) { const QModelIndex& index = model()->index(c, 0); opt.state = QStyle::State_None; if( m_hoverIndex == index && isEnabled() ) opt.state = qApp->mouseButtons() == Qt::NoButton ? QStyle::State_MouseOver : QStyle::State_Active; if( selectionModel()->isSelected( index ) ) opt.state |= QStyle::State_Selected; if( isEnabled() ) opt.state |= QStyle::State_Enabled; itemDelegate()->paint( &p, opt, index ); } } } void TagCloudView::onFetchedTags() { m_fetched = true; } void TagCloudView::setModel(QAbstractItemModel *model) { QAbstractItemView::setModel(model); connect(model, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(onRows(QModelIndex, int, int))); connect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)), SLOT(onRows(QModelIndex, int, int))); // connect(model, SIGNAL(modelReset()), SLOT(onModelReset())); } void TagCloudView::onRows(const QModelIndex & parent, int start, int end) { qDebug() << "."; Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(end); // the model has changed (row inserted or deleted) // which means our rects are now rubbish // (they will get refreshed in paintEvent() // ...which will happen eventually) m_dirty = true; viewport()->update(); } int gBaseline, gLeftMargin; //filthy but easiest void TagCloudView::rectcalc() { QStyleOptionViewItem const opt = viewOptions(); int baseline = 0; m_rects.clear(); for (int j = 0; j < model()->rowCount(); ++j) { QModelIndex const i = model()->index( j, 0 ); QRect r( QPoint(), itemDelegate()->sizeHint( opt, i ) ); if (baseline == 0) baseline = gBaseline; r.moveTo( gLeftMargin, baseline-gBaseline ); m_rects.insert(j, r); } } void TagCloudView::updateGeometries() { boost::timer bt; if (!model()) return; rectcalc(); //TODO only needs to be done once when data is set! const int VIEWPORT_MARGIN = 10; int y = VIEWPORT_MARGIN; int left_margin = 0; // the left baseline to align text against // iterate in model-order to lay the tags out left to right for (int j = 0; j < model()->rowCount(); ++j) { QRect r = m_rects[j]; if (left_margin == 0) left_margin = r.x(); // do new row int x = VIEWPORT_MARGIN + (left_margin - r.x()); int tallest = 0; for (; j < model()->rowCount(); ++j) { QRect r = m_rects[j]; r.moveTo( x, y + r.y() ); x += r.width(); if (tallest != 0 //need at least one thing per row && x > viewport()->width() - VIEWPORT_MARGIN) { --j; break; } m_rects[j] = r; tallest = qMax( tallest, r.bottom() - y ); } y += tallest; } verticalScrollBar()->setRange( 0, y + VIEWPORT_MARGIN - viewport()->height() ); verticalScrollBar()->setPageStep( viewport()->height() ); verticalScrollBar()->setSingleStep( 20 /*TODO*/ ); viewport()->update(); QAbstractItemView::updateGeometries(); } void TagCloudView::selectAll() { QItemSelection allSelection( model()->index( 0 , 0 ), model()->index( model()->rowCount()-1, 0 )); selectionModel()->select( allSelection, QItemSelectionModel::Toggle ); viewport()->update(); } QModelIndex TagCloudView::indexAt( const QPoint& pos ) const { QPoint p = pos + QPoint( 0, verticalScrollBar()->value()); RectsConstIt it = m_rects.constBegin(); for (int c = 0; it != m_rects.constEnd(); it++, c++) { if( it->contains( p )) { return model()->index(c, 0); } } return QModelIndex(); } QRect TagCloudView::visualRect( const QModelIndex& i ) const { return m_rects[ i.row() ].translated( 0, -verticalScrollBar()->value()); } bool TagCloudView::viewportEvent( QEvent* event ) { switch( event->type() ) { case QEvent::Wheel: { // do first so it moves the view bool b = QAbstractItemView::viewportEvent( event ); // then calculate the new tag under the mouse m_hoverIndex = indexAt( viewport()->mapFromGlobal( QCursor::pos() ) ); return b; } case QEvent::MouseMove: { QMouseEvent* e = static_cast< QMouseEvent* >( event ); QModelIndex const oldindex = m_hoverIndex; m_hoverIndex = indexAt( e->pos() ); if (oldindex != m_hoverIndex) viewport()->update(); break; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: if (m_hoverIndex.isValid()) viewport()->update(); break; case QEvent::KeyPress: case QEvent::KeyRelease: return false; default: break; } return QAbstractItemView::viewportEvent( event ); } ================================================ FILE: app/boffin/TagCloudView.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TAG_CLOUD_VIEW_H #define TAG_CLOUD_VIEW_H #include #include "playdar/BoffinTagRequest.h" class TagCloudView : public QAbstractItemView { Q_OBJECT public: TagCloudView( QWidget* parent = 0 ); // QAbstractItemView pure virtual functions: virtual QModelIndex indexAt( const QPoint& ) const; virtual void scrollTo( const QModelIndex&, ScrollHint ) {}; virtual QRect visualRect( const QModelIndex& ) const; void setModel(QAbstractItemModel *model); QStringList currentTags() const; public slots: virtual void selectAll(); protected slots: virtual void updateGeometries(); void onRows(const QModelIndex & parent, int start, int end); void onFetchedTags(); void onTag( const BoffinTagItem& ); protected: void rectcalc(); virtual void paintEvent( QPaintEvent* ); virtual bool isIndexHidden( const QModelIndex& ) const{ return false; } virtual void setSelection( const QRect&, QItemSelectionModel ){}; virtual QRegion visualRegionForSelection( const QItemSelection& ) const{ return QRegion(); } virtual QModelIndex moveCursor( CursorAction, Qt::KeyboardModifiers ) { return QModelIndex(); } virtual int horizontalOffset() const{ return 0; } virtual int verticalOffset() const{ return 0; } virtual void setSelection( const QRect&, QItemSelectionModel::SelectionFlags ); virtual bool viewportEvent(QEvent *event); QModelIndex m_hoverIndex; QHash m_rects; // row -> QRect typedef QHash::const_iterator RectsConstIt; typedef QHash::iterator RectsIt; bool m_dirty; bool m_fetched; QString m_loadedTag; }; #endif //TAG_CLOUD_VIEW_H ================================================ FILE: app/boffin/TagDelegate.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "TagDelegate.h" #include "PlaydarTagCloudModel.h" #include static inline QFont font( QFont f, float const weight ) { static const float k_factor = 16; f.setPointSize( f.pointSize() + (k_factor * weight )); f.setWeight( 99 * weight ); return f; } static inline QSize margins( float const weight ) { return QSize( 17 + (11*weight), 20 - (10*weight) ); } TagDelegate::TagDelegate( QObject* parent ) : QAbstractItemDelegate( parent ) {} void TagDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QPen p( Qt::NoPen ); QBrush b( Qt::NoBrush ); int alpha = 255; float tagRelevance = index.data( PlaydarTagCloudModel::RelevanceRole ).value() * 0.8; QColor bc(54,115,213); QColor borderColor = bc; bc.setAlphaF( tagRelevance * 0.5 ); borderColor.setAlphaF( tagRelevance ); b = QBrush( bc ); p = QPen( borderColor ); QColor const dark = option.palette.color( QPalette::Highlight ).darker(); if( option.state & (QStyle::State_Selected | QStyle::State_Active) ) { b = option.state & QStyle::State_Enabled ? option.palette.highlight() : QColor(0x64, 0x64, 0x64, alpha ); } if( option.state & QStyle::State_MouseOver ) p = option.palette.color( QPalette::Highlight ); if( option.state & QStyle::State_Active ) p = dark; p.setWidth( 3 ); painter->setPen( p ); painter->setBrush( b ); painter->setRenderHint( QPainter::Antialiasing, true ); painter->drawRoundedRect( option.rect.adjusted( 2, 2, -2, -2 ), 5.0f, 5.0f ); const float weight = index.data( PlaydarTagCloudModel::LinearWeightRole ).value(); painter->setFont( font( option.font, weight ) ); painter->setRenderHint( QPainter::Antialiasing, false ); QColor textColor = option.state & (QStyle::State_Selected|QStyle::State_Active) ? option.palette.color( QPalette::HighlightedText ) : option.palette.color( QPalette::Text ); textColor.setAlpha( alpha ); painter->setPen( textColor ); QString const text = index.data().toString(); QFontMetrics const metrics = painter->fontMetrics(); QPoint pt; pt.setX( option.rect.x() + (option.rect.width() - metrics.width( text ))/2 ); pt.setY( option.rect.y() + margins( weight ).height()/2 + metrics.ascent() ); painter->drawText( pt, text ); } extern int gBaseline; extern int gLeftMargin; QSize TagDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const { const float weight = index.data( PlaydarTagCloudModel::LinearWeightRole ).value(); QFontMetrics fm( font( option.font, weight ) ); const QSize margin = margins( weight ); gBaseline = margin.height()/2 + fm.ascent(); gLeftMargin = margin.width()/2; return fm.size( Qt::TextSingleLine, index.data().toString() ) + margin; } ================================================ FILE: app/boffin/TagDelegate.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TAG_DELEGATE_H #define TAG_DELEGATE_H #include class TagDelegate: public QAbstractItemDelegate { public: TagDelegate( QObject* parent = 0 ); virtual void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; virtual QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; }; #endif //TAG_DELEGATE_H ================================================ FILE: app/boffin/TrackSource.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "TrackSource.h" #include "Shuffler.h" static Track toTrack(const BoffinPlayableItem& item) { Track t; MutableTrack mt(t); mt.setArtist(item.artist()); mt.setAlbum(item.album()); mt.setTitle(item.track()); mt.setDuration(item.duration()); mt.setUrl(QUrl(item.url())); mt.setSource(Track::Player); return t; } TrackSource::TrackSource(Shuffler* shuffler, QObject *parent) : QObject(parent) , m_shuffler(shuffler) , m_maxSize(1) { } Track TrackSource::takeNextTrack() { fillBuffer(); if (m_buffer.isEmpty()) return Track(); Track result = toTrack(m_buffer.takeFirst()); fillBuffer(); emit changed(); return result; } void TrackSource::fillBuffer() { while (m_buffer.size() < m_maxSize) { BoffinPlayableItem item = m_shuffler->sampleOne(); if (item.isValid()) m_buffer.append(item); else break; } } BoffinPlayableItem TrackSource::peek(unsigned index) { return (index >= 0 && index < (unsigned) m_buffer.size()) ? m_buffer[index] : BoffinPlayableItem(); } int TrackSource::size() { return m_buffer.size(); } void TrackSource::setSize(unsigned maxSize) { m_maxSize = maxSize; } void TrackSource::clear() { if (!m_buffer.isEmpty()) { m_buffer.clear(); emit changed(); } } ================================================ FILE: app/boffin/TrackSource.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TRACK_SOURCE_H #define TRACK_SOURCE_H #include #include #include "playdar/BoffinPlayableItem.h" class Shuffler; // Tracksource samples BoffinPlayableItem objects from the Shuffler, // maintains a small buffer of them (for upcoming-track feature). // MediaPipeline then takes Track objects from us. class TrackSource : public QObject { Q_OBJECT public: TrackSource(Shuffler* shuffler, QObject *parent); Track takeNextTrack(); BoffinPlayableItem peek(unsigned index); int size(); void setSize(unsigned maxSize); void clear(); signals: void changed(); // the buffer has changed somehow. private: void fillBuffer(); Shuffler* m_shuffler; QList m_buffer; int m_maxSize; }; #endif ================================================ FILE: app/boffin/WordleDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef WORDLE_DIALOG_H #define WORDLE_DIALOG_H #include #include #include #include #include #include #include class WordleDialog : public QDialog { QTextEdit* output; public: WordleDialog( QWidget* parent = 0 ) : QDialog( parent ) { new QVBoxLayout( this ); layout()->addWidget( output = new QTextEdit()); output->setReadOnly( true ); QLabel* hint; layout()->addWidget( hint = new QLabel); hint->setOpenExternalLinks( true ); hint->setTextFormat( Qt::RichText ); hint->setText( tr("

    We copied the wordlist to your clipboard already.
    " "So just visit Wordle Advanced and paste!

    " "

    Why not save your Wordle with username \"boffin\" and your
    " "Last.fm username as the title so we can start a community?

    ") ); QDialogButtonBox* buttons; layout()->addWidget( buttons = new QDialogButtonBox( QDialogButtonBox::Ok )); connect( buttons, SIGNAL(accepted()), SLOT( accept())); buttons->button( QDialogButtonBox::Ok )->setText( tr("Close") ); setWindowTitle( tr("Your Wordlized Tags") ); } void setText( const QString& t ) { output->setText( t ); //output->selectAll(); //looks wrong QApplication::clipboard()->setText( t ); } }; #endif //WORDLE_DIALOG_H ================================================ FILE: app/boffin/XspfDialog.cpp ================================================ /*************************************************************************** * Copyright 2009 Last.fm Ltd. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include "XspfDialog.h" #include "XspfReader.h" #include "playdar/PlaydarConnection.h" #include "playdar/TrackResolveRequest.h" #include "playdar/BoffinPlayableItem.h" XspfDialog::XspfDialog(QString path, PlaydarConnection* playdar, QWidget *parent) :QDialog(parent) ,m_playdar(playdar) { QLayout* layout = new QHBoxLayout(); m_treewidget = new QTreeWidget(); m_treewidget->setColumnCount(12); QStringList labels; labels << "Position" << "Artist" << "Album" << "Title" << "Length" << "Url" << "Score" << "Pref" << "Source" << "Bitrate" << "Size" << "Mimetype"; m_treewidget->setHeaderLabels(labels); layout->addWidget(m_treewidget); setLayout(layout); setSizeGripEnabled(true); m_reader = new XspfReader(path); connect(m_reader, SIGNAL(xspf(lastfm::Xspf)), SLOT(onXspf(lastfm::Xspf))); } void XspfDialog::onXspf(const lastfm::Xspf& xspf) { setWindowTitle(xspf.title()); int i = 0; foreach(const lastfm::Track& t, xspf.tracks()) { TrackResolveRequest* req = m_playdar->trackResolve(t.artist(), t.album(), t.title()); QTreeWidgetItem* item = new QTreeWidgetItem(); m_reqmap[req->qid()] = item; item->setData(0, Qt::DisplayRole, QString::number(i)); item->setData(1, Qt::DisplayRole, (QString)t.artist()); item->setData(2, Qt::DisplayRole, (QString)t.album()); item->setData(3, Qt::DisplayRole, (QString)t.title()); item->setData(4, Qt::DisplayRole, t.duration()); item->setData(5, Qt::DisplayRole, t.url().toString()); m_treewidget->addTopLevelItem(item); connect(req, SIGNAL(result(BoffinPlayableItem)), SLOT(onResolveResult(BoffinPlayableItem))); i++; } } void XspfDialog::onResolveResult(const BoffinPlayableItem& item) { TrackResolveRequest* req = qobject_cast(sender()); if (req) { QMap::const_iterator it = m_reqmap.constFind(req->qid()); if (it != m_reqmap.constEnd()) { QTreeWidgetItem* child = new QTreeWidgetItem(); child->setFirstColumnSpanned(true); child->setData(1, Qt::DisplayRole, item.artist()); child->setData(2, Qt::DisplayRole, item.album()); child->setData(3, Qt::DisplayRole, item.track()); child->setData(4, Qt::DisplayRole, item.duration()); child->setData(5, Qt::DisplayRole, item.url()); child->setData(6, Qt::DisplayRole, item.score()); child->setData(7, Qt::DisplayRole, item.preference()); child->setData(8, Qt::DisplayRole, item.source()); child->setData(9, Qt::DisplayRole, item.bitrate()); child->setData(10, Qt::DisplayRole, item.size()); child->setData(11, Qt::DisplayRole, item.mimetype()); it.value()->addChild(child); } else { qDebug() << "unknown qid " << req->qid(); } } } ================================================ FILE: app/boffin/XspfDialog.h ================================================ /*************************************************************************** * Copyright 2009 Last.fm Ltd. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 XSPF_DIALOG_H #define XSPF_DIALOG_H #include #include #include class QTreeWidget; class QTreeWidgetItem; class XspfReader; class PlaydarConnection; class BoffinPlayableItem; class XspfDialog : public QDialog { Q_OBJECT public: XspfDialog(QString url, PlaydarConnection* playdar, QWidget *parent = 0); private slots: void onXspf(const lastfm::Xspf& xspf); void onResolveResult(const BoffinPlayableItem& item); private: XspfReader* m_reader; QTreeWidget* m_treewidget; PlaydarConnection* m_playdar; QMap m_reqmap; // map request qid to index }; #endif ================================================ FILE: app/boffin/XspfReader.cpp ================================================ /*************************************************************************** * Copyright 2005-2009 Last.fm Ltd. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "XspfReader.h" #include #include #include XspfReader::XspfReader(QUrl url) : m_url(url) { m_reply = (new lastfm::NetworkAccessManager(this))->get(QNetworkRequest(m_url)); connect(m_reply, SIGNAL(finished()), SLOT(onFinished())); } void XspfReader::onFinished() { QDomDocument xmldoc; xmldoc.setContent(m_reply); QDomElement docElement(xmldoc.documentElement()); if (docElement.tagName() == "playlist") { handleXspf(docElement); } else if (docElement.tagName() == "lfm") { handleXspf(docElement.firstChildElement("playlist")); } else { // todo } m_reply->deleteLater(); m_reply = 0; } void XspfReader::handleXspf(const QDomElement& playlistElement) { Xspf xspf(playlistElement); emit XspfReader::xspf(xspf); } ================================================ FILE: app/boffin/XspfReader.h ================================================ /*************************************************************************** * Copyright 2005-2009 Last.fm Ltd. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 XSPF_READER #define XSPF_READER #include #include class XspfReader : public QObject { Q_OBJECT QUrl m_url; class QNetworkReply *m_reply; QList m_queue; void handleXspf(const QDomElement& playlistElement); signals: void xspf(lastfm::Xspf); private slots: void onFinished(); public: XspfReader(QUrl); }; #endif ================================================ FILE: app/boffin/boffin.pro ================================================ CONFIG += unicorn boost yajl QT += opengl sql phonon VERSION = 1.0.0 DEFINES += LASTFM_COLLAPSE_NAMESPACE include( $$ROOT_DIR/admin/include.qmake ) mac{ release{ QMAKE_INFO_PLIST = Info.plist.in system( $$ROOT_DIR/admin/dist/mac/Makefile.dmg.pl $$LIBS > Makefile.dmg ) QMAKE_EXTRA_INCLUDES += Makefile.dmg ICON = ../client/mac/client.icns CONFIG += app_bundle } } SOURCES += \ XspfReader.cpp \ XspfDialog.cpp \ TrackSource.cpp \ TagDelegate.cpp \ TagCloudView.cpp \ TagBrowserWidget.cpp \ Shuffler.cpp \ ScrobSocket.cpp \ ScanProgressWidget.cpp \ PlaylistModel.cpp \ Playlist.cpp \ PlaydarTagCloudModel.cpp \ PlaydarHostsModel.cpp \ playdar/TrackResolveRequest.cpp \ playdar/PlaydarStatRequest.cpp \ playdar/PlaydarRosterRequest.cpp \ playdar/PlaydarConnection.cpp \ playdar/PlaydarCometRequest.cpp \ playdar/PlaydarAuthRequest.cpp \ playdar/jsonGetMember.cpp \ playdar/CometRequest.cpp \ playdar/BoffinTagRequest.cpp \ playdar/BoffinRqlRequest.cpp \ playdar/BoffinPlayableItem.cpp \ PickDirsDialog.cpp \ MediaPipeline.cpp \ MainWindow.cpp \ main.cpp \ LocalCollectionScanner.cpp \ layouts/SideBySideLayout.cpp \ json_spirit/json_spirit_writer.cpp \ json_spirit/json_spirit_value.cpp \ json_spirit/json_spirit_reader.cpp \ HistoryWidget.cpp \ comet/CometParser.cpp \ App.cpp HEADERS += \ XspfReader.h \ XspfDialog.h \ WordleDialog.h \ TrackSource.h \ TagDelegate.h \ TagCloudView.h \ TagBrowserWidget.h \ Shuffler.h \ ScrobSocket.h \ ScanProgressWidget.h \ sample/SampleFromDistribution.h \ PlaylistWidget.h \ PlaylistModel.h \ Playlist.h \ PlaydarTagCloudModel.h \ PlaydarHostsModel.h \ playdar/TrackResolveRequest.h \ playdar/PlaydarStatRequest.h \ playdar/PlaydarRosterRequest.h \ playdar/PlaydarConnection.h \ playdar/PlaydarCometRequest.h \ playdar/PlaydarAuthRequest.h \ playdar/PlaydarApi.h \ playdar/jsonGetMember.h \ playdar/CometRequest.h \ playdar/BoffinTagRequest.h \ playdar/BoffinRqlRequest.h \ playdar/BoffinPlayableItem.h \ PickDirsDialog.h \ MediaPipeline.h \ MainWindow.h \ LocalCollectionScanner.h \ layouts/SideBySideLayout.h \ json_spirit/json_spirit_writer.h \ json_spirit/json_spirit_value.h \ json_spirit/json_spirit_utils.h \ json_spirit/json_spirit_reader.h \ json_spirit/json_spirit.h \ HistoryWidget.h \ comet/CometParser.h \ App.h RESOURCES += \ qrc/boffin.qrc ================================================ FILE: app/boffin/comet/CometParser.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "CometParser.h" CometParser::CometParser(QObject *parent) : QObject(parent) { yajl_parser_config cfg = { 1 /* allow comments */, 0 /* don't check the incoming utf8 */ }; m_handle = yajl_alloc(&CometCallbacks::callbacks, &cfg, NULL, (void *) this); } CometParser::~CometParser() { if (m_handle) yajl_free(m_handle); } bool CometParser::push(const QByteArray& ba) { const yajl_status status = yajl_parse(m_handle, (const unsigned char*) ba.constData(), ba.length()); return status == yajl_status_ok || status == yajl_status_insufficient_data; } //static void CometParser::objectInserter(CometParser::Object* o, const QString& name, const QVariant& value) { o->insert(name, value); } //static void CometParser::arrayInserter(CometParser::Array* a, const QString&, const QVariant& value) { a->push_back(value); } //static void CometParser::postObjectInserter(CometParser::Inserter i, const QString& name, Object* o) { i(name, *o); delete o; } //static void CometParser::postArrayInserter(CometParser::Inserter i, const QString& name, Array* a) { i(name, *a); delete a; } void CometParser::haveObject(const QString&, const QVariant& v) { if (v.type() == QVariant::Map) { emit haveObject(v.toMap()); } } //static void CometParser::nop() { } //////////////////////////////// // yajl callbacks: int CometParser::json_null() { m_insertStack.top()( m_key, QVariant() ); return 1; } int CometParser::json_boolean(int boolVal) { m_insertStack.top()( m_key, QVariant(boolVal ? true : false) ); return 1; } int CometParser::json_number(const QString& s) { bool ok; qlonglong l; double d; if ((l = s.toLongLong(&ok), ok)) { m_insertStack.top()( m_key, QVariant(l) ); } else if ((d = s.toDouble(&ok), ok)) { m_insertStack.top()( m_key, QVariant(d) ); } return 1; } int CometParser::json_string(const QString& s) { m_insertStack.top()( m_key, QVariant(s) ); return 1; } int CometParser::json_start_map() { if (m_insertStack.size() == 0) return 0; // the comet stream we expect has objects wrapped in an array, thx. Object *o = new Object(); m_atEndStack.push( boost::bind(&CometParser::postObjectInserter, m_insertStack.top(), m_key, o) ); m_insertStack.push( boost::bind(&CometParser::objectInserter, o, _1, _2) ); return 1; } int CometParser::json_map_key(const QString& s) { m_key = s; return 1; } int CometParser::json_start_array() { Array *a = new Array(); if (m_insertStack.size() == 0) { // the first enclosing array... // has a special inserter to call the function to emit the haveObject signal m_atEndStack.push( boost::bind(&CometParser::nop) ); m_insertStack.push( boost::bind(&CometParser::haveObject, this, _1, _2) ); } else { m_atEndStack.push( boost::bind(&CometParser::postArrayInserter, m_insertStack.top(), m_key, a) ); m_insertStack.push( boost::bind(&CometParser::arrayInserter, a, _1, _2) ); } return 1; } int CometParser::json_end_map() { m_insertStack.pop(); m_atEndStack.pop()(); return 1; } int CometParser::json_end_array() { m_insertStack.pop(); m_atEndStack.pop()(); return 1; } ================================================ FILE: app/boffin/comet/CometParser.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef COMET_PARSER_H #define COMET_PARSER_H #include #include #include #include #include #include #include "YajlCallbacks.hpp" // parses a neverending json comet stream like: // "[ {}, {}, ...." // // signal emitted for each top-level object // // yajl does the json (developed using yajl 1.0.5) // this class transforms the tokens into QVariantMap types // class CometParser : public QObject { Q_OBJECT; Q_DISABLE_COPY(CometParser); struct QStringPolicy { static QString stringize(const char* s, unsigned int len) { return QString::fromUtf8(s, len); } }; typedef TYajlCallbacks CometCallbacks; typedef QVariantMap Object; typedef QVariantList Array; typedef boost::function Inserter; typedef boost::function AtEnd; friend class TYajlCallbacks; public: CometParser(QObject *parent = 0); ~CometParser(); bool push(const QByteArray& ba); // push data in... signals: void haveObject(QVariantMap o); // ...and objects pop out private: // inserters and post-inserters static void objectInserter(Object* o, const QString& name, const QVariant& value); static void arrayInserter(Array* a, const QString&, const QVariant& value); static void postObjectInserter(Inserter i, const QString& name, Object* o); static void postArrayInserter(Inserter i, const QString& name, Array* a); void haveObject(const QString&, const QVariant& v); static void nop(); //////////////////////////////// // yajl callbacks int json_null(); int json_boolean(int boolVal); int json_number(const QString& s); int json_string(const QString& s); int json_start_map(); int json_map_key(const QString& s); int json_start_array(); int json_end_map(); int json_end_array(); ///////////////// QString m_key; // the last key we saw QStack m_insertStack; // inserters are used to put values into objects and arrays QStack m_atEndStack; // we call these at the end of an object or array yajl_handle m_handle; }; #endif ================================================ FILE: app/boffin/comet/YajlCallbacks.hpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef YAJL_CALLBACKS_HPP #define YAJL_CALLBACKS_HPP #include // trampoline yajl callbacks to "json_" method calls on class T // strings are converted using Policy::stringize(const char*, unsigned int len) // we use the 'number' callback instead of the integer/double callbacks. template class TYajlCallbacks : private Policy { private: static int _null(void *ctx) { return ((T*)ctx)->json_null(); } static int _boolean(void *ctx, int boolVal) { return ((T*)ctx)->json_boolean(boolVal); } static int _number(void *ctx, const char *numberVal, unsigned int numberLen) { return ((T*)ctx)->json_number(Policy::stringize(numberVal, numberLen)); } static int _string(void *ctx, const unsigned char *stringVal, unsigned int stringLen) { return ((T*)ctx)->json_string(Policy::stringize((const char *)stringVal, stringLen)); } static int _start_map(void *ctx) { return ((T*)ctx)->json_start_map(); } static int _map_key(void *ctx, const unsigned char *key, unsigned int stringLen) { return ((T*)ctx)->json_map_key(Policy::stringize((const char *)key, stringLen)); } static int _end_map(void *ctx) { return ((T*)ctx)->json_end_map(); } static int _start_array(void *ctx) { return ((T*)ctx)->json_start_array(); } static int _end_array(void *ctx) { return ((T*)ctx)->json_end_array(); } public: static yajl_callbacks callbacks; }; template yajl_callbacks TYajlCallbacks::callbacks = { TYajlCallbacks::_null, TYajlCallbacks::_boolean, 0, // integer 0, // double TYajlCallbacks::_number, TYajlCallbacks::_string, TYajlCallbacks::_start_map, TYajlCallbacks::_map_key, TYajlCallbacks::_end_map, TYajlCallbacks::_start_array, TYajlCallbacks::_end_array }; #endif ================================================ FILE: app/boffin/json_spirit/README ================================================ json spirit v3.0 From: http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx ================================================ FILE: app/boffin/json_spirit/json_spirit.h ================================================ #ifndef JASON_SPIRIT #define JASON_SPIRIT /* Copyright (c) 2007-2009 John W Wilkinson This source code can be used for any purpose as long as this comment is retained. */ // json spirit version 3.00 #if defined(_MSC_VER) && (_MSC_VER >= 1020) # pragma once #endif #include "json_spirit_value.h" #include "json_spirit_reader.h" #include "json_spirit_writer.h" #include "json_spirit_utils.h" #endif ================================================ FILE: app/boffin/json_spirit/json_spirit_reader.cpp ================================================ /* Copyright (c) 2007-2009 John W Wilkinson This source code can be used for any purpose as long as this comment is retained. */ // json spirit version 3.00 #include "json_spirit_reader.h" #include "json_spirit_value.h" #define BOOST_SPIRIT_THREADSAFE // uncomment for multithreaded use, requires linking to boost.thead #include #include #include #include #include #include #include using namespace json_spirit; using namespace std; using namespace boost; using namespace boost::spirit; // Error_position::Error_position() : line_( 0 ) , column_( 0 ) { } Error_position::Error_position( unsigned int line, unsigned int column, const std::string& reason ) : line_( line ) , column_( column ) , reason_( reason ) { } bool Error_position::operator==( const Error_position& lhs ) const { if( this == &lhs ) return true; return ( reason_ == lhs.reason_ ) && ( line_ == lhs.line_ ) && ( column_ == lhs.column_ ); } // namespace { const int_parser< int64_t > int64_p = int_parser< int64_t >(); template< class Iter_t > bool is_eq( Iter_t first, Iter_t last, const char* c_str ) { for( Iter_t i = first; i != last; ++i, ++c_str ) { if( *c_str == 0 ) return false; if( *i != *c_str ) return false; } return true; } template< class Char_t > Char_t hex_to_num( const Char_t c ) { if( ( c >= '0' ) && ( c <= '9' ) ) return c - '0'; if( ( c >= 'a' ) && ( c <= 'f' ) ) return c - 'a' + 10; if( ( c >= 'A' ) && ( c <= 'F' ) ) return c - 'A' + 10; return 0; } template< class Char_t, class Iter_t > Char_t hex_str_to_char( Iter_t& begin ) { const Char_t c1( *( ++begin ) ); const Char_t c2( *( ++begin ) ); return ( hex_to_num( c1 ) << 4 ) + hex_to_num( c2 ); } template< class Char_t, class Iter_t > Char_t unicode_str_to_char( Iter_t& begin ) { const Char_t c1( *( ++begin ) ); const Char_t c2( *( ++begin ) ); const Char_t c3( *( ++begin ) ); const Char_t c4( *( ++begin ) ); return ( hex_to_num( c1 ) << 12 ) + ( hex_to_num( c2 ) << 8 ) + ( hex_to_num( c3 ) << 4 ) + hex_to_num( c4 ); } template< class String_t > void append_esc_char_and_incr_iter( String_t& s, typename String_t::const_iterator& begin, typename String_t::const_iterator end ) { typedef typename String_t::value_type Char_t; const Char_t c2( *begin ); switch( c2 ) { case 't': s += '\t'; break; case 'b': s += '\b'; break; case 'f': s += '\f'; break; case 'n': s += '\n'; break; case 'r': s += '\r'; break; case '\\': s += '\\'; break; case '/': s += '/'; break; case '"': s += '"'; break; case 'x': { if( end - begin >= 3 ) // expecting "xHH..." { s += hex_str_to_char< Char_t >( begin ); } break; } case 'u': { if( end - begin >= 5 ) // expecting "uHHHH..." { s += unicode_str_to_char< Char_t >( begin ); } break; } } } template< class String_t > String_t substitute_esc_chars( typename String_t::const_iterator begin, typename String_t::const_iterator end ) { typedef typename String_t::const_iterator Iter_t; if( end - begin < 2 ) return String_t( begin, end ); String_t result; result.reserve( end - begin ); const Iter_t end_minus_1( end - 1 ); Iter_t substr_start = begin; Iter_t i = begin; for( ; i < end_minus_1; ++i ) { if( *i == '\\' ) { result.append( substr_start, i ); ++i; // skip the '\' append_esc_char_and_incr_iter( result, i, end ); substr_start = i + 1; } } result.append( substr_start, end ); return result; } template< class String_t > String_t get_str_( typename String_t::const_iterator begin, typename String_t::const_iterator end ) { assert( end - begin >= 2 ); typedef typename String_t::const_iterator Iter_t; Iter_t str_without_quotes( ++begin ); Iter_t end_without_quotes( --end ); return substitute_esc_chars< String_t >( str_without_quotes, end_without_quotes ); } string get_str( string::const_iterator begin, string::const_iterator end ) { return get_str_< string >( begin, end ); } wstring get_str( wstring::const_iterator begin, wstring::const_iterator end ) { return get_str_< wstring >( begin, end ); } template< class String_t, class Iter_t > String_t get_str( Iter_t begin, Iter_t end ) { const String_t tmp( begin, end ); // convert multipass iterators to string iterators return get_str( tmp.begin(), tmp.end() ); } // this class's methods get called by the spirit parse resulting // in the creation of a JSON object or array // // NB Iter_t could be a std::string iterator, wstring iterator, a position iterator or a multipass iterator // template< class String_t, class Iter_t > class Semantic_actions { public: typedef Value_impl< String_t > Value_t; typedef Pair_impl < String_t > Pair_t; typedef typename Value_t::Object Object_t; typedef typename Value_t::Array Array_t; typedef typename String_t::value_type Char_t; Semantic_actions( Value_t& value ) : value_( value ) , current_p_( 0 ) { } void begin_obj( Char_t c ) { assert( c == '{' ); begin_compound< Object_t >(); } void end_obj( Char_t c ) { assert( c == '}' ); end_compound(); } void begin_array( Char_t c ) { assert( c == '[' ); begin_compound< Array_t >(); } void end_array( Char_t c ) { assert( c == ']' ); end_compound(); } void new_name( Iter_t begin, Iter_t end ) { assert( current_p_->type() == obj_type ); name_ = get_str< String_t >( begin, end ); } void new_str( Iter_t begin, Iter_t end ) { add_to_current( get_str< String_t >( begin, end ) ); } void new_true( Iter_t begin, Iter_t end ) { assert( is_eq( begin, end, "true" ) ); add_to_current( true ); } void new_false( Iter_t begin, Iter_t end ) { assert( is_eq( begin, end, "false" ) ); add_to_current( false ); } void new_null( Iter_t begin, Iter_t end ) { assert( is_eq( begin, end, "null" ) ); add_to_current( Value_t() ); } void new_int( int64_t i ) { add_to_current( i ); } void new_real( double d ) { add_to_current( d ); } private: void add_first( const Value_t& value ) { assert( current_p_ == 0 ); value_ = value; current_p_ = &value_; } template< class Array_or_obj > void begin_compound() { if( current_p_ == 0 ) { add_first( Array_or_obj() ); } else { stack_.push_back( current_p_ ); Array_or_obj new_array_or_obj; // avoid copy by building new array or object in place add_to_current( new_array_or_obj ); if( current_p_->type() == array_type ) { current_p_ = ¤t_p_->get_array().back(); } else { current_p_ = ¤t_p_->get_obj().back().value_; } } } void end_compound() { if( current_p_ != &value_ ) { current_p_ = stack_.back(); stack_.pop_back(); } } void add_to_current( const Value_t& value ) { if( current_p_ == 0 ) { add_first( value ); } else if( current_p_->type() == array_type ) { current_p_->get_array().push_back( value ); } else if( current_p_->type() == obj_type ) { current_p_->get_obj().push_back( Pair_t( name_, value ) ); } } Value_t& value_; // this is the object or array that is being created Value_t* current_p_; // the child object or array that is currently being constructed vector< Value_t* > stack_; // previous child objects and arrays String_t name_; // of current name/value pair }; template< typename Iter_t > void throw_error( position_iterator< Iter_t > i, const std::string& reason ) { throw Error_position( i.get_position().line, i.get_position().column, reason ); } template< typename Iter_t > void throw_error( Iter_t i, const std::string& reason ) { throw reason; } // the spirit grammer // template< class String_t, class Iter_t > class Json_grammer : public grammar< Json_grammer< String_t, Iter_t > > { public: typedef Semantic_actions< String_t, Iter_t > Semantic_actions_t; Json_grammer( Semantic_actions_t& semantic_actions ) : actions_( semantic_actions ) { } static void throw_not_value( Iter_t begin, Iter_t end ) { throw_error( begin, "not a value" ); } static void throw_not_array( Iter_t begin, Iter_t end ) { throw_error( begin, "not an array" ); } static void throw_not_object( Iter_t begin, Iter_t end ) { throw_error( begin, "not an object" ); } static void throw_not_pair( Iter_t begin, Iter_t end ) { throw_error( begin, "not a pair" ); } static void throw_not_colon( Iter_t begin, Iter_t end ) { throw_error( begin, "no colon in pair" ); } static void throw_not_string( Iter_t begin, Iter_t end ) { throw_error( begin, "not a string" ); } template< typename ScannerT > struct definition { definition( const Json_grammer& self ) { typedef typename String_t::value_type Char_t; // first we convert the semantic action class methods to functors with the // parameter signature expected by spirit typedef function< void( Char_t ) > Char_action; typedef function< void( Iter_t, Iter_t ) > Str_action; typedef function< void( double ) > Real_action; typedef function< void( int64_t ) > Int_action; Char_action begin_obj ( bind( &Semantic_actions_t::begin_obj, &self.actions_, _1 ) ); Char_action end_obj ( bind( &Semantic_actions_t::end_obj, &self.actions_, _1 ) ); Char_action begin_array( bind( &Semantic_actions_t::begin_array, &self.actions_, _1 ) ); Char_action end_array ( bind( &Semantic_actions_t::end_array, &self.actions_, _1 ) ); Str_action new_name ( bind( &Semantic_actions_t::new_name, &self.actions_, _1, _2 ) ); Str_action new_str ( bind( &Semantic_actions_t::new_str, &self.actions_, _1, _2 ) ); Str_action new_true ( bind( &Semantic_actions_t::new_true, &self.actions_, _1, _2 ) ); Str_action new_false ( bind( &Semantic_actions_t::new_false, &self.actions_, _1, _2 ) ); Str_action new_null ( bind( &Semantic_actions_t::new_null, &self.actions_, _1, _2 ) ); Real_action new_real ( bind( &Semantic_actions_t::new_real, &self.actions_, _1 ) ); Int_action new_int ( bind( &Semantic_actions_t::new_int, &self.actions_, _1 ) ); // actual grammer json_ = value_ | eps_p[ &throw_not_value ] ; value_ = string_[ new_str ] | number_ | object_ | array_ | str_p( "true" ) [ new_true ] | str_p( "false" )[ new_false ] | str_p( "null" ) [ new_null ] ; object_ = ch_p('{')[ begin_obj ] >> !members_ >> ( ch_p('}')[ end_obj ] | eps_p[ &throw_not_object ] ) ; members_ = pair_ >> *( ',' >> pair_ ) ; pair_ = string_[ new_name ] >> ( ':' | eps_p[ &throw_not_colon ] ) >> ( value_ | eps_p[ &throw_not_value ] ) ; array_ = ch_p('[')[ begin_array ] >> !elements_ >> ( ch_p(']')[ end_array ] | eps_p[ &throw_not_array ] ) ; elements_ = value_ >> *( ',' >> value_ ) ; string_ = lexeme_d // this causes white space inside a string to be retained [ confix_p ( '"', *lex_escape_ch_p, '"' ) ] ; number_ = strict_real_p[ new_real ] | int64_p [ new_int ] ; } rule< ScannerT > json_, object_, members_, pair_, array_, elements_, value_, string_, number_; const rule< ScannerT >& start() const { return json_; } }; Semantic_actions_t& actions_; }; template< class Iter_t, class Value_t > Iter_t read_range_or_throw( Iter_t begin, Iter_t end, Value_t& value ) { typedef typename Value_t::String_type String_t; Semantic_actions< String_t, Iter_t > semantic_actions( value ); const parse_info< Iter_t > info = parse( begin, end, Json_grammer< String_t, Iter_t >( semantic_actions ), space_p ); if( !info.hit ) { assert( false ); // in theory exception should already have been thrown throw_error( info.stop, "error" ); } return info.stop; } template< class Iter_t, class Value_t > void add_posn_iter_and_read_range_or_throw( Iter_t begin, Iter_t end, Value_t& value ) { typedef position_iterator< Iter_t > Posn_iter_t; const Posn_iter_t posn_begin( begin, end ); const Posn_iter_t posn_end( end, end ); read_range_or_throw( posn_begin, posn_end, value ); } template< class Iter_t, class Value_t > bool read_range( Iter_t& begin, Iter_t end, Value_t& value ) { try { begin = read_range_or_throw( begin, end, value ); return true; } catch( ... ) { return false; } } template< class String_t, class Value_t > void read_string_or_throw( const String_t& s, Value_t& value ) { add_posn_iter_and_read_range_or_throw( s.begin(), s.end(), value ); } template< class String_t, class Value_t > bool read_string( const String_t& s, Value_t& value ) { typename String_t::const_iterator begin = s.begin(); return read_range( begin, s.end(), value ); } template< class Istream_t > struct Multi_pass_iters { typedef typename Istream_t::char_type Char_t; typedef istream_iterator< Char_t, Char_t > istream_iter; typedef multi_pass< istream_iter > multi_pass_iter; Multi_pass_iters( Istream_t& is ) { is.unsetf( ios::skipws ); begin_ = make_multi_pass( istream_iter( is ) ); end_ = make_multi_pass( istream_iter() ); } multi_pass_iter begin_; multi_pass_iter end_; }; template< class Istream_t, class Value_t > bool read_stream( Istream_t& is, Value_t& value ) { Multi_pass_iters< Istream_t > mp_iters( is ); return read_range( mp_iters.begin_, mp_iters.end_, value ); } template< class Istream_t, class Value_t > void read_stream_or_throw( Istream_t& is, Value_t& value ) { const Multi_pass_iters< Istream_t > mp_iters( is ); add_posn_iter_and_read_range_or_throw( mp_iters.begin_, mp_iters.end_, value ); } } bool json_spirit::read( const std::string& s, Value& value ) { return read_string( s, value ); } void json_spirit::read_or_throw( const std::string& s, Value& value ) { read_string_or_throw( s, value ); } bool json_spirit::read( std::istream& is, Value& value ) { return read_stream( is, value ); } void json_spirit::read_or_throw( std::istream& is, Value& value ) { read_stream_or_throw( is, value ); } bool json_spirit::read( std::string::const_iterator& begin, std::string::const_iterator end, Value& value ) { return read_range( begin, end, value ); } void json_spirit::read_or_throw( std::string::const_iterator& begin, std::string::const_iterator end, Value& value ) { begin = read_range_or_throw( begin, end, value ); } #ifndef BOOST_NO_STD_WSTRING bool json_spirit::read( const std::wstring& s, wValue& value ) { return read_string( s, value ); } void json_spirit::read_or_throw( const std::wstring& s, wValue& value ) { read_string_or_throw( s, value ); } bool json_spirit::read( std::wistream& is, wValue& value ) { return read_stream( is, value ); } void json_spirit::read_or_throw( std::wistream& is, wValue& value ) { read_stream_or_throw( is, value ); } bool json_spirit::read( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value ) { return read_range( begin, end, value ); } void json_spirit::read_or_throw( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value ) { begin = read_range_or_throw( begin, end, value ); } #endif ================================================ FILE: app/boffin/json_spirit/json_spirit_reader.h ================================================ #ifndef JASON_SPIRIT_READER #define JASON_SPIRIT_READER /* Copyright (c) 2007-2009 John W Wilkinson json spirit version 3.00 This source code can be used for any purpose as long as this comment is retained. */ #if defined(_MSC_VER) && (_MSC_VER >= 1020) # pragma once #endif #include "json_spirit_value.h" #include namespace json_spirit { // functions to reads a JSON values bool read( const std::string& s, Value& value ); bool read( std::istream& is, Value& value ); bool read( std::string::const_iterator& begin, std::string::const_iterator end, Value& value ); #ifndef BOOST_NO_STD_WSTRING bool read( const std::wstring& s, wValue& value ); bool read( std::wistream& is, wValue& value ); bool read( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value ); #endif // the following functions read a JSON values as per the standard functions above but throw an // Error_position on finding an error, they are around 3 times slower than the standard functions struct Error_position { Error_position(); Error_position( unsigned int line, unsigned int column, const std::string& reason ); bool operator==( const Error_position& lhs ) const; unsigned int line_; unsigned int column_; std::string reason_; }; void read_or_throw( const std::string& s, Value& value ); void read_or_throw( std::istream& is, Value& value ); void read_or_throw( std::string::const_iterator& begin, std::string::const_iterator end, Value& value ); #ifndef BOOST_NO_STD_WSTRING void read_or_throw( const std::wstring& s, wValue& value ); void read_or_throw( std::wistream& is, wValue& value ); void read_or_throw( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value ); #endif } #endif ================================================ FILE: app/boffin/json_spirit/json_spirit_utils.h ================================================ #ifndef JASON_SPIRIT_UTILS #define JASON_SPIRIT_UTILS /* Copyright (c) 2007-2009 John W Wilkinson This source code can be used for any purpose as long as this comment is retained. */ // json spirit version 3.00 #if defined(_MSC_VER) && (_MSC_VER >= 1020) # pragma once #endif #include "json_spirit_value.h" #include namespace json_spirit { //template< class Object_t, class String_t > //Value_impl< String_t >& find_value( const Object_t& obj, const String_t &name ); template< class Obj_t, class Map_t > void obj_to_map( const Obj_t& obj, Map_t& mp_obj ) { mp_obj.clear(); for( typename Obj_t::const_iterator i = obj.begin(); i != obj.end(); ++i ) { mp_obj[ i->name_ ] = i->value_; } } template< class Obj_t, class Map_t > void map_to_obj( const Map_t& mp_obj, Obj_t& obj ) { obj.clear(); for( typename Map_t::const_iterator i = mp_obj.begin(); i != mp_obj.end(); ++i ) { obj.push_back( typename Obj_t::value_type( i->first, i->second ) ); } } typedef std::map< std::string, Value > Mapped_obj; #ifndef BOOST_NO_STD_WSTRING typedef std::map< std::wstring, wValue > wMapped_obj; #endif template< class Object_t, class String_t > const Value_impl< String_t >& find_value( const Object_t& obj, const String_t& name ) { for( typename Object_t::const_iterator i = obj.begin(); i != obj.end(); ++i ) { if( i->name_ == name ) { return i->value_; } } return Value_impl< String_t >::null; } } #endif ================================================ FILE: app/boffin/json_spirit/json_spirit_value.cpp ================================================ /* Copyright (c) 2007 John W Wilkinson This source code can be used for any purpose as long as this comment is retained. */ // json spirit version 2.00 #include "json_spirit_value.h" ================================================ FILE: app/boffin/json_spirit/json_spirit_value.h ================================================ #ifndef JASON_SPIRIT_VALUE #define JASON_SPIRIT_VALUE /* Copyright (c) 2007-2009 John W Wilkinson This source code can be used for any purpose as long as this comment is retained. */ // json spirit version 3.00 #if defined(_MSC_VER) && (_MSC_VER >= 1020) # pragma once #endif #include #include #include #include #include #include namespace json_spirit { enum Value_type{ obj_type, array_type, str_type, bool_type, int_type, real_type, null_type }; template< class String > struct Pair_impl; template< class String > // eg std::string or std::wstring class Value_impl { public: typedef String String_type; typedef Pair_impl< String > Pair; typedef std::vector< Pair > Object; typedef std::vector< Value_impl< String > > Array; typedef typename String::const_pointer Const_str_ptr; // eg const char* Value_impl(); // creates null value Value_impl( Const_str_ptr value ); Value_impl( const String& value ); Value_impl( const Object& value ); Value_impl( const Array& value ); Value_impl( bool value ); Value_impl( int value ); Value_impl( boost::int64_t value ); Value_impl( double value ); Value_impl( const Value_impl& other ); bool operator==( const Value_impl& lhs ) const; Value_impl& operator=( const Value_impl& lhs ); Value_type type() const; const String& get_str() const; const Object& get_obj() const; const Array& get_array() const; bool get_bool() const; int get_int() const; boost::int64_t get_int64() const; double get_real() const; Object& get_obj(); Array& get_array(); template< typename T > T get_value() const; // example usage: int i = value.get_value< int >(); // or double d = value.get_value< double >(); static const Value_impl null; private: Value_type type_; typedef boost::shared_ptr< Object > Object_ptr; typedef boost::shared_ptr< Array > Array_ptr; String str_; Object_ptr obj_p_; Array_ptr array_p_; union { bool bool_; boost::int64_t i_; double d_; }; }; template< class String > struct Pair_impl { typedef String String_type; typedef Value_impl< String > Value_type; Pair_impl( const String& name, const Value_type& value ); bool operator==( const Pair_impl< String >& lhs ) const; String name_; Value_type value_; }; // typedefs for ASCII, compatible with JSON Spirit v1.02 typedef Value_impl< std::string > Value; typedef Pair_impl < std::string > Pair; typedef Value::Object Object; typedef Value::Array Array; // typedefs for Unicode, new for JSON Spirit v2.00 #ifndef BOOST_NO_STD_WSTRING typedef Value_impl< std::wstring > wValue; typedef Pair_impl < std::wstring > wPair; typedef wValue::Object wObject; typedef wValue::Array wArray; #endif /////////////////////////////////////////////////////////////////////////////////////////////// // // implementation template< class String > const Value_impl< String > Value_impl< String >::null; template< class String > Value_impl< String >::Value_impl() : type_( null_type ) { } template< class String > Value_impl< String >::Value_impl( const Const_str_ptr value ) : type_( str_type ) , str_( value ) { } template< class String > Value_impl< String >::Value_impl( const String& value ) : type_( str_type ) , str_( value ) { } template< class String > Value_impl< String >::Value_impl( const Object& value ) : type_( obj_type ) , obj_p_( new Object( value ) ) { } template< class String > Value_impl< String >::Value_impl( const Array& value ) : type_( array_type ) , array_p_( new Array( value ) ) { } template< class String > Value_impl< String >::Value_impl( bool value ) : type_( bool_type ) , bool_( value ) { } template< class String > Value_impl< String >::Value_impl( int value ) : type_( int_type ) , i_( value ) { } template< class String > Value_impl< String >::Value_impl( boost::int64_t value ) : type_( int_type ) , i_( value ) { } template< class String > Value_impl< String >::Value_impl( double value ) : type_( real_type ) , d_( value ) { } template< class String > Value_impl< String >::Value_impl( const Value_impl< String >& other ) : type_( other.type() ) { switch( type_ ) { case str_type: str_ = other.get_str(); break; case obj_type: obj_p_ = Object_ptr( new Object( other.get_obj() ) ); break; case array_type: array_p_ = Array_ptr ( new Array ( other.get_array() ) ); break; case bool_type: bool_ = other.get_bool(); break; case int_type: i_ = other.get_int64(); break; case real_type: d_ = other.get_real(); break; case null_type: break; default: assert( false ); }; } template< class String > Value_impl< String >& Value_impl< String >::operator=( const Value_impl& lhs ) { Value_impl tmp( lhs ); std::swap( type_, tmp.type_ ); str_ .swap( tmp.str_ ); obj_p_ .swap( tmp.obj_p_ ); array_p_.swap( tmp.array_p_ ); switch( type_ ) { case bool_type: std::swap( bool_, tmp.bool_ ); break; case int_type: std::swap( i_, tmp.i_ ); break; case real_type: std::swap( d_, tmp.d_ ); break; default: ; }; return *this; } template< class String > bool Value_impl< String >::operator==( const Value_impl& lhs ) const { if( this == &lhs ) return true; if( type() != lhs.type() ) return false; switch( type_ ) { case str_type: return get_str() == lhs.get_str(); case obj_type: return get_obj() == lhs.get_obj(); case array_type: return get_array() == lhs.get_array(); case bool_type: return get_bool() == lhs.get_bool(); case int_type: return get_int64() == lhs.get_int64(); case real_type: return get_real() == lhs.get_real(); case null_type: return true; }; assert( false ); return false; } template< class String > Value_type Value_impl< String >::type() const { return type_; } template< class String > const String& Value_impl< String >::get_str() const { assert( type() == str_type ); return str_; } template< class String > const typename Value_impl< String >::Object& Value_impl< String >::get_obj() const { assert( type() == obj_type ); return *obj_p_; } template< class String > const typename Value_impl< String >::Array& Value_impl< String >::get_array() const { assert( type() == array_type ); return *array_p_; } template< class String > bool Value_impl< String >::get_bool() const { assert( type() == bool_type ); return bool_; } template< class String > int Value_impl< String >::get_int() const { assert( type() == int_type ); return static_cast< int >( i_ ); } template< class String > boost::int64_t Value_impl< String >::get_int64() const { assert( type() == int_type ); return i_; } template< class String > double Value_impl< String >::get_real() const { assert( type() == real_type ); return d_; } template< class String > typename Value_impl< String >::Object& Value_impl< String >::get_obj() { assert( type() == obj_type ); return *obj_p_; } template< class String > typename Value_impl< String >::Array& Value_impl< String >::get_array() { assert( type() == array_type ); return *array_p_; } template< class String > Pair_impl< String >::Pair_impl( const String& name, const Value_impl< String >& value ) : name_( name ) , value_( value ) { } template< class String > bool Pair_impl< String >::operator==( const Pair_impl& lhs ) const { if( this == &lhs ) return true; return ( name_ == lhs.name_ ) && ( value_ == lhs.value_ ); } // converts a C string, ie. 8 bit char array, to a string object // template < class String_t > String_t to_str( const char* c_str ) { String_t result; for( const char* p = c_str; *p != 0; ++p ) { result += *p; } return result; } // namespace internal_ { template< typename T > struct Type_to_type { }; template< class Value > int get_value( const Value& value, Type_to_type< int > ) { return value.get_int(); } template< class Value > boost::int64_t get_value( const Value& value, Type_to_type< boost::int64_t > ) { return value.get_int64(); } template< class Value > double get_value( const Value& value, Type_to_type< double > ) { return value.get_real(); } template< class Value > typename Value::String_type get_value( const Value& value, Type_to_type< typename Value::String_type > ) { return value.get_str(); } template< class Value > typename Value::Array get_value( const Value& value, Type_to_type< typename Value::Array > ) { return value.get_array(); } template< class Value > typename Value::Object get_value( const Value& value, Type_to_type< typename Value::Object > ) { return value.get_obj(); } template< class Value > bool get_value( const Value& value, Type_to_type< bool > ) { return value.get_bool(); } } template< class String > template< typename T > T Value_impl< String >::get_value() const { return internal_::get_value( *this, internal_::Type_to_type< T >() ); } } #endif ================================================ FILE: app/boffin/json_spirit/json_spirit_writer.cpp ================================================ /* Copyright (c) 2007-2009 John W Wilkinson This source code can be used for any purpose as long as this comment is retained. */ // json spirit version 3.00 #include "json_spirit_writer.h" #include "json_spirit_value.h" #include #include #include using namespace json_spirit; using namespace std; namespace { // this class solely allows its inner classes and methods to // be conveniently templatised by Value type and dependent types // template< class Value_t > struct Writer { typedef typename Value_t::String_type String_t; typedef typename Value_t::Object Object_t; typedef typename Value_t::Array Array_t; typedef typename String_t::value_type Char_t; typedef typename String_t::const_iterator Iter_t; typedef Pair_impl< String_t > Pair_t; typedef std::basic_ostream< Char_t > Ostream_t; // this class generates the JSON text, // it keeps track of the indentation level etc. // class Generator { public: Generator( const Value_t& value, Ostream_t& os, bool pretty ) : os_( os ) , indentation_level_( 0 ) , pretty_( pretty ) { output( value ); } private: void output( const Value_t& value ) { switch( value.type() ) { case obj_type: output( value.get_obj() ); break; case array_type: output( value.get_array() ); break; case str_type: output( value.get_str() ); break; case bool_type: output( value.get_bool() ); break; case int_type: os_ << value.get_int64(); break; case real_type: os_ << showpoint << setprecision( 16 ) << value.get_real(); break; case null_type: os_ << "null"; break; default: assert( false ); } } void output( const Object_t& obj ) { output_array_or_obj( obj, '{', '}' ); } void output( const Array_t& arr ) { output_array_or_obj( arr, '[', ']' ); } void output( const Pair_t& pair ) { output( pair.name_ ); space(); os_ << ':'; space(); output( pair.value_ ); } void output( const String_t& s ) { os_ << '"' << add_esc_chars( s ) << '"'; } void output( bool b ) { os_ << to_str( b ? "true" : "false" ); } template< class T > void output_array_or_obj( const T& t, Char_t start_char, Char_t end_char ) { os_ << start_char; new_line(); ++indentation_level_; for( typename T::const_iterator i = t.begin(); i != t.end(); ++i ) { indent(); output( *i ); if( i != t.end() - 1 ) { os_ << ','; } new_line(); } --indentation_level_; indent(); os_ << end_char; } void indent() { if( !pretty_ ) return; for( int i = 0; i < indentation_level_; ++i ) { os_ << " "; } } void space() { if( pretty_ ) os_ << ' '; } void new_line() { if( pretty_ ) os_ << '\n'; } String_t to_str( const char* c_str ) { return ::to_str< String_t >( c_str ); } Char_t to_hex( Char_t c ) { assert( c <= 0xF ); if( c < 10 ) return '0' + c; return 'A' + c - 10; } String_t non_printable_to_string( unsigned int c ) { String_t result( 6, '\\' ); result[1] = 'u'; result[ 5 ] = to_hex( c & 0x000F ); c >>= 4; result[ 4 ] = to_hex( c & 0x000F ); c >>= 4; result[ 3 ] = to_hex( c & 0x000F ); c >>= 4; result[ 2 ] = to_hex( c & 0x000F ); return result; } bool add_esc_char( Char_t c, String_t& s ) { switch( c ) { case '"': s += to_str( "\\\"" ); return true; case '\\': s += to_str( "\\\\" ); return true; case '\b': s += to_str( "\\b" ); return true; case '\f': s += to_str( "\\f" ); return true; case '\n': s += to_str( "\\n" ); return true; case '\r': s += to_str( "\\r" ); return true; case '\t': s += to_str( "\\t" ); return true; } return false; } String_t add_esc_chars( const String_t& s ) { String_t result; const Iter_t end( s.end() ); for( Iter_t i = s.begin(); i != end; ++i ) { const Char_t c( *i ); if( add_esc_char( c, result ) ) continue; const wint_t unsigned_c( ( c >= 0 ) ? c : 256 + c ); if( iswprint( unsigned_c ) ) { result += c; } else { result += non_printable_to_string( unsigned_c ); } } return result; } Ostream_t& os_; int indentation_level_; bool pretty_; }; static void write( const Value_t& value, Ostream_t& os, bool pretty ) { Generator( value, os, pretty ); } static String_t write( const Value_t& value, bool pretty ) { basic_ostringstream< Char_t > os; write( value, os, pretty ); return os.str(); } }; } void json_spirit::write( const Value& value, std::ostream& os ) { Writer< Value >::write( value, os, false ); } void json_spirit::write_formatted( const Value& value, std::ostream& os ) { Writer< Value >::write( value, os, true ); } std::string json_spirit::write( const Value& value ) { return Writer< Value >::write( value, false ); } std::string json_spirit::write_formatted( const Value& value ) { return Writer< Value >::write( value, true ); } #ifndef BOOST_NO_STD_WSTRING void json_spirit::write( const wValue& value, std::wostream& os ) { Writer< wValue >::write( value, os, false ); } void json_spirit::write_formatted( const wValue& value, std::wostream& os ) { Writer< wValue >::write( value, os, true ); } std::wstring json_spirit::write( const wValue& value ) { return Writer< wValue >::write( value, false ); } std::wstring json_spirit::write_formatted( const wValue& value ) { return Writer< wValue >::write( value, true ); } #endif ================================================ FILE: app/boffin/json_spirit/json_spirit_writer.h ================================================ #ifndef JASON_SPIRIT_WRITER #define JASON_SPIRIT_WRITER /* Copyright (c) 2007-2009 John W Wilkinson This source code can be used for any purpose as long as this comment is retained. */ // json spirit version 3.00 #if defined(_MSC_VER) && (_MSC_VER >= 1020) # pragma once #endif #include "json_spirit_value.h" #include namespace json_spirit { // functions to convert JSON Values to text, // the "formatted" versions add whitespace to format the output nicely void write ( const Value& value, std::ostream& os ); void write_formatted( const Value& value, std::ostream& os ); std::string write ( const Value& value ); std::string write_formatted( const Value& value ); #ifndef BOOST_NO_STD_WSTRING std::wstring write ( const wValue& value ); std::wstring write_formatted( const wValue& value ); void write ( const wValue& value, std::wostream& os ); void write_formatted( const wValue& value, std::wostream& os ); #endif } #endif ================================================ FILE: app/boffin/layouts/SideBySideLayout.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "SideBySideLayout.h" #include #include #include SideBySideLayout::SideBySideLayout( QWidget* parent ) : QLayout( parent ), m_currentItem( 0 ), m_timeLine( new QTimeLine( 1500, this ) ) { m_timeLine->setUpdateInterval( 25 ); connect( m_timeLine, SIGNAL( frameChanged( int )), SLOT( onFrameChanged( int ))); connect( m_timeLine, SIGNAL( finished() ), SIGNAL( animationFinished() )); } SideBySideLayout::~SideBySideLayout() { while( QLayoutItem* i = takeAt( 0 ) ) delete i; } void SideBySideLayout::addItem(QLayoutItem *item) { m_itemList.push_back( item ); if( !m_currentItem ) m_currentItem = item; } Qt::Orientations SideBySideLayout::expandingDirections() const { return (Qt::Horizontal | Qt::Vertical); } bool SideBySideLayout::hasHeightForWidth() const { return false; } int SideBySideLayout::count() const { return m_itemList.count(); } QLayoutItem* SideBySideLayout::itemAt( int index ) const { if( index >= m_itemList.count() || index < 0 || m_itemList.isEmpty() ) return 0; return m_itemList.at( index ); } QSize SideBySideLayout::minimumSize() const { QSize minSize; foreach( QLayoutItem* i, m_itemList ) minSize = minSize.expandedTo( i->minimumSize() ); return minSize; } void SideBySideLayout::setGeometry(const QRect &rect) { QLayout::setGeometry( rect ); if( !m_currentItem || m_timeLine->state() == QTimeLine::Running ) return; doLayout( rect ); } void SideBySideLayout::doLayout( const QRect& rect, int hOffset ) { m_currentItem->setGeometry( rect ); foreach( QLayoutItem* i, m_itemList ) { const int hUnits = m_itemList.indexOf( i ) - m_itemList.indexOf( m_currentItem ); i->setGeometry( rect.translated( ( rect.width() * hUnits ) + hOffset, 0 )); } } QSize SideBySideLayout::sizeHint() const { QSize sh; foreach( QLayoutItem* i, m_itemList ) sh = sh.expandedTo( i->sizeHint() ); return sh; } QLayoutItem* SideBySideLayout::takeAt( int index ) { if( index >= m_itemList.count() || index < 0 || m_itemList.isEmpty() ) return 0; return m_itemList.takeAt( index ); } void SideBySideLayout::moveForward() { int nextIndex = m_itemList.indexOf( m_currentItem ) + 1; if( nextIndex >= m_itemList.count() ) return; m_currentItem = m_itemList.at( nextIndex ); if( m_timeLine->state() == QTimeLine::Running && m_timeLine->direction() == QTimeLine::Backward ) m_timeLine->setDirection( QTimeLine::Forward ); else { m_timeLine->setDirection( QTimeLine::Forward ); m_timeLine->setStartFrame( geometry().width() ); m_timeLine->setEndFrame( 0 ); m_timeLine->start(); } } void SideBySideLayout::moveBackward() { int nextIndex = m_itemList.indexOf( m_currentItem ) - 1; if( nextIndex < 0 ) return; m_currentItem = m_itemList.at( nextIndex ); if( m_timeLine->state() == QTimeLine::Running && m_timeLine->direction() == QTimeLine::Forward ) m_timeLine->setDirection( QTimeLine::Backward ); else { m_timeLine->setDirection( QTimeLine::Backward ); m_timeLine->setStartFrame( 0 ); m_timeLine->setEndFrame( -geometry().width() ); m_timeLine->start(); } } void SideBySideLayout::onFrameChanged( int frame ) { doLayout( geometry(), frame ); } QWidget* SideBySideLayout::currentWidget() { return m_currentItem->widget(); } int SideBySideLayout::currentItemIndex() { return m_itemList.indexOf( m_currentItem ); } ================================================ FILE: app/boffin/layouts/SideBySideLayout.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SIDE_BY_SIDE_LAYOUT_H #define SIDE_BY_SIDE_LAYOUT_H #include /** @brief: A layout that allows 2 or more widgets to be laid out side by side * and scroll forwards and backwards with nice animation. */ /** still to implement: scrollToWidget method * handle wrapping around the last/first widgets more nicely * make sure it's robust */ class SideBySideLayout : public QLayout { Q_OBJECT public: SideBySideLayout( class QWidget* parent = 0 ); ~SideBySideLayout(); void addItem(QLayoutItem *item); Qt::Orientations expandingDirections() const; bool hasHeightForWidth() const; int count() const; QLayoutItem *itemAt(int index) const; QSize minimumSize() const; void setGeometry(const QRect &rect); QSize sizeHint() const; QLayoutItem *takeAt(int index); QWidget* currentWidget(); int currentItemIndex(); signals: void animationFinished(); public slots: void moveForward(); void moveBackward(); private slots: void onFrameChanged( int frame ); private: void doLayout( const QRect &rect, int hOffset = 0 ); QList m_itemList; QLayoutItem* m_currentItem; class QTimeLine* m_timeLine; }; #endif //SIDE_BY_SIDE_LAYOUT_H ================================================ FILE: app/boffin/main.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "App.h" #include "MainWindow.h" #include "_version.h" int main( int argc, char* argv[] ) { QCoreApplication::setApplicationName( "Boffin" ); QCoreApplication::setApplicationVersion( VERSION ); try { App app( argc, argv ); MainWindow window; window.show(); app.init( &window ); return app.exec(); } catch (int i) { return i; } } ================================================ FILE: app/boffin/playdar/BoffinPlayableItem.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "BoffinPlayableItem.h" #include "jsonGetMember.h" BoffinPlayableItemData::BoffinPlayableItemData() :size(-1) ,bitrate(-1) ,duration(-1) ,weight(0) ,score(0) ,preference(0) ,workingweight(0) ,artistId(0) { } BoffinPlayableItem::BoffinPlayableItem() { d = new BoffinPlayableItemData; } //static BoffinPlayableItem BoffinPlayableItem::fromTrackResolveResult(const QVariantMap& map) { BoffinPlayableItem result; jsonGetMember(map, "album", result.d->album); jsonGetMember(map, "artist", result.d->artist); jsonGetMember(map, "bitrate", result.d->bitrate); jsonGetMember(map, "duration", result.d->duration); jsonGetMember(map, "mimetype", result.d->mimetype); jsonGetMember(map, "preference", result.d->preference); jsonGetMember(map, "score", result.d->score); jsonGetMember(map, "source", result.d->source); jsonGetMember(map, "size", result.d->size); jsonGetMember(map, "track", result.d->track); jsonGetMember(map, "url", result.d->url); return result; } //static BoffinPlayableItem BoffinPlayableItem::fromBoffinRqlResult(const QVariantMap& map) { BoffinPlayableItem result; jsonGetMember(map, "album", result.d->album); jsonGetMember(map, "artist", result.d->artist); jsonGetMember(map, "bitrate", result.d->bitrate); jsonGetMember(map, "duration", result.d->duration); jsonGetMember(map, "mimetype", result.d->mimetype); jsonGetMember(map, "preference", result.d->preference); jsonGetMember(map, "source", result.d->source); jsonGetMember(map, "size", result.d->size); jsonGetMember(map, "track", result.d->track); jsonGetMember(map, "url", result.d->url); jsonGetMember(map, "weight", result.d->weight); return result; } ================================================ FILE: app/boffin/playdar/BoffinPlayableItem.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef BOFFIN_PLAYABLE_ITEM_H #define BOFFIN_PLAYABLE_ITEM_H #include #include #include struct BoffinPlayableItemData : QSharedData { BoffinPlayableItemData(); QString artist; QString album; QString track; QString source; QString mimetype; QString url; int size; // file size in bytes int bitrate; // nominal kb/sec of encoding int duration; // track length in seconds float weight; // boffin rql items have a weight float score; // resolved items have a score int preference; // plugin preference weighting (percentage?) float workingweight; int artistId; }; class BoffinPlayableItem { public: BoffinPlayableItem(); // we set the bar low; if it has a url we can try to play it: bool isValid() const { return d->url.length() > 0; } QString artist() const { return d->artist; } QString album() const { return d->album; } QString track() const { return d->track; } QString source() const { return d->source; } QString mimetype() const { return d->mimetype; } QString url() const { return d->url; } int size() const { return d->size; } int bitrate() const { return d->bitrate; } int duration() const { return d->duration; } float weight() const { return d->weight; } float score() const { return d->score; } int preference() const { return d->preference; } float workingweight() const { return d->workingweight; } int artistId() const { return d->artistId; } // mutable: float& workingweight() { return d->workingweight; } int& artistId() { return d->artistId; } static BoffinPlayableItem fromTrackResolveResult(const QVariantMap& map); static BoffinPlayableItem fromBoffinRqlResult(const QVariantMap& map); protected: QExplicitlySharedDataPointer d; }; #endif ================================================ FILE: app/boffin/playdar/BoffinRqlRequest.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "BoffinRqlRequest.h" #include "jsonGetMember.h" void BoffinRqlRequest::onFinished() { sender()->deleteLater(); QNetworkReply *reply = (QNetworkReply*) sender(); if (reply->error() == QNetworkReply::NoError) { QString queryId; if (getQueryId(reply->readAll(), queryId)) { if (queryId == qid()) { // all is good return; } fail("qid mismatch"); // we can't handle this } fail("bad response"); } fail(""); } void BoffinRqlRequest::issueRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api, const QString& rql, const QString& session) { QNetworkReply *reply = wam->get(QNetworkRequest(api.boffinTracks(session, qid(), rql))); if (reply) { connect(reply, SIGNAL(finished()), this, SLOT(onFinished())); emit requestMade( qid()); } else { fail("couldn't issue boffin rql request"); } } //virtual void BoffinRqlRequest::receiveResult(const QVariantMap& o) { emit playableItem( BoffinPlayableItem::fromBoffinRqlResult(o) ); } void BoffinRqlRequest::fail(const char *message) { qDebug() << message; emit error(); } ================================================ FILE: app/boffin/playdar/BoffinRqlRequest.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef BOFFIN_RQL_REQUEST_H #define BOFFIN_RQL_REQUEST_H #include #include "PlaydarApi.h" #include "CometRequest.h" #include "BoffinPlayableItem.h" class BoffinRqlRequest : public CometRequest { Q_OBJECT public: void issueRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api, const QString& rql, const QString& session); virtual void receiveResult(const QVariantMap& o); signals: void error(); void playableItem(BoffinPlayableItem item); void requestMade( const QString ); private slots: void onFinished(); private: void fail(const char* message); }; #endif ================================================ FILE: app/boffin/playdar/BoffinTagRequest.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "PlaydarApi.h" #include "BoffinTagRequest.h" #include "jsonGetMember.h" // virtual void BoffinTagRequest::issueRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api, const QString& rql, const QString& session) { QNetworkReply* reply = wam->get(QNetworkRequest(api.boffinTags(session, qid(), rql))); if (reply) { connect(reply, SIGNAL(finished()), this, SLOT(onFinished())); emit requestMade( qid()); } else { fail("couldn't issue boffin request"); } } void BoffinTagRequest::onFinished() { sender()->deleteLater(); QNetworkReply *reply = (QNetworkReply*) sender(); if (reply->error() == QNetworkReply::NoError) { QString queryId; if (getQueryId(reply->readAll(), queryId)) { if (queryId == qid()) { // all is good return; } fail("qid mismatch"); // we can't handle this } fail("bad response"); } fail(""); } // virtual void BoffinTagRequest::receiveResult(const QVariantMap& o) { QString tagName, hostName; int count; double weight; int seconds; if (jsonGetMember(o, "name", tagName) && jsonGetMember(o, "source", hostName) && jsonGetMember(o, "count", count) && jsonGetMember(o, "weight", weight) && jsonGetMember(o, "seconds", seconds)) { emit tagItem(BoffinTagItem(tagName, hostName, count, static_cast(weight), seconds)); } } void BoffinTagRequest::fail(const char* message) { qDebug() << message; emit error(); } ================================================ FILE: app/boffin/playdar/BoffinTagRequest.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef BOFFIN_REQUEST_H #define BOFFIN_REQUEST_H #include #include "PlaydarApi.h" #include "CometRequest.h" struct BoffinTagItem { BoffinTagItem() {} BoffinTagItem( const QString& name ): m_name( name ) {}; BoffinTagItem(const QString& name, const QString& host, int count, float weight, int seconds) : m_name(name) , m_host(host) , m_count(count) , m_weight(weight) , m_seconds(seconds) { } bool operator==( const BoffinTagItem& that) { return m_name == that.m_name; } bool operator <( const BoffinTagItem& that ) const { return m_count < that.m_count; } QString m_name; QString m_host; int m_count; float m_weight; float m_logWeight; float m_logCount; int m_seconds; // the total duration of tracks with this tag }; class BoffinTagRequest : public CometRequest { Q_OBJECT public: void issueRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api, const QString& rql, const QString& session); virtual void receiveResult(const QVariantMap& o); signals: void error(); void tagItem(BoffinTagItem item); void requestMade( const QString ); private slots: void onFinished(); private: void fail(const char* message); }; #endif ================================================ FILE: app/boffin/playdar/CometRequest.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "CometRequest.h" #include #include "jsonGetMember.h" CometRequest::CometRequest() :m_qid(QUuid::createUuid().toString().mid(1, 36)) { } const QString& CometRequest::qid() const { return m_qid; } bool CometRequest::getQueryId(const QByteArray& data, QString& out) { json_spirit::Value v; if (json_spirit::read(std::string(data.constData(), data.size()), v)) { std::string qid; if (jsonGetMember(v, "qid", qid)) { out = QString::fromStdString(qid); return true; } } return false; } ================================================ FILE: app/boffin/playdar/CometRequest.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef COMET_REQUEST_H #define COMET_REQUEST_H #include #include #include class CometRequest : public QObject { Q_OBJECT public: CometRequest(); const QString& qid() const; virtual void receiveResult(const QVariantMap& o) = 0; protected: bool getQueryId(const QByteArray& data, QString& out); QString m_qid; }; #endif ================================================ FILE: app/boffin/playdar/PlaydarApi.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYDAR_API_H #define PLAYDAR_API_H #include "TPlaydarApi.hpp" #include #include typedef QList< QPair > ParamList; class PlaydarApiQtPolicy { public: static void paramsAdd(ParamList& params, const QString& name, const QString& value) { params.append(QPair< QString, QString>(name, value)); } static QUrl createUrl(const QString& base, const QString& path, const ParamList& params) { QUrl url(base + path); url.setQueryItems(params); return url; } }; typedef TPlaydarApi PlaydarApi; #endif ================================================ FILE: app/boffin/playdar/PlaydarAuthRequest.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include "PlaydarAuthRequest.h" #include #include "jsonGetMember.h" PlaydarAuthRequest::PlaydarAuthRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api) :m_wam(wam) ,m_api(api) { } void PlaydarAuthRequest::start(QString applicationName) { m_applicationName = applicationName; QNetworkReply* auth1Reply = m_wam->get( QNetworkRequest( m_api.auth1(applicationName) ) ); if (auth1Reply) { connect(auth1Reply, SIGNAL(finished()), SLOT(onAuth1Finished())); } else { fail("couldn't issue auth_1 request"); } } void PlaydarAuthRequest::onAuth1Finished() { QNetworkReply *reply = (QNetworkReply*) sender(); if (reply->error() == QNetworkReply::NoError) { using namespace std; json_spirit::Value v; QByteArray ba( reply->readAll() ); if (json_spirit::read( string( ba.constData(), ba.size() ), v) ) { string formtoken; if (jsonGetMember(v, "formtoken", formtoken)) { ParamList params; QUrl url = m_api.auth2( m_applicationName, QString::fromStdString(formtoken), params ); // form encode: QByteArray form; typedef QPair Param; foreach (Param p, params) { if (form.size()) { form += "&"; } form += QUrl::toPercentEncoding( p.first ) + "=" + QUrl::toPercentEncoding( p.second ); } QNetworkReply* auth2Reply = m_wam->post(QNetworkRequest(url), form); if (auth2Reply) { connect(auth2Reply, SIGNAL(finished()), SLOT(onAuth2Finished())); return; } fail("couldn't issue auth_2 request"); } } fail("bad json in auth1 response"); } fail(""); } void PlaydarAuthRequest::onAuth2Finished() { QNetworkReply *reply = (QNetworkReply*) sender(); if (reply->error() == QNetworkReply::NoError) { using namespace std; json_spirit::Value v; QByteArray ba( reply->readAll() ); if (json_spirit::read( string( ba.constData(), ba.size() ), v) ) { string authtoken; if (jsonGetMember(v, "authtoken", authtoken)) { emit authed(QString::fromStdString(authtoken)); return; } } fail("bad json in auth2 response"); } fail(""); } void PlaydarAuthRequest::fail(const char*) { emit error(); } ================================================ FILE: app/boffin/playdar/PlaydarAuthRequest.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYDAR_AUTH_REQUEST_H #define PLAYDAR_AUTH_REQUEST_H #include "PlaydarApi.h" #include #include class PlaydarAuthRequest : public QObject { Q_OBJECT public: PlaydarAuthRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api); void start(QString applicationName); signals: void error(); void authed(QString token); private slots: void onAuth1Finished(); void onAuth2Finished(); private: void fail(const char* message); lastfm::NetworkAccessManager* m_wam; PlaydarApi m_api; QString m_applicationName; }; #endif ================================================ FILE: app/boffin/playdar/PlaydarCometRequest.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "PlaydarCometRequest.h" #include #include "../comet/CometParser.h" PlaydarCometRequest::PlaydarCometRequest() :m_parser(0) ,m_sessionId( QUuid::createUuid().toString().mid(1, 36) ) { } // returns the sessionId, empty string if request fails bool PlaydarCometRequest::issueRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api) { QNetworkReply* reply = wam->get(QNetworkRequest(api.comet(m_sessionId))); if (!reply) { return false; } m_parser = new CometParser(this); connect(m_parser, SIGNAL(haveObject(QVariantMap)), SIGNAL(receivedObject(QVariantMap))); connect(reply, SIGNAL(readyRead()), SLOT(onFirstReadyRead())); connect(reply, SIGNAL(readyRead()), SLOT(onReadyRead())); connect(reply, SIGNAL(finished()), SLOT(onFinished())); connect(reply, SIGNAL(error( QNetworkReply::NetworkError )), SIGNAL( error())); return true; } void PlaydarCometRequest::onReadyRead() { QNetworkReply* reply = (QNetworkReply*) sender(); QByteArray ba = reply->readAll(); if (!m_parser->push(ba)) { qDebug() << "json comet parse problem"; qDebug() << ba; reply->abort(); // can't recover from this onFinished(); // assuming abort() doesn't emit finished } } void PlaydarCometRequest::onFirstReadyRead() { sender()->disconnect(this, SLOT(onFirstReadyRead())); emit connected(m_sessionId); } void PlaydarCometRequest::onFinished() { sender()->deleteLater(); emit finished(); } ================================================ FILE: app/boffin/playdar/PlaydarCometRequest.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYDAR_COMET_REQUEST_H #define PLAYDAR_COMET_REQUEST_H #include "PlaydarApi.h" #include #include class CometParser; // makes a request to playdar for comet results, // emits signals as result objects arrive // class PlaydarCometRequest : public QObject { Q_OBJECT public: PlaydarCometRequest(); // returns the sessionId, empty string if request fails bool issueRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api); signals: void receivedObject(QVariantMap); void connected(QString); void finished(); void error(); private slots: void onReadyRead(); void onFirstReadyRead(); void onFinished(); private: CometParser *m_parser; QString m_sessionId; }; #endif ================================================ FILE: app/boffin/playdar/PlaydarConnection.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include "PlaydarConnection.h" #include "PlaydarStatRequest.h" #include "PlaydarAuthRequest.h" #include "PlaydarRosterRequest.h" #include "PlaydarCometRequest.h" #include "TrackResolveRequest.h" #include "BoffinRqlRequest.h" #include "BoffinTagRequest.h" #include PlaydarConnection::PlaydarConnection(lastfm::NetworkAccessManager* wam, PlaydarApi& api) : m_comet(0) , m_wam(wam) , m_api(api) , m_state(Querying) { updateText(); } void PlaydarConnection::start() { PlaydarStatRequest* stat = new PlaydarStatRequest(m_wam, m_api); connect(stat, SIGNAL(stat(QString, QString, QString, bool)), SLOT(onStat(QString, QString, QString, bool))); connect(stat, SIGNAL(error()), SLOT(onError())); stat->start(); } void PlaydarConnection::onStat(QString name, QString version, QString hostname, bool bAuthenticated) { m_name = name; m_version = version; m_hostname = hostname; m_state = bAuthenticated ? Connecting : Authorising; if (!bAuthenticated) { PlaydarAuthRequest* auth = new PlaydarAuthRequest(m_wam, m_api); connect(auth, SIGNAL(authed(QString)), SLOT(onAuth(QString))); connect(auth, SIGNAL(authed(QString)), SIGNAL(authed(QString))); connect(auth, SIGNAL(error()), SLOT(onError())); auth->start("Boffin"); } else { makeCometRequest(); } updateText(); } void PlaydarConnection::onError() { sender()->deleteLater(); switch (m_state) { case Querying : m_state = NotPresent; break; case Authorising : m_state = NotAuthorised; break; case Connecting: m_state = Connecting; break; case Connected : m_state = Querying; start(); break; default: break; } updateText(); } void PlaydarConnection::onAuth(QString authToken) { sender()->deleteLater(); m_api.setAuthToken(authToken); m_state = Connecting; updateText(); makeCometRequest(); } void PlaydarConnection::onLanRoster(const QStringList& roster) { sender()->deleteLater(); m_hostsModel.setStringList(roster); QTimer::singleShot(60 * 1000, this, SLOT(makeRosterRequest())); } void PlaydarConnection::makeRosterRequest() { PlaydarRosterRequest* req = new PlaydarRosterRequest(m_wam, m_api); connect(req, SIGNAL(roster(QStringList)), SLOT(onLanRoster(QStringList))); connect(req, SIGNAL(error()), SLOT(onError())); req->start(); } void PlaydarConnection::makeCometRequest() { m_comet = new PlaydarCometRequest(); if (m_comet->issueRequest(m_wam, m_api)) { connect(m_comet, SIGNAL(connected(QString)), SLOT(onCometConnected(QString))); connect(m_comet, SIGNAL(error()), SLOT(onError())); connect(m_comet, SIGNAL(receivedObject(QVariantMap)), SLOT(receivedCometObject(QVariantMap))); } } void PlaydarConnection::onCometConnected(const QString& sessionId) { m_cometSession = sessionId; m_state = Connected; updateText(); emit connected(); } void PlaydarConnection::updateText() { QString s; switch (m_state) { case Querying: s = "Looking for Playdar"; break; case NotPresent: s = "Playdar not available"; break; case Authorising: s = "Authorising with Playdar"; break; case NotAuthorised: s = "Couldn't authorise with Playdar"; break; case Connecting: s = "Connecting to Playdar"; break; case Connected: s = "Connected to Playdar"; break; default: s = "PlaydarConnection::updateText is broken!"; } emit changed(s); } QStringListModel* PlaydarConnection::hostsModel() { return &m_hostsModel; } TrackResolveRequest* PlaydarConnection::trackResolve(const QString& artist, const QString& album, const QString& track) { if (!m_cometSession.length()) { return 0; } TrackResolveRequest* r = new TrackResolveRequest(); connect(r, SIGNAL(requestMade(QString)), SLOT(onRequestMade(QString))); connect(r, SIGNAL(destroyed(QObject*)), SLOT(onRequestDestroyed(QObject*))); r->issueRequest(m_wam, m_api, artist, album, track, m_cometSession); return r; } BoffinRqlRequest* PlaydarConnection::boffinRql(const QString& rql) { if (!m_cometSession.length()) { return 0; } BoffinRqlRequest* r = new BoffinRqlRequest(); connect(r, SIGNAL(requestMade(QString)), SLOT(onRequestMade(QString))); connect(r, SIGNAL(destroyed(QObject*)), SLOT(onRequestDestroyed(QObject*))); r->issueRequest(m_wam, m_api, rql, m_cometSession); return r; } BoffinTagRequest* PlaydarConnection::boffinTagcloud(const QString& rql) { if (!m_cometSession.length()) { return 0; } BoffinTagRequest* r = new BoffinTagRequest(); connect(r, SIGNAL(requestMade(QString)), SLOT(onRequestMade(QString))); connect(r, SIGNAL(destroyed(QObject*)), SLOT(onRequestDestroyed(QObject*))); r->issueRequest(m_wam, m_api, rql, m_cometSession); return r; } void PlaydarConnection::onRequestMade(const QString& qid) { m_cometReqMap[qid] = (CometRequest*) sender(); } void PlaydarConnection::onRequestDestroyed(QObject* o) { m_cometReqMap.remove(((CometRequest*)o)->qid()); } void PlaydarConnection::receivedCometObject(const QVariantMap& obj) { QVariantMap::const_iterator qit = obj.find("query"); if (qit != obj.end() && qit->type() == QVariant::String) { QVariantMap::const_iterator rit = obj.find("result"); if (rit != obj.end() && rit->type() == QVariant::Map) { // obj is the right shape, find it in // the request map and emit the callback QMap::const_iterator reqIt = m_cometReqMap.find(qit->toString()); if (reqIt != m_cometReqMap.end()) { reqIt.value()->receiveResult(rit->toMap()); } else { // unknown query id. qDebug() << "warning: result for unknown query " << qit->toString() << " was discarded"; } } } } ================================================ FILE: app/boffin/playdar/PlaydarConnection.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYDAR_CONNECTION_H #define PLAYDAR_CONNECTION_H #include "PlaydarApi.h" #include #include class PlaydarCometRequest; class CometRequest; class TrackResolveRequest; class BoffinTagRequest; class BoffinRqlRequest; class PlaydarConnection : public QObject { Q_OBJECT public: PlaydarConnection(lastfm::NetworkAccessManager* wam, PlaydarApi& api); void start(); QStringListModel* hostsModel(); TrackResolveRequest* trackResolve(const QString& artist, const QString& album, const QString& track); BoffinTagRequest* boffinTagcloud(const QString& rql); BoffinRqlRequest* boffinRql(const QString& rql); signals: void changed(QString newStatusMessage); void authed(QString authtoken); void connected(); private slots: void onStat(QString name, QString version, QString hostname, bool bAuthenticated); void onAuth(QString authToken); void onLanRoster(const QStringList& roster); void onError(); void makeRosterRequest(); void onCometConnected(const QString& sessionId); void receivedCometObject(const QVariantMap&); void onRequestMade(const QString& qid); void onRequestDestroyed(QObject* o); private: void updateText(); void makeCometRequest(); QString cometSession(); enum State { Querying, NotPresent, Authorising, NotAuthorised, Connecting, Connected }; QString m_name; QString m_version; QString m_hostname; QStringListModel m_hostsModel; PlaydarCometRequest* m_comet; QString m_cometSession; QMap m_cometReqMap; lastfm::NetworkAccessManager* m_wam; PlaydarApi& m_api; State m_state; }; #endif ================================================ FILE: app/boffin/playdar/PlaydarRosterRequest.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "PlaydarRosterRequest.h" #include #include #include #include #include "jsonGetMember.h" PlaydarRosterRequest::PlaydarRosterRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api) :m_wam(wam) ,m_api(api) { } void PlaydarRosterRequest::start() { QNetworkReply* reply = m_wam->get(QNetworkRequest(m_api.lanRoster())); if (reply) { connect(reply, SIGNAL(finished()), SLOT(onFinished())); } else { fail("couldn't issue lan roster request"); } } void PlaydarRosterRequest::onFinished() { sender()->deleteLater(); QNetworkReply *reply = (QNetworkReply*) sender(); if (reply->error() == QNetworkReply::NoError) { using namespace std; json_spirit::Value v; QByteArray ba( reply->readAll() ); string sReply( ba.constData(), ba.size() ); if (json_spirit::read(sReply, v) && v.type() == json_spirit::array_type) { QStringList result; for (size_t i = 0; i < v.get_array().size(); i++) { string name; if (jsonGetMember(v.get_array()[i], "name", name)) { result.append(QString::fromStdString(name)); } } if (result.size()) { emit roster(result); } return; } fail("bad json in poll response"); } fail(""); } void PlaydarRosterRequest::fail(const char* ) { emit error(); } ================================================ FILE: app/boffin/playdar/PlaydarRosterRequest.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYDAR_ROSTER_REQUEST_H #define PLAYDAR_ROSTER_REQUEST_H #include "PlaydarApi.h" #include #include class PlaydarRosterRequest : public QObject { Q_OBJECT public: PlaydarRosterRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api); void start(); signals: void error(); void roster(const QStringList&); private slots: void onFinished(); private: void fail(const char* message); lastfm::NetworkAccessManager* m_wam; PlaydarApi m_api; }; #endif ================================================ FILE: app/boffin/playdar/PlaydarStatRequest.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include "PlaydarStatRequest.h" #include #include "jsonGetMember.h" PlaydarStatRequest::PlaydarStatRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api) :m_wam(wam) ,m_api(api) ,m_statReply(0) { } void PlaydarStatRequest::start() { delete m_statReply; m_statReply = 0; m_statReply = m_wam->get( QNetworkRequest( m_api.stat() ) ); if (m_statReply) { connect(m_statReply, SIGNAL(finished()), SLOT(onReqFinished())); connect(m_statReply, SIGNAL(error( QNetworkReply::NetworkError )), SLOT(onError( QNetworkReply::NetworkError ))); } else { fail("couldn't issue stat request"); } } void PlaydarStatRequest::onReqFinished() { QNetworkReply *reply = (QNetworkReply*) sender(); if (reply->error() == QNetworkReply::NoError) { using namespace std; json_spirit::Value v; QByteArray ba( reply->readAll() ); if (json_spirit::read( string( ba.constData(), ba.size() ), v) ) { // note: hostname is only present when authenticated is true string name, version, hostname; bool authenticated; if (jsonGetMember(v, "name", name) && jsonGetMember(v, "version", version) && jsonGetMember(v, "authenticated", authenticated) && (!authenticated || jsonGetMember(v, "hostname", hostname)) ) { emit stat(QString::fromStdString(name), QString::fromStdString(version), QString::fromStdString(hostname), authenticated); return; } } fail("bad json in poll response"); } fail(""); } void PlaydarStatRequest::onError( QNetworkReply::NetworkError code ) { //QObject* s = sender(); qDebug() << "StatRequest error: " << code << endl; } void PlaydarStatRequest::fail(const char* /*message*/) { emit error(); } ================================================ FILE: app/boffin/playdar/PlaydarStatRequest.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYDAR_STAT_REQUEST_H #define PLAYDAR_STAT_REQUEST_H #include #include #include #include "PlaydarApi.h" #include class PlaydarStatRequest : public QObject { Q_OBJECT public: PlaydarStatRequest(lastfm::NetworkAccessManager*, PlaydarApi&); void start(); signals: void error(); void stat(QString name, QString version, QString hostname, bool bAuthenticated); private slots: void onReqFinished(); void onError( QNetworkReply::NetworkError code ); private: void fail(const char* message); lastfm::NetworkAccessManager* m_wam; PlaydarApi m_api; QPointer m_statReply; }; #endif ================================================ FILE: app/boffin/playdar/TPlaydarApi.hpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TPLAYDAR_API_HPP #define TPLAYDAR_API_HPP // Policy provides: // // void paramsAdd(ParamsT& p, StringT name, StringT value) // UrlT createUrl(StringT base, StringT path, ParamsT p) template class TPlaydarApi : public Policy { public: TPlaydarApi(const StringT& baseUrl, const StringT& token) : m_baseUrl(baseUrl) , m_token(token) { } void setAuthToken(const StringT& token) { m_token = token; } UrlT apiCall(const ParamsT& params) { return makeUrl("/api/", params); } UrlT stat() { ParamsT params; paramsAdd(params, "method", "stat"); paramsAdd(params, "auth", m_token); return apiCall(params); } UrlT auth1(const StringT& applicationName) { ParamsT params; paramsAdd(params, "name", applicationName); paramsAdd(params, "website", ""); paramsAdd(params, "json", ""); return makeUrl("/auth_1/", params); } UrlT auth2(const StringT& applicationName, const StringT& formtoken, ParamsT& outPostParams) { paramsAdd(outPostParams, "name", applicationName); paramsAdd(outPostParams, "website", ""); paramsAdd(outPostParams, "formtoken", formtoken); paramsAdd(outPostParams, "json", ""); return makeUrl("/auth_2/"); } UrlT getResults(const StringT& qid) { ParamsT params; paramsAdd(params, "method", "get_results"); paramsAdd(params, "qid", qid); paramsAdd(params, "auth", m_token); return apiCall(params); } UrlT lanRoster() { return makeUrl("/lan/roster"); } // cometsession optional // qid optional UrlT trackResolve(const StringT& artist, const StringT& album, const StringT& track, const StringT& cometSession = StringT(), const StringT& qid = StringT()) { ParamsT params; paramsAdd(params, "method", "resolve"); paramsAdd(params, "artist", artist); paramsAdd(params, "album", album); paramsAdd(params, "track", track); paramsAdd(params, "auth", m_token); if (cometSession != "") { paramsAdd(params, "comet", cometSession); } if (qid != "") { paramsAdd(params, "qid", qid); } return apiCall(params); } // boffinTags to obtain tags and weights for a query // qid optional // rql optional UrlT boffinTags(const StringT& cometSession, const StringT& qid = StringT(), const StringT& rql = StringT()) { ParamsT params; paramsAdd(params, "auth", m_token); paramsAdd(params, "comet", cometSession); if (qid != "") { paramsAdd(params, "qid", qid); } return makeUrl("/boffin/tags/" + rql, params); } // boffinTracks to obtain playable items matching a query // qid optional UrlT boffinTracks(const StringT& cometSession, const StringT& qid, const StringT& rql) { ParamsT params; paramsAdd(params, "auth", m_token); paramsAdd(params, "comet", cometSession); if (qid != "") { paramsAdd(params, "qid", qid); } return makeUrl("/boffin/tracks/" + rql, params); } // boffinSummary provides file count and total play time for a query // qid optional UrlT boffinSummary(const StringT& cometSession, const StringT& qid, const StringT& rql) { ParamsT params; paramsAdd(params, "auth", m_token); paramsAdd(params, "comet", cometSession); if (qid != "") { paramsAdd(params, "qid", qid); } return makeUrl("/boffin/summary/" + rql, params); } UrlT comet(const StringT& session) { ParamsT params; paramsAdd(params, "session", session); paramsAdd(params, "auth", m_token); return makeUrl("/comet/", params); } private: UrlT makeUrl(const StringT& path, const ParamsT& params = ParamsT()) { return createUrl(m_baseUrl, path, params); } StringT m_baseUrl; StringT m_token; }; #endif ================================================ FILE: app/boffin/playdar/TrackResolveRequest.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "TrackResolveRequest.h" #include "BoffinPlayableItem.h" void TrackResolveRequest::issueRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api, const QString& artist, const QString& album, const QString& track, const QString& session) { QNetworkReply* reply = wam->get(QNetworkRequest(api.trackResolve(artist, album, track, session, qid()))); if (reply) { connect(reply, SIGNAL(finished()), this, SLOT(onFinished())); emit requestMade( qid()); } else { fail("couldn't issue boffin request"); } } //virtual void TrackResolveRequest::receiveResult(const QVariantMap& o) { emit result(BoffinPlayableItem::fromTrackResolveResult(o)); } void TrackResolveRequest::onFinished() { sender()->deleteLater(); QNetworkReply *reply = (QNetworkReply*) sender(); if (reply->error() == QNetworkReply::NoError) { QString queryId; if (getQueryId(reply->readAll(), queryId)) { if (queryId == qid()) { // all is good return; } fail("qid mismatch"); // we can't handle this } fail("bad response"); } fail(""); } void TrackResolveRequest::fail(const char* message) { qDebug() << message; emit error(); } ================================================ FILE: app/boffin/playdar/TrackResolveRequest.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TRACK_RESOLVE_REQUEST_H #define TRACK_RESOLVE_REQUEST_H #include #include "PlaydarApi.h" #include "CometRequest.h" #include "BoffinPlayableItem.h" class TrackResolveRequest : public CometRequest { Q_OBJECT public: void issueRequest(lastfm::NetworkAccessManager* wam, PlaydarApi& api, const QString& artist, const QString& album, const QString& track, const QString& session); virtual void receiveResult(const QVariantMap& o); signals: void error(); void result( BoffinPlayableItem ); void requestMade( const QString ); private slots: void onFinished(); private: void fail(const char* message); }; #endif ================================================ FILE: app/boffin/playdar/jsonGetMember.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "jsonGetMember.h" bool jsonGetMember(const QVariantMap& o, const char* key, QString& out) { QVariantMap::const_iterator it = o.find(key); if (it != o.end() && it->type() == QVariant::String) { out = it->toString(); return true; } return false; } bool jsonGetMember(const QVariantMap& o, const char* key, int& out) { QVariantMap::const_iterator it = o.find(key); if (it != o.end() && it->type() == QVariant::LongLong) { out = it->toLongLong(); return true; } return false; } bool jsonGetMember(const QVariantMap& o, const char* key, double& out) { QVariantMap::const_iterator it = o.find(key); if (it != o.end() && it->type() == QVariant::Double) { out = it->toDouble(); return true; } return false; } bool jsonGetMember(const QVariantMap& o, const char* key, float& out) { QVariantMap::const_iterator it = o.find(key); if (it != o.end() && it->type() == QVariant::Double) { out = (float) it->toDouble(); return true; } return false; } ================================================ FILE: app/boffin/playdar/jsonGetMember.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef JSON_GET_MEMBER_H #define JSON_GET_MEMBER_H // gets the named property from the json value // // returns true if it's got ok. // // todo: support nested objects, eg: "playable.result.id" // todo: support arrays, eg: "results[1].id" // for json_spirit: // ( T can be any type acceptable to json_spirit's get_value method ) // #include #include "../json_spirit/json_spirit.h" template bool jsonGetMember(const json_spirit::Value& value, const char* name, T& out) { using namespace json_spirit; if (value.type() == obj_type) { // yeah, only objects have values. BOOST_FOREACH(const Pair& pair, value.get_obj()) { if (pair.name_ == name) { out = pair.value_.get_value(); return true; } } } return false; } // for QVariantMaps: #include bool jsonGetMember(const QVariantMap& o, const char* key, QString& out); bool jsonGetMember(const QVariantMap& o, const char* key, int& out); bool jsonGetMember(const QVariantMap& o, const char* key, double& out); bool jsonGetMember(const QVariantMap& o, const char* key, float& out); #endif ================================================ FILE: app/boffin/qrc/boffin.qrc ================================================ pause.png play.png skip.png stop.png ================================================ FILE: app/boffin/sample/SampleFromDistribution.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ // this file courtesy norman and mir // Sample from EMPIRICAL (given) distribution. // There are three types of sampling here: // linear: uses a simple linear search that does not guarantee // that the number of requested samples will be found. // Use it for LARGE distributions // tree: Simple tree balanced on probability. The best solution for // small/medium distributions // justrandom: simply pull random samples #ifndef __SAMPLE_FROM_DISTRIBUTION_H #define __SAMPLE_FROM_DISTRIBUTION_H #include #include #include #include #include namespace fm { namespace last { namespace algo { // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- template < typename AccessPolicy, typename CopyPolicy > class ListBasedSampler { const CopyPolicy m_copyElement; const AccessPolicy m_accessElement; boost::mt19937 m_randomGenerator; // be very careful with this!! even if the methods are const // they are NOT thread safe due to this variable! mutable boost::uniform_01 m_uniform01Distr; public: ListBasedSampler() : m_copyElement(), m_accessElement(), m_randomGenerator(), m_uniform01Distr(m_randomGenerator) { boost::uint32_t localSeed = time(0); //static_cast( reinterpret_cast(this) % //(std::numeric_limits::max)() ); m_uniform01Distr.base().seed(localSeed); } // ----------------------------------------------------------------------------- // sample just one element from the distribution // isPDF assumes that the passed data is a probability distribution // function, and therefore the sum of all it's elements is = 1 template IT singleSample( IT first, IT last, bool isPDF = false ) const { double sum; if ( isPDF ) sum = 1; else { sum = 0; for ( IT it = first; it != last; ++it ) sum += m_accessElement(it); } double randPos = m_uniform01Distr() * sum; double summedPos = sum; IT foundIt; for ( foundIt = first; foundIt != last ; ++foundIt ) { summedPos -= m_accessElement(foundIt); if ( randPos > summedPos ) break; } if ( foundIt == last ) return first; else return foundIt; } }; // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- }}} // end of namespaces #endif // __SAMPLE_FROM_DISTRIBUTION_H ================================================ FILE: app/client/Application.cpp ================================================ /* Copyright 2005-2010 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/listener/State.h" #include "lib/listener/PlayerConnection.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/dialogs/AboutDialog.h" #include "lib/unicorn/dialogs/ShareDialog.h" #include "lib/unicorn/UnicornSession.h" #include "lib/unicorn/dialogs/TagDialog.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/widgets/UserMenu.h" #include "lib/unicorn/DesktopServices.h" #include "lib/unicorn/dialogs/UserManagerDialog.h" #ifdef Q_OS_MAC #include "lib/unicorn/notify/Notify.h" #include "CommandReciever/CommandReciever.h" #endif #include "Dialogs/LicensesDialog.h" #include "MediaDevices/DeviceScrobbler.h" #include "Services/ScrobbleService.h" #include "Services/AnalyticsService.h" #include "Widgets/PointyArrow.h" #include "Widgets/ScrobbleControls.h" #include "Widgets/MetadataWidget.h" #include "Wizard/FirstRunWizard.h" #include "Application.h" #include "MainWindow.h" #include "AudioscrobblerSettings.h" #if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) #include "Mpris2/Mpris2.h" #endif #ifdef Q_OS_WIN32 #include "windows.h" #endif using audioscrobbler::Application; #define ELLIPSIS QString::fromUtf8("…") #define CONTROL_KEY_CHAR QString::fromUtf8("⌃") #define APPLE_KEY_CHAR QString::fromUtf8("⌘") #define SKIP_LIMIT 6 #ifdef Q_WS_X11 #define AS_TRAY_ICON ":/22x22.png" #define AS_TRAY_ICON_OFF ":/lastfm_icon_22_grayscale.png" #elif defined( Q_WS_WIN ) #define AS_TRAY_ICON ":/16x16.png" #define AS_TRAY_ICON_OFF ":/lastfm_icon_16_grayscale.png" #elif defined( Q_WS_MAC ) #define AS_TRAY_ICON ":/systray_icon_rest_mac.png" #define AS_TRAY_ICON_OFF ":/mac_control_bar_as_OFF.png" #endif Application::Application(int& argc, char** argv) :unicorn::Application( "fm.last.Scrobbler", argc, argv ) , m_raiseHotKeyId( (void*)-1 ) , m_reauthenticating( false ) { setAttribute( Qt::AA_DontShowIconsInMenus ); unicorn::AppSettings appSettings; int proxyType = appSettings.value( "proxyType", 0 ).toInt(); QString proxyHost = appSettings.value( "proxyHost", "" ).toString(); QString proxyPort = appSettings.value( "proxyPort", "" ).toString(); QString proxyUsername = appSettings.value( "proxyUsername", "" ).toString(); QString proxyPassword = appSettings.value( "proxyPassword", "" ).toString(); // set this new proxy QNetworkProxy::ProxyType type = QNetworkProxy::DefaultProxy; if ( proxyType == 1 ) type = QNetworkProxy::NoProxy; else if ( proxyType == 2 ) type = QNetworkProxy::HttpProxy; else if ( proxyType == 3 ) type = QNetworkProxy::Socks5Proxy; QNetworkProxy proxy( type, proxyHost, proxyPort.toInt(), proxyUsername, proxyPassword ); lastfm::NetworkAccessManager* nam = qobject_cast( lastfm::nam() ); if ( nam ) nam->setUserProxy( proxy ); AudioscrobblerSettings settings; lastfm::ws::setScheme( settings.value( "enableSsl", false ).toBool() ? lastfm::ws::Https : lastfm::ws::Http ); } void Application::initiateLogin( bool forceWizard ) throw( StubbornUserException ) { closeAllWindows(); if( forceWizard || !unicorn::Settings().firstRunWizardCompleted() ) { setWizardRunning( true ); FirstRunWizard w; if( w.exec() != QDialog::Accepted ) { setWizardRunning( false ); throw StubbornUserException(); } setWizardRunning( false ); } //this covers the case where the last user was removed //and the main window was closed. if ( m_mw ) m_mw->show(); if ( m_tray ) { //HACK: turns out when all the windows are closed, the tray stops working //unless you call the following methods. m_tray->hide(); m_tray->show(); } } void Application::init() { // Initialise the unicorn base class first! unicorn::Application::init(); #ifdef Q_WS_X11 setWindowIcon( QIcon( ":/as.png" ) ); #endif if ( !currentSession().isValid() ) { // there won't be a current session if one wasn't created by the wizard QMap lastSession = unicorn::Session::lastSessionData(); if ( lastSession.contains( "username" ) && lastSession.contains( "sessionKey" ) ) changeSession( lastSession[ "username" ], lastSession[ "sessionKey" ] ); } initiateLogin( !currentSession().isValid() ); onSessionChanged( currentSession() ); // QNetworkDiskCache* diskCache = new QNetworkDiskCache(this); // diskCache->setCacheDirectory( lastfm::dir::cache().path() ); // lastfm::nam()->setCache( diskCache ); /// tray tray(); // this will initialise m_tray if it doesn't already exist /// tray menu QMenu* menu = new QMenu; m_tray->setContextMenu(menu); menu->addMenu( new UserMenu() )->setText( tr( "Accounts" ) ); m_show_window_action = menu->addAction( tr("Show Scrobbler")); m_show_window_action->setShortcut( Qt::CTRL + Qt::META + Qt::Key_S ); menu->addSeparator(); { m_love_action = new QAction( tr("Love"), this ); m_love_action->setIconVisibleInMenu( false ); m_love_action->setCheckable( true ); #ifdef Q_OS_WIN QIcon loveIcon; loveIcon.addFile( ":/controls_love_OFF_REST.png", QSize( 16, 16 ), QIcon::Normal, QIcon::Off ); loveIcon.addFile( ":/controls_love_ON_REST.png", QSize( 16, 16 ), QIcon::Normal, QIcon::On ); m_love_action->setIcon( loveIcon ); #endif m_love_action->setEnabled( false ); connect( m_love_action, SIGNAL(triggered(bool)), SLOT(changeLovedState(bool))); } { m_play_action = new QAction( tr( "Play" ), this ); m_play_action->setIconVisibleInMenu( false ); m_play_action->setCheckable( true ); #ifdef Q_OS_WIN QIcon playIcon; playIcon.addFile( ":/controls_pause_REST.png", QSize(), QIcon::Normal, QIcon::On ); playIcon.addFile( ":/controls_play_REST.png", QSize(), QIcon::Normal, QIcon::Off ); m_play_action->setIcon( playIcon ); #endif } { m_tag_action = new QAction( tr( "Tag" ) + ELLIPSIS, this ); m_tag_action->setIconVisibleInMenu( false ); #ifdef Q_OS_WIN m_tag_action->setIcon( QIcon( ":/controls_tag_REST.png" ) ); #endif m_tag_action->setEnabled( false ); connect( m_tag_action, SIGNAL(triggered()), SLOT(onTagTriggered())); } { m_share_action = new QAction( tr( "Share" ) + ELLIPSIS, this ); m_share_action->setIconVisibleInMenu( false ); #ifdef Q_OS_WIN m_share_action->setIcon( QIcon( ":/controls_share_REST.png" ) ); #endif m_share_action->setEnabled( false ); connect( m_share_action, SIGNAL(triggered()), SLOT(onShareTriggered())); } #ifdef Q_WS_X11 menu->addSeparator(); m_scrobble_ipod_action = menu->addAction( tr( "Scrobble iPod..." ) ); connect( m_scrobble_ipod_action, SIGNAL( triggered() ), ScrobbleService::instance().deviceScrobbler(), SLOT( onScrobbleIpodTriggered() ) ); #endif menu->addSeparator(); m_visit_profile_action = menu->addAction( tr( "Visit Last.fm profile" ) ); connect( m_visit_profile_action, SIGNAL( triggered() ), SLOT( onVisitProfileTriggered() ) ); menu->addSeparator(); m_submit_scrobbles_toggle = menu->addAction( tr("Enable Scrobbling") ); m_submit_scrobbles_toggle->setCheckable( true ); bool scrobblingOn = unicorn::UserSettings().value( "scrobblingOn", true ).toBool(); m_submit_scrobbles_toggle->setChecked( scrobblingOn ); ScrobbleService::instance().scrobbleSettingsChanged(); connect( m_submit_scrobbles_toggle, SIGNAL(toggled(bool)), SLOT(onScrobbleToggled(bool)) ); connect( this, SIGNAL(scrobbleToggled(bool)), &ScrobbleService::instance(), SLOT(scrobbleSettingsChanged()) ); menu->addSeparator(); QAction* quit = menu->addAction(tr("Quit %1").arg( applicationName())); connect(quit, SIGNAL(triggered()), SLOT(quit())); m_menuBar = new QMenuBar( 0 ); /// MainWindow m_mw = new MainWindow( m_menuBar ); m_mw->addWinThumbBarButton( m_love_action ); m_mw->addWinThumbBarButton( m_play_action ); m_toggle_window_action = new QAction( this ), SLOT( trigger()); #ifndef Q_WS_X11 AudioscrobblerSettings settings; setRaiseHotKey( settings.raiseShortcutModifiers(), settings.raiseShortcutKey() ); #endif m_tag_action->setShortcut( Qt::CTRL + Qt::Key_T ); m_share_action->setShortcut( Qt::CTRL + Qt::Key_S ); m_love_action->setShortcut( Qt::CTRL + Qt::Key_L ); // make the love buttons sychronised connect(this, SIGNAL(lovedStateChanged(bool)), m_love_action, SLOT(setChecked(bool))); // tell everyone that is interested that data about the current track has been fetched connect( m_mw, SIGNAL(trackGotInfo(XmlQuery)), SIGNAL(trackGotInfo(XmlQuery))); connect( m_mw, SIGNAL(albumGotInfo(XmlQuery)), SIGNAL(albumGotInfo(XmlQuery))); connect( m_mw, SIGNAL(artistGotInfo(XmlQuery)), SIGNAL(artistGotInfo(XmlQuery))); connect( m_mw, SIGNAL(artistGotEvents(XmlQuery)), SIGNAL(artistGotEvents(XmlQuery))); connect( m_mw, SIGNAL(trackGotTopFans(XmlQuery)), SIGNAL(trackGotTopFans(XmlQuery))); connect( m_mw, SIGNAL(trackGotTags(XmlQuery)), SIGNAL(trackGotTags(XmlQuery))); connect( m_mw, SIGNAL(finished()), SIGNAL(finished())); connect( m_mw, SIGNAL(trackGotInfo(XmlQuery)), this, SLOT(onTrackGotInfo(XmlQuery))); connect( m_show_window_action, SIGNAL( triggered()), SLOT( showWindow()), Qt::QueuedConnection ); connect( m_toggle_window_action, SIGNAL( triggered()), SLOT( toggleWindow()), Qt::QueuedConnection ); connect( this, SIGNAL(messageReceived(QStringList)), SLOT(onMessageReceived(QStringList)) ); connect( this, SIGNAL(sessionChanged(unicorn::Session)), &ScrobbleService::instance(), SLOT(onSessionChanged(unicorn::Session)) ); connect( &ScrobbleService::instance(), SIGNAL(trackStarted(lastfm::Track,lastfm::Track)), SLOT(onTrackStarted(lastfm::Track,lastfm::Track))); connect( &ScrobbleService::instance(), SIGNAL(paused(bool)), SLOT(onTrackPaused(bool))); // clicking on a system tray message should show the scrobbler connect( m_tray, SIGNAL(messageClicked()), m_show_window_action, SLOT(trigger())); // make sure cached scrobbles get submitted when the connection comes back online connect( m_icm, SIGNAL(up(QString)), &ScrobbleService::instance(), SLOT(submitCache()) ); #ifdef Q_OS_WIN32 QStringList args = arguments(); #else QStringList args = arguments().mid( 1 ); #endif emit messageReceived( args ); #ifdef Q_OS_MAC m_notify = new Notify( this ); connect( m_notify, SIGNAL(clicked()), SLOT(showWindow()) ); connect( &ScrobbleService::instance(), SIGNAL(paused()), m_notify, SLOT(paused()) ); connect( &ScrobbleService::instance(), SIGNAL(resumed()), m_notify, SLOT(resumed()) ); connect( &ScrobbleService::instance(), SIGNAL(stopped()), m_notify, SLOT(stopped()) ); new CommandReciever( this ); #endif #if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) new Mpris2( this ); #endif } QWidget* Application::mainWindow() const { return m_mw; } QSystemTrayIcon* Application::tray() { if ( !m_tray ) { m_tray = new QSystemTrayIcon(this); setTrayIcon(); #if defined(Q_OS_WIN) || defined(Q_WS_X11) connect( m_tray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT( onTrayActivated(QSystemTrayIcon::ActivationReason)) ); #endif showAs( unicorn::Settings().showAS() ); connect( this, SIGNAL( aboutToQuit()), m_tray, SLOT( hide())); } return m_tray; } void Application::setTrayIcon() { if ( m_tray ) { bool scrobblingOn = unicorn::UserSettings().value( "scrobblingOn", true ).toBool(); QIcon trayIcon( scrobblingOn ? AS_TRAY_ICON : AS_TRAY_ICON_OFF ); #ifdef Q_WS_MAC trayIcon.addFile( ":systray_icon_pressed_mac.png", QSize(), QIcon::Selected ); #endif m_tray->setIcon(trayIcon); } } void Application::showAs( bool showAs ) { m_tray->setVisible( showAs ); #ifdef Q_OS_MAC setQuitOnLastWindowClosed( false ); #else setQuitOnLastWindowClosed( !showAs && !QSystemTrayIcon::isSystemTrayAvailable() ); #endif } void Application::setRaiseHotKey( Qt::KeyboardModifiers mods, int key ) { if( m_raiseHotKeyId >= (void *)0 ) unInstallHotKey( m_raiseHotKeyId ); m_raiseHotKeyId = installHotKey( mods, key, m_toggle_window_action, SLOT(trigger())); } void Application::setBetaUpdates( bool betaUpdates ) { m_mw->setBetaUpdates( betaUpdates ); } void Application::startBootstrap( const QString& pluginId ) { if ( pluginId == "itw" || pluginId == "osx" ) m_bootstrapper = new iTunesBootstrapper( this ); else m_bootstrapper = new PluginBootstrapper( pluginId, this ); connect( m_bootstrapper, SIGNAL(done(AbstractBootstrapper::BootstrapStatus)), SIGNAL(bootstrapDone(AbstractBootstrapper::BootstrapStatus)) ); emit bootstrapStarted( pluginId ); m_bootstrapper->bootStrap(); } QString Application::currentCategory() const { return m_mw->currentCategory(); } void Application::onTrackGotInfo( const XmlQuery& lfm ) { MutableTrack( ScrobbleService::instance().currentConnection()->track() ).setFromLfm( lfm ); } void Application::onCorrected(QString /*correction*/) { onTrackStarted( ScrobbleService::instance().currentTrack(), ScrobbleService::instance().currentTrack()); } void Application::onTrackStarted( const lastfm::Track& track, const Track& oldTrack ) { disconnect( oldTrack.signalProxy(), 0, this, 0 ); if ( track != m_currentTrack ) { m_currentTrack = track; if ( ScrobbleService::instance().scrobblableTrack( m_currentTrack ) && unicorn::Settings().notifications() ) { #ifdef Q_OS_MAC m_notify->newTrack( track ); #else tray()->showMessage( track.toString(), tr("from %1").arg( track.album() ) ); #endif } } if ( unicorn::UserSettings().value( "fingerprint", true ).toBool() #if QT_VERSION >= 0x040800 && track.url().isLocalFile() #endif ) { QFileInfo trackFileInfo( track.url().toLocalFile() ); if ( trackFileInfo.exists() && trackFileInfo.isWritable() ) // this stops us fingerprinting CDs (but maybe other things) { QProcess* fpProcess = new QProcess( this ); connect( fpProcess, SIGNAL(finished(int)), fpProcess, SLOT(deleteLater()) ); QStringList arguments; arguments << "--username" << User().name(); arguments << "--filename" << track.url().toLocalFile(); arguments << "--title" << track.title(); arguments << "--album" << track.album(); arguments << "--artist" << track.artist(); #ifdef Q_OS_WIN QString fpExe = QDir( QCoreApplication::applicationDirPath() ).absoluteFilePath( "fingerprinter.exe" ); #elif defined( Q_OS_MAC ) QString fpExe = QDir( QCoreApplication::applicationDirPath() ).absoluteFilePath( "../Helpers/fingerprinter" ); #else QString fpExe = QDir( QCoreApplication::applicationDirPath() ).absoluteFilePath( "fingerprinter" ); #endif fpProcess->start( fpExe, arguments ); } } m_tray->setToolTip( track.toString() ); m_love_action->setEnabled( true ); m_tag_action->setEnabled( true ); m_share_action->setEnabled( true ); // make sure that if the love state changes we update all the buttons connect( track.signalProxy(), SIGNAL(loveToggled(bool)), SIGNAL(lovedStateChanged(bool)) ); connect( track.signalProxy(), SIGNAL(corrected(QString)), SLOT(onCorrected(QString))); } void Application::onSessionChanged( unicorn::Session& session ) { } void Application::onTrackSpooled( const Track& /*track*/ ) { } void Application::onTrackPaused( bool ) { } void Application::onTagTriggered() { TagDialog* td = new TagDialog( m_currentTrack, m_mw ); td->raise(); td->show(); td->activateWindow(); } void Application::onShareTriggered() { ShareDialog* sd = new ShareDialog( m_currentTrack, m_mw ); sd->raise(); sd->show(); sd->activateWindow(); } void Application::onVisitProfileTriggered() { unicorn::DesktopServices::openUrl( User().www() ); } void Application::onFaqTriggered() { unicorn::DesktopServices::openUrl( lastfm::UrlBuilder( "help" ).slash( "faq" ).url() ); } void Application::onForumsTriggered() { unicorn::DesktopServices::openUrl( lastfm::UrlBuilder( "forum" ).slash( "34905" ).url() ); } void Application::onTourTriggered() { FirstRunWizard w( true, m_mw ); w.exec(); } void Application::onAboutTriggered() { if ( !m_aboutDialog ) m_aboutDialog = new AboutDialog( m_mw ); m_aboutDialog->show(); } void Application::onLicensesTriggered() { if ( !m_licensesDialog ) m_licensesDialog = new LicensesDialog( m_mw ); m_licensesDialog->show(); } void Application::changeLovedState(bool loved) { MutableTrack track( m_currentTrack ); if (loved) track.love(); else track.unlove(); } void Application::onScrobbleToggled( bool scrobblingOn ) { if ( unicorn::UserSettings().value( "scrobblingOn", true ) != scrobblingOn ) { unicorn::UserSettings().setValue( "scrobblingOn", scrobblingOn ); AnalyticsService::instance().sendEvent(SETTINGS_CATEGORY, SCROBBLING_SETTINGS, scrobblingOn ? "ScrobbleTurnedOn" : "ScrobbleTurnedOff" ); } m_submit_scrobbles_toggle->setChecked( scrobblingOn ); setTrayIcon(); emit scrobbleToggled( scrobblingOn ); } void Application::onBusLovedStateChanged( bool loved ) { MutableTrack( m_currentTrack ).setLoved( loved ); } void Application::onTrayActivated( QSystemTrayIcon::ActivationReason reason ) { if( reason == QSystemTrayIcon::Context ) return; #ifdef Q_WS_WIN if( reason != QSystemTrayIcon::DoubleClick ) return; #endif m_show_window_action->trigger(); } void Application::showWindow() { m_mw->showNormal(); m_mw->setFocus(); m_mw->raise(); m_mw->activateWindow(); } void Application::toggleWindow() { if( activeWindow() ) m_mw->hide(); else showWindow(); } // lastfmlib invokes this directly, for some errors: void Application::onWsError( lastfm::ws::Error e ) { switch (e) { case lastfm::ws::InvalidSessionKey: if ( !m_reauthenticating ) { m_reauthenticating = true; QString unauthedUser = aApp->currentSession().user().name(); unicorn::Settings us; us.beginGroup( "Users" ); us.remove( unauthedUser ); us.endGroup(); us.setFirstRunWizardCompleted( false ); // Tell them that there was an error QMessageBoxBuilder( m_mw ) .setIcon( QMessageBox::Critical ) .setTitle( tr("Authentication Required") ) .setText( tr( "

    The user account %1 is no longer authenticated with Last.fm.

    " "

    Click OK to start the setup process and reauthenticate this account.

    " ).arg( unauthedUser ) ) .setButtons( QMessageBox::Ok ) .exec(); qobject_cast( qApp )->restart(); m_reauthenticating = false; } break; default: break; } } Application::Argument Application::argument( const QString& arg ) { if (arg == "--exit") return Exit; if (arg == "--twiddly") return Twiddly; if (arg == "--settings") return Settings; return ArgUnknown; } void Application::onPrefsTriggered() { m_mw->onPrefsTriggered(); } void Application::onDiagnosticsTriggered() { m_mw->onDiagnosticsTriggered(); } void Application::onMessageReceived( const QStringList& message ) { parseArguments( message ); qDebug() << "Messages: " << message; if ( !( message.contains( "--tray" ) || message.contains( "--twiddly" ) || message.contains( "--new-ipod-detected" ) || message.contains( "--ipod-detected" ) || message.contains( "--settings" ) ) ) { // raise the app m_show_window_action->trigger(); #ifdef Q_OS_WIN32 SetForegroundWindow(m_mw->winId()); #endif } } void Application::parseArguments( const QStringList& args ) { qDebug() << args; foreach ( QString const arg, args ) { switch ( argument( arg ) ) { case Exit: exit(); break; case Twiddly: ScrobbleService::instance().handleTwiddlyMessage( args ); break; case Settings: m_mw->onPrefsTriggered(); break; case ArgUnknown: break; } } } void Application::quit() { if( activeWindow() ) activeWindow()->raise(); if( unicorn::AppSettings().value( "quitDontAsk", false ).toBool()) { actuallyQuit(); return; } bool dontAsk = false; int result = 1; if( !unicorn::AppSettings().value( "quitDontAsk", false ).toBool()) result = QMessageBoxBuilder( activeWindow()).setTitle( tr("Are you sure you want to quit %1?").arg(applicationName())) .setText( tr("%1 is about to quit. Tracks played will not be scrobbled if you continue." ).arg(applicationName()) ) .dontAskAgain() .setIcon( QMessageBox::Question ) .setButtons( QMessageBox::Yes | QMessageBox::No ) .exec(&dontAsk); if( result == QMessageBox::Yes ) { unicorn::AppSettings().setValue( "quitDontAsk", dontAsk ); QCoreApplication::quit(); } } void Application::actuallyQuit() { QDialog* d = qobject_cast( sender()); if( d ) { QCheckBox* dontAskCB = d->findChild(); if( dontAskCB ) { unicorn::AppSettings().setValue( "quitDontAsk", ( dontAskCB->checkState() == Qt::Checked )); } } QCoreApplication::quit(); } ================================================ FILE: app/client/Application.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef AUDIOSCROBBLER_APPLICATION_H_ #define AUDIOSCROBBLER_APPLICATION_H_ #include #include #include #include #include #include #include "lib/unicorn/UnicornApplication.h" #include "Bootstrapper/iTunesBootstrapper.h" #include "Bootstrapper/PluginBootstrapper.h" class AboutDialog; class LicensesDialog; class MainWindow; class QAction; class ScrobbleInfoFetcher; class Drawer; class QMenuBar; class UserManagerDialog; namespace unicorn { class Notify; } using unicorn::Notify; #ifdef Q_WS_X11 class IpodDeviceLinux; #endif #if defined(aApp) #undef aApp #endif #define aApp (static_cast(QCoreApplication::instance())) namespace audioscrobbler { /** * @brief Main application logic for the audioscrobbler app. * * This class contains the core components of the application * (ie Audioscrobbler, PlayerConnection etc), top-level gui widgets and the system tray. */ class Application : public unicorn::Application { Q_OBJECT enum Argument { Exit, Twiddly, Settings, ArgUnknown }; // we delete these so QPointers QPointer m_tray; QPointer m_mw; QPointer m_menuBar; QPointer m_notify; QPointer m_userManager; QPointer m_bootstrapper; Track m_currentTrack; Track m_trackToScrobble; void* m_raiseHotKeyId; QPointer m_aboutDialog; QPointer m_licensesDialog; QAction* m_submit_scrobbles_toggle; QAction* m_love_action; QAction* m_tag_action; QAction* m_share_action; QAction* m_play_action; QAction* m_show_window_action; QAction* m_toggle_window_action; QAction* m_scrobble_ipod_action; QAction* m_visit_profile_action; QAction* m_mute_action; bool m_reauthenticating; public: Application(int& argc, char** argv); void init(); QAction* loveAction() const { return m_love_action; } QAction* tagAction() const { return m_tag_action; } QAction* shareAction() const { return m_share_action; } QAction* playAction() const { return m_play_action; } QAction* muteAction() const { return m_mute_action; } QAction* scrobbleToggleAction() const { return m_submit_scrobbles_toggle; } QSystemTrayIcon* tray(); QWidget* mainWindow() const; void setBetaUpdates( bool betaUpdates ); void setRaiseHotKey( Qt::KeyboardModifiers mods, int key ); void startBootstrap( const QString& pluginId ); void showAs( bool showAs ); QString currentCategory() const; signals: void lovedStateChanged(bool loved); // re-route all the info fetchers singals void trackGotInfo(const XmlQuery& lfm); void albumGotInfo(const XmlQuery& lfm); void artistGotInfo(const XmlQuery& lfm); void artistGotEvents(const XmlQuery& lfm); void trackGotTopFans(const XmlQuery& lfm); void trackGotTags(const XmlQuery& lfm); void finished(); void error( const QString& message ); void status( const QString& message, const QString& id ); void showMessage( const QString& message, const QString& id ); void bootstrapStarted( const QString& pluginId ); void bootstrapDone( AbstractBootstrapper::BootstrapStatus status ); void scrobbleToggled( bool on ); public slots: void showWindow(); void quit(); void actuallyQuit(); void changeLovedState(bool loved); void onBusLovedStateChanged(bool); void onTrackGotInfo(const XmlQuery& ); void parseArguments( const QStringList& args ); void onPrefsTriggered(); void onDiagnosticsTriggered(); void onScrobbleToggled( bool scrobblingOn ); protected: virtual void initiateLogin( bool forceWizard ) throw( StubbornUserException ); private: static Argument argument( const QString& arg ); private slots: void onTrayActivated(QSystemTrayIcon::ActivationReason); void onCorrected(QString correction); void onTagTriggered(); void onShareTriggered(); void onVisitProfileTriggered(); void onFaqTriggered(); void onForumsTriggered(); void onAboutTriggered(); void onTourTriggered(); void onLicensesTriggered(); void toggleWindow(); void onTrackStarted( const lastfm::Track&, const lastfm::Track& ); void onTrackPaused( bool ); void onTrackSpooled( const Track& ); void onSessionChanged( unicorn::Session& ); void onMessageReceived(const QStringList& message); void setTrayIcon(); /** all webservices connect to this and emit in the case of bad errors that * need to be handled at a higher level */ void onWsError( lastfm::ws::Error ); }; } #endif //AUDIOSCROBBER_APPLICATION_H_ ================================================ FILE: app/client/AudioscrobblerSettings.cpp ================================================ /* Copyright 2005-2011 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "AudioscrobblerSettings.h" AudioscrobblerSettings::AudioscrobblerSettings() { } Qt::KeyboardModifiers AudioscrobblerSettings::raiseShortcutModifiers() const { return (Qt::KeyboardModifiers)value( "raiseShortcutModifiers", (int)(Qt::ControlModifier | Qt::MetaModifier) ).toInt(); } int AudioscrobblerSettings::raiseShortcutKey() const { #ifdef Q_WS_MAC const int sKeyCode = 1; return value( "raiseShortcutKey", sKeyCode ).toInt(); #elif defined Q_WS_WIN const int sKeyCode = 83; return value( "raiseShortcutKey", sKeyCode ).toInt(); #endif } QString AudioscrobblerSettings::raiseShortcutDescription() const { return value( "raiseShortcutDescription", QString::fromUtf8( #ifdef Q_OS_MAC "⌃⌘ S" #else "Ctrl+Shift+S" #endif ) ).toString(); } void AudioscrobblerSettings::setRaiseShortcutKey( int key ) { setValue( "raiseShortcutKey", key ); } void AudioscrobblerSettings::setRaiseShortcutModifiers( Qt::KeyboardModifiers m ) { setValue( "raiseShortcutModifiers", (int)m ); } void AudioscrobblerSettings::setRaiseShortcutDescription( QString d ) { setValue( "raiseShortcutDescription", d ); } ================================================ FILE: app/client/AudioscrobblerSettings.h ================================================ /* Copyright 2005-2011 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef AUDIOSCROBBLER_SETTINGS_H #define AUDIOSCROBBLER_SETTINGS_H #include "lib/unicorn/UnicornSettings.h" class AudioscrobblerSettings : public unicorn::AppSettings { public: AudioscrobblerSettings(); Qt::KeyboardModifiers raiseShortcutModifiers() const; int raiseShortcutKey() const; QString raiseShortcutDescription() const; void setRaiseShortcutKey( int key ); void setRaiseShortcutModifiers( Qt::KeyboardModifiers m ); void setRaiseShortcutDescription( QString d ); }; #endif // AUDIOSCROBBLER_SETTINGS_H ================================================ FILE: app/client/Bootstrapper/AbstractBootstrapper.cpp ================================================ /*************************************************************************** * Copyright (C) 2005 - 2007 by * * Jono Cole, Last.fm Ltd * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "AbstractBootstrapper.h" #include #include #include #include #include #include #include #include #include "zlib.h" AbstractBootstrapper::AbstractBootstrapper( QObject* parent ) :QObject( parent ) { } AbstractBootstrapper::~AbstractBootstrapper(void) { } bool AbstractBootstrapper::zipFiles( const QString& inFileName, const QString& outFileName ) const { QDir temp = QDir::temp(); temp.remove( outFileName ); gzFile outFile = gzopen( outFileName.toLocal8Bit(), "wb" ); if ( !outFile ) return false; QFile inFile( inFileName ); if ( !inFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) return false; if ( gzputs( outFile, inFile.readAll().data() ) < 1 ) return false; gzclose( outFile ); inFile.close(); return true; } void AbstractBootstrapper::sendZip( const QString& inFile ) { QString username = lastfm::ws::Username; QString timestamp = QString::number( QDateTime::currentDateTime().toUTC().toTime_t() ); QMap params; params["user"] = username; params["time"] = timestamp; params["auth"] = lastfm::md5( QString( QString( lastfm::ws::SharedSecret ) + timestamp ).toUtf8() ); params["api_key"] = lastfm::ws::ApiKey; params["sk"] = lastfm::ws::SessionKey; //lastfm::ws::sign( params, true ); //params["auth"] = params.take( "api_sig" ); QUrl url( "http://bootstrap.last.fm/bootstrap/index.php" ); QMapIterator i( params ); while ( i.hasNext() ) { i.next(); QByteArray const key = QUrl::toPercentEncoding( i.key() ); QByteArray const value = QUrl::toPercentEncoding( i.value() ); url.addEncodedQueryItem( key, value ); } QFile* zipFile = new QFile( this ); zipFile->setFileName( inFile ); zipFile->open( QIODevice::ReadOnly ); QNetworkRequest request( url ); request.setRawHeader( "Content-type", "multipart/form-data, boundary=AaB03x" ); request.setRawHeader( "Cache-Control", "no-cache" ); request.setRawHeader( "Accept", "*/*" ); QByteArray bytes; bytes.append( "--AaB03x\r\n" ); bytes.append( "content-disposition: " ); bytes.append( "form-data; name=\"agency\"\r\n" ); bytes.append( "\r\n" ); bytes.append( "0\r\n" ); bytes.append( "--AaB03x\r\n" ); bytes.append( "content-disposition: " ); bytes.append( "form-data; name=\"bootstrap\"; filename=\"" + zipFile->fileName() + "\"\r\n" ); bytes.append( "Content-Transfer-Encoding: binary\r\n" ); bytes.append( "\r\n" ); bytes.append( zipFile->readAll() ); zipFile->close(); bytes.append( "\r\n" ); bytes.append( "--AaB03x--" ); request.setHeader( QNetworkRequest::ContentLengthHeader, bytes.length() ); qDebug() << "Sending " << url; emit percentageUploaded( 0 ); QNetworkReply* reply = lastfm::nam()->post( request, bytes ); connect( reply, SIGNAL(uploadProgress(qint64,qint64)), SLOT( onUploadProgress(qint64,qint64))); connect( reply, SIGNAL(finished()), SLOT(onUploadDone())); } void AbstractBootstrapper::onUploadProgress( qint64 done, qint64 total ) { emit percentageUploaded( int( float(done / total) * 100.0 ) ); } void AbstractBootstrapper::onUploadDone() { QNetworkReply* reply = qobject_cast( sender() ); qDebug() << reply->readAll(); if( reply->error() == QNetworkReply::ContentAccessDenied || reply->error() == QNetworkReply::ContentOperationNotPermittedError ) { emit done( Bootstrap_Denied ); return; } if( reply->error() != QNetworkReply::NoError ) { qDebug() << reply->errorString(); emit done( Bootstrap_UploadError ); return; } qDebug() << "Bootstrap.zip sent to last.fm!"; emit done( Bootstrap_Ok ); } ================================================ FILE: app/client/Bootstrapper/AbstractBootstrapper.h ================================================ /************************************************************************** * Copyright (C) 2005 - 2007 by * * Jono Cole, Last.fm Ltd * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef ABSTRACTBOOTSTRAPPER_H #define ABSTRACTBOOTSTRAPPER_H #include class QString; /** * @author Jono Cole * * @brief The AbstractBootstrapper class is the abstract base class of * bootstrapper implementations, providing common functionality. * * Subclasses of this class can implement the bootStrap method to perform * the bootstrapping operation and then call the zipFile / sendZip methods * to submit the bootstrap. */ class AbstractBootstrapper : public QObject { Q_OBJECT public: enum BootstrapStatus { Bootstrap_Ok = 0, Bootstrap_UploadError, Bootstrap_Denied, Bootstrap_Spam, /* eg. 1 billion plays for Bjork */ Bootstrap_Cancelled }; AbstractBootstrapper( QObject* parent = NULL ); virtual ~AbstractBootstrapper(void); bool zipFiles( const QString& inFileName, const QString& outFileName ) const; void sendZip( const QString& inFile ); virtual void bootStrap() = 0; signals: void percentageUploaded( int ); void done( AbstractBootstrapper::BootstrapStatus status ); protected slots: void onUploadDone(); void onUploadProgress( qint64 done, qint64 total ); }; #endif //ABSTRACTBOOTSTRAPPER_H ================================================ FILE: app/client/Bootstrapper/AbstractFileBootstrapper.cpp ================================================ /************************************************************************** * Copyright (C) 2005 - 2007 by * * Jono Cole, Last.fm Ltd * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "AbstractFileBootstrapper.h" #include #include #include static const int k_maxPlaysPerTrack = 10000; static const int k_maxTotalPlays = 300000; static const QString XML_VERSION = "1.0"; AbstractFileBootstrapper::AbstractFileBootstrapper( QString product, QObject* parent ) : AbstractBootstrapper( parent ), m_runningPlayCount( 0 ) { m_bootstrapElement = m_xmlDoc.createElement( "bootstrap" ); m_xmlDoc.appendChild( m_bootstrapElement ); m_bootstrapElement.setAttribute( "product", product ); m_bootstrapElement.setAttribute( "version", XML_VERSION ); m_savePath = lastfm::dir::runtimeData().path() + "/" + product + "_bootstrap.xml"; } AbstractFileBootstrapper::~AbstractFileBootstrapper(void) { } static QDomElement trackToDom( const Track& t, QDomDocument& d ) { QDomElement item = d.createElement( "item" ); QDomElement artist = d.createElement( "artist" ); QDomElement album = d.createElement( "album" ); QDomElement track = d.createElement( "track" ); QDomElement duration = d.createElement( "duration" ); QDomElement timestamp = d.createElement( "timestamp" ); QDomElement playcount = d.createElement( "playcount" ); QDomElement filename = d.createElement( "filename" ); QDomElement uniqueid = d.createElement( "uniqueID" ); artist.appendChild( d.createTextNode( t.artist())); album.appendChild( d.createTextNode( t.album())); track.appendChild( d.createTextNode( t.title())); duration.appendChild( d.createTextNode( QString::number( t.duration()))); timestamp.appendChild( d.createTextNode( QString::number( t.timestamp().toTime_t()))); playcount.appendChild( d.createTextNode( t.extra( "playcount" ))); filename.appendChild( d.createTextNode( t.url().toString())); uniqueid.appendChild( d.createTextNode( t.extra( "unique_id" ))); item.appendChild( artist ); item.appendChild( album ); item.appendChild( track ); item.appendChild( duration ); item.appendChild( timestamp ); item.appendChild( playcount ); item.appendChild( filename ); item.appendChild( uniqueid ); return item; } bool AbstractFileBootstrapper::appendTrack(Track& track) { int playCount = track.extra( "playcount" ).toInt(); m_runningPlayCount += playCount; if ( playCount > k_maxPlaysPerTrack || m_runningPlayCount > k_maxTotalPlays ) { // LOGL( 2, "Playcount for bootstrap exceeded maximum allowed. Track: " << // track.playCount() << ", total: " << m_runningPlayCount ); emit done( Bootstrap_Spam ); return false; } const QDomElement& i = trackToDom( track, m_xmlDoc ); m_bootstrapElement.appendChild( i ); return true; } void AbstractFileBootstrapper::zipAndSend() { QFile file( m_savePath ); file.open( QIODevice::WriteOnly | QIODevice::Text ); QTextStream stream( &file ); stream.setCodec( "UTF-8" ); stream << "\n"; stream << m_xmlDoc.toString(); file.close(); QString zipPath = m_savePath + ".gz"; zipFiles( m_savePath, zipPath ); sendZip( zipPath ); } ================================================ FILE: app/client/Bootstrapper/AbstractFileBootstrapper.h ================================================ /************************************************************************** * Copyright (C) 2005 - 2007 by * * Jono Cole, Last.fm Ltd * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef ABSTRACTFILEBOOTSTRAPPER_H #define ABSTRACTFILEBOOTSTRAPPER_H #include "AbstractBootstrapper.h" #include /** * @author Jono Cole * @brief AbstractFileBootstrapper is an Abstract class which provides * common functionality for bootstrappers which read library / * playcount information from the filesystem. * * Bootstrapping classes using this base class should call the appendTrack * method for each track that it has processed from the file before calling * the zipAndSend method to submit the bootstrap. */ class AbstractFileBootstrapper : public AbstractBootstrapper { Q_OBJECT public: AbstractFileBootstrapper( QString product, QObject* parent = NULL ); virtual ~AbstractFileBootstrapper( void ); protected: bool appendTrack( Track& track ); void zipAndSend(); signals: void trackProcessed( int percentDone, const Track track ); private: QDomDocument m_xmlDoc; QDomElement m_bootstrapElement; QString m_savePath; int m_runningPlayCount; }; #endif //ABSTRACTFILEBOOTSTRAPPER_H ================================================ FILE: app/client/Bootstrapper/ITunesDevice/ITunesParser.h ================================================ /*************************************************************************** * Copyright 2005 - 2008 Last.fm Ltd. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef ITUNES_PARSER_H #define ITUNES_PARSER_H #include #include /** * @brief XML Event handler for parsing the iTunes Library xml * file. * * @todo this should probably be renamed to something more appropriate. */ class ITunesParser : public QXmlContentHandler { public: ITunesParser() { m_nextIsKey = false; } Track peekTrack() { if ( m_tracks.size() ) return m_tracks.at( 0 ); else return Track(); } Track takeTrack() { if ( m_tracks.size() ) return m_tracks.takeAt( 0 ); else return Track(); } int trackCount() { return m_tracks.size(); } bool startElement( const QString & /*namespaceURI*/, const QString & localName, const QString & /*qName*/, const QXmlAttributes & /*atts*/ ) { // qDebug() << "stEl" << localName; if ( localName == "key" ) m_nextIsKey = true; else m_nextIsKey = false; return true; } bool characters ( const QString & ch ) { // qDebug() << "strings" << ch; if ( ch.trimmed().isEmpty() ) return true; if ( m_nextIsKey ) { // qDebug() << "New Key:" << ch.trimmed(); m_lastKey = ch.trimmed(); m_nextIsKey = false; } else { // qDebug() << "New Value:" << ch.trimmed(); if ( m_lastKey == "Name" ) MutableTrack( m_track ).setTitle( ch.trimmed() ); if ( m_lastKey == "Artist" ) MutableTrack( m_track ).setArtist( ch.trimmed() ); if ( m_lastKey == "Album" ) MutableTrack( m_track ).setAlbum( ch.trimmed() ); if ( m_lastKey == "Total Time" ) MutableTrack( m_track ).setDuration( ch.trimmed().toInt() / 1000 ); if ( m_lastKey == "Play Count" ) MutableTrack( m_track ).setExtra( "playcount", ch.trimmed() ); if ( m_lastKey == "Location" ) MutableTrack( m_track ).setUrl( QUrl( ch.trimmed() ) ); if ( m_lastKey == "Persistent ID" ) MutableTrack( m_track ).setExtra( "unique_id", ch.trimmed() ); if ( m_lastKey == "Play Date UTC" ) { QDateTime dt = QDateTime::fromString( ch.trimmed(), Qt::ISODate ); MutableTrack( m_track ).setTimeStamp( dt ); } } return true; } bool endElement ( const QString & /*namespaceURI*/, const QString & localName, const QString & /*qName*/ ) { // qDebug() << "enEl" << localName; if ( localName == "dict" ) { if ( !m_track.isNull() && m_track.extra( "playcount" ).toInt() > 0 ) { MutableTrack( m_track ).setSource( Track::MediaDevice ); m_tracks << m_track; } m_track = Track(); } return true; } bool endDocument () { return true; } bool endPrefixMapping ( const QString & /*prefix*/ ) { return true; } QString errorString () const { return QString(); } bool ignorableWhitespace ( const QString & /*ch*/ ) { return true; } bool processingInstruction ( const QString & /*target*/, const QString & /*data*/ ) { return true; } void setDocumentLocator ( QXmlLocator * /*locator*/ ) {} bool skippedEntity ( const QString & /*name*/ ) { return true; } bool startDocument () { return true; } bool startPrefixMapping ( const QString & /*prefix*/, const QString & /*uri*/ ) { return true; } private: QString m_lastKey; bool m_nextIsKey; Track m_track; QList m_tracks; }; #endif //ITUNES_PARSER_H ================================================ FILE: app/client/Bootstrapper/ITunesDevice/MediaDeviceInterface.h ================================================ /*************************************************************************** * Copyright (C) 2005 - 2007 by * * Christian Muehlhaeuser, Last.fm Ltd * * Erik Jaelevik, Last.fm Ltd * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef MEDIADEVICE_INTERFACE_H #define MEDIADEVICE_INTERFACE_H #include #include /** * @brief The MediaDeviceInterface is a base class for classes * representing a mediaplayer (currently only iTunes/iPod). This * code has not been properly migrated into client 2.0 yet. * */ class MediaDeviceInterface : public QObject { public: virtual ~MediaDeviceInterface() {} virtual QString LibraryPath() = 0; virtual Track firstTrack( const QString& file ) = 0; virtual Track nextTrack() = 0; virtual void forceDetection( const QString& path ) = 0; virtual void setupWatchers() = 0; signals: void deviceAdded( const QString& uid ); void deviceChangeStart( const QString& uid, QDateTime lastItunesUpdateTime ); void deviceChangeEnd( const QString& uid ); void progress( int percentage, const Track& track ); void trackChanged( const Track& track, int playCounter ); }; Q_DECLARE_INTERFACE( MediaDeviceInterface, "fm.last.MediaDevice/1.0" ) #endif ================================================ FILE: app/client/Bootstrapper/ITunesDevice/itunesdevice.cpp ================================================ /*************************************************************************** * Copyright 2005 - 2008 Last.fm Ltd. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include "itunesdevice.h" #include "ITunesParser.h" #ifdef WIN32 #include "windows.h" #include "shlobj.h" #endif QString ITunesDevice::LibraryPath() { if ( !m_iTunesLibraryPath.isEmpty() ) return m_iTunesLibraryPath; QString path; QString confPath; #ifdef Q_WS_MAC QSettings ist( "apple.com", "iTunes" ); path = ist.value( "AppleNavServices:ChooseObject:0:Path" ).toString(); path = path.remove( "file://localhost" ); qDebug() << "Found iTunes Library in:" << path; QFileInfo fi( path + "iTunes Music Library.xml" ); if ( fi.exists() ) m_iTunesLibraryPath = fi.absoluteFilePath(); else m_iTunesLibraryPath = QFileInfo( QDir::homePath() + "/Music/iTunes/iTunes Music Library.xml" ).absoluteFilePath(); return m_iTunesLibraryPath; #endif #ifdef WIN32 { // Get path to My Music char acPath[MAX_PATH]; HRESULT h = SHGetFolderPathA( NULL, CSIDL_MYMUSIC, NULL, 0, acPath ); if ( h == S_OK ) path = QString::fromLocal8Bit( acPath ); // else // LOG( 1, "Couldn't get My Music path\n" ); qDebug() << "CSIDL_MYMUSIC path: " << path; } { // Get path to Local App Data char acPath[MAX_PATH]; HRESULT h = SHGetFolderPathA( NULL, CSIDL_LOCAL_APPDATA, NULL, 0, acPath ); if ( h == S_OK ) confPath = QString::fromLocal8Bit( acPath ); // else // LOG( 1, "Couldn't get Local Application Data path\n" ); qDebug() << "CSIDL_LOCAL_APPDATA path: " << confPath; } // Try reading iTunesPrefs.xml for custom library path QFile f( confPath + "/Apple Computer/iTunes/iTunesPrefs.xml" ); if ( f.open( QIODevice::ReadOnly | QIODevice::Text ) ) { qDebug() << "Found iTunesPrefs.xml"; QByteArray content = f.readAll(); int tagStart = content.indexOf( "iTunes Library XML Location:1" ); if ( tagStart != -1 ) { // TODO: this could fail if the XML is broken int dataTagStart = content.indexOf( "", tagStart ); int dataTagEnd = dataTagStart + 6; int dataEndTagStart = content.indexOf( "", dataTagStart ); QByteArray lp = content.mid( dataTagEnd, dataEndTagStart - dataTagEnd ); qDebug() << "lp before trim: " << lp; // The file contains whitespace and linebreaks in the middle of // the data so need to squeeze all that out lp = lp.simplified(); lp = lp.replace( ' ', "" ); qDebug() << "lp after simplified: " << lp; lp = QByteArray::fromBase64( lp ); qDebug() << "lp after base64: " << lp; QString sp = QString::fromUtf16( (ushort*)lp.data() ); qDebug() << "Found iTunes Library path (after conversion to QString):" << sp; QFileInfo fi( sp ); if ( fi.exists() ) { qDebug() << "file exists, returning: " << fi.absoluteFilePath(); m_iTunesLibraryPath = fi.absoluteFilePath(); return m_iTunesLibraryPath; } } else { qDebug() << "No custom library location found in iTunesPrefs.xml"; } } // Fall back to default path otherwise m_iTunesLibraryPath = path + "/iTunes/iTunes Music Library.xml"; qDebug() << "Will use default iTunes Library path: " << m_iTunesLibraryPath; return m_iTunesLibraryPath; #endif // Fallback for testing // m_iTunesLibraryPath = "/tmp/iTunes Music Library.xml"; // return m_iTunesLibraryPath; } ITunesDevice::ITunesDevice() : m_totalSize( 0 ), m_file( 0 ), m_handler( 0 ), m_xmlReader( 0 ), m_xmlInput( 0 ) { } Track ITunesDevice::firstTrack( const QString& file ) { m_database = file; if ( !m_file ) { m_file = new QFile( file ); if ( !m_file->open( QIODevice::ReadOnly | QIODevice::Text ) ) { qDebug() << "Could not open iTunes Library" << m_database; return Track(); } m_totalSize = m_file->size(); m_xmlReader = new QXmlSimpleReader(); m_xmlInput = new QXmlInputSource(); m_handler = new ITunesParser(); m_xmlReader->setContentHandler( m_handler ); m_xmlInput->setData( m_file->read( 32768 ) ); if ( !m_xmlReader->parse( m_xmlInput, true ) ) { qDebug() << "Couldn't read file: " << m_database; return Track(); } } return nextTrack(); } Track ITunesDevice::nextTrack() { while ( m_handler->trackCount() < 20 && !m_file->atEnd() ) { m_xmlInput->setData( m_file->read( 32768 ) ); m_xmlReader->parseContinue(); emit progress( (float)( (float)m_file->pos() / (float)m_file->size() ) * 100.0, m_handler->peekTrack() ); } Track t = m_handler->takeTrack(); if ( !t.isNull() ) { return t; } if ( m_file->atEnd() ) { // Finished with the database, let's close our stuff qDebug() << "Finished reading"; m_file->close(); delete m_file; m_file = 0; } return Track(); } ================================================ FILE: app/client/Bootstrapper/ITunesDevice/itunesdevice.h ================================================ /*************************************************************************** * Copyright 2005 - 2008 Last.fm Ltd. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ /** @author Christian Muehlhaeuser * @contributor Erik Jaelevik * @contributor Max Howell */ #ifndef ITUNES_DEVICE_H #define ITUNES_DEVICE_H #include "MediaDeviceInterface.h" #include /** * @brief The ITunesDevice class parses the iTunes Music Library.xml file * and allows iterating over the Track information. * * @todo Rename this to something more descriptive. */ class ITunesDevice : public QObject { Q_OBJECT public: ITunesDevice(); QString LibraryPath(); Track firstTrack( const QString& file ); Track nextTrack(); signals: void progress( int percentage, const Track& track ); private: QString m_iTunesLibraryPath; QString m_database; int m_totalSize; class QFile* m_file; class ITunesParser* m_handler; class QXmlSimpleReader* m_xmlReader; class QXmlInputSource* m_xmlInput; }; #endif //ITUNES_DEVICE_H ================================================ FILE: app/client/Bootstrapper/PluginBootstrapper.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include "lib/unicorn/QMessageBoxBuilder.h" #include "PluginBootstrapper.h" PluginBootstrapper::PluginBootstrapper( QString pluginId, QObject* parent ) :AbstractBootstrapper( parent ), m_pluginId( pluginId ) { connect( this, SIGNAL( done( int ) ), SLOT( onUploadCompleted( int ) ) ); } void PluginBootstrapper::bootStrap() { QSettings bootstrap( QSettings::NativeFormat, QSettings::UserScope, "Last.fm", "Bootstrap", this ); bootstrap.setValue( m_pluginId, lastfm::ws::Username ); bootstrap.setValue( "data_path", lastfm::dir::runtimeData().path() ); bootstrap.setValue( "Strings/progress_label", tr("Last.fm is importing your current media library...") ); bootstrap.setValue( "Strings/complete_label", tr("Last.fm has imported your media library.\n\n Click OK to continue.") ); bootstrap.setValue( "Strings/progress_title", tr("Last.fm Library Import") ); bootstrap.setValue( "Strings/cancel_confirmation", tr("Are you sure you want to cancel the import?") ); bootstrap.setValue( "Strings/no_tracks_found", tr("Last.fm couldn't find any played tracks in your media library.\n\n Click OK to continue.") ); // start the media player QProcess* process = new QProcess( this ); QString mediaPlayer = ""; if ( m_pluginId == "wa2" ) { mediaPlayer = QString( qgetenv( "ProgramFiles(x86)" ) ).append( "/Winamp/winamp.exe" ); if ( !QFile::exists( mediaPlayer ) ) mediaPlayer = QString( qgetenv( "ProgramFiles" ) ).append( "/Winamp/winamp.exe" ); } else { mediaPlayer = QString( qgetenv( "ProgramFiles(x86)" ) ).append( "/Windows Media Player/wmplayer.exe" ); if ( !QFile::exists( mediaPlayer ) ) mediaPlayer = QString( qgetenv( "ProgramFiles" ) ).append( "/Windows Media Player/wmplayer.exe" ); } qDebug() << mediaPlayer; if ( !QFile::exists( mediaPlayer ) ) { mediaPlayer = QFileDialog::getOpenFileName( 0, m_pluginId == "wa2" ? tr( "Where is Winamp?" ) : tr( "Where is Windows Media Player?" ), QString( getenv( "ProgramFiles(x86)" ) ), m_pluginId == "wa2" ? "winamp.exe" : "wmplayer.exe" ); } qDebug() << mediaPlayer; mediaPlayer = QString( "\"%1\"" ).arg( mediaPlayer ); if ( !process->startDetached( mediaPlayer ) ) { qDebug() << process->error() << process->errorString(); // We were unable to start the bootstrap so don't tell the media // player to do the bootstrap on it's next launch anymore bootstrap.setValue( m_pluginId, "" ); emit done( Bootstrap_Cancelled ); } else { // wait for it to do its stuff QTimer::singleShot( 1000, this, SLOT(checkBootstrapped()) ); } } void PluginBootstrapper::checkBootstrapped() { // check if the file exists QString savePath = lastfm::dir::runtimeData().filePath( lastfm::ws::Username + "_" + m_pluginId + "_bootstrap.xml" ); if ( QFile::exists( savePath ) ) { // make sure winamp doesn't create the bootstrap file again QSettings bootstrap( QSettings::NativeFormat, QSettings::UserScope, "Last.fm", "Bootstrap", this ); bootstrap.remove( m_pluginId ); submitBootstrap(); } else QTimer::singleShot( 1000, this, SLOT(checkBootstrapped()) ); } void PluginBootstrapper::submitBootstrap() { QString savePath = lastfm::dir::runtimeData().filePath( lastfm::ws::Username + "_" + m_pluginId + "_bootstrap.xml" ); QString zipPath = savePath + ".gz"; zipFiles( savePath, zipPath ); sendZip( zipPath ); } void PluginBootstrapper::onUploadCompleted( int status ) { QString savePath = lastfm::dir::runtimeData().filePath( lastfm::ws::Username + "_" + m_pluginId + "_bootstrap.xml" ); QString zipPath = savePath + ".gz"; if( status == Bootstrap_Ok ) { QMessageBoxBuilder( 0 ) .setIcon( QMessageBox::Information ) .setTitle( tr("Media Library Import Complete") ) .setText( tr( "Last.fm has submitted your listening history to the server.\n" "Your profile will be updated with the new tracks in a few minutes.") ); } else if( status == Bootstrap_Denied ) { QMessageBoxBuilder( 0 ) .setIcon( QMessageBox::Warning ) .setTitle( tr("Library Import Failed") ) .setText( tr( "Sorry, Last.fm was unable to import your listening history. " "This is probably because you've already scrobbled too many tracks. " "Listening history can only be imported to brand new profiles.") ); QFile::remove( savePath ); QFile::remove( zipPath ); } } ================================================ FILE: app/client/Bootstrapper/PluginBootstrapper.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLUGIN_BOOTSTRAPPER_H_ #define PLUGIN_BOOTSTRAPPER_H_ #include "AbstractBootstrapper.h" class PluginBootstrapper : public AbstractBootstrapper { Q_OBJECT public: PluginBootstrapper( QString pluginId, QObject* parent = NULL ); void bootStrap(); void submitBootstrap(); private: QString m_pluginId; private slots: void onUploadCompleted( int status ); void checkBootstrapped(); }; #endif //PLUGIN_BOOTSTRAPPER_H_ ================================================ FILE: app/client/Bootstrapper/iTunesBootstrapper.cpp ================================================ /*************************************************************************** * Copyright (C) 2005 - 2007 by * * Jono Cole, Last.fm Ltd * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "iTunesBootstrapper.h" #include "ITunesDevice/itunesdevice.h" #include #include iTunesBootstrapper::iTunesBootstrapper( QObject* parent ) : AbstractFileBootstrapper( "iTunes", parent ) { m_iTunesDatabase = new ITunesDevice; connect( m_iTunesDatabase, SIGNAL(progress(int,Track)), SIGNAL(trackProcessed(int,Track))); } void iTunesBootstrapper::bootStrap() { Track track; if ( m_iTunesDatabase ) { qDebug() << "Reading iTunes database..."; track = m_iTunesDatabase->firstTrack( m_iTunesDatabase->LibraryPath() ); } while ( !track.isNull() ) { qApp->processEvents(); if ( !appendTrack( track ) ) { return; } track = m_iTunesDatabase->nextTrack(); } zipAndSend(); } ================================================ FILE: app/client/Bootstrapper/iTunesBootstrapper.h ================================================ /*************************************************************************** * Copyright (C) 2005 - 2007 by * * Jono Cole, Last.fm Ltd * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef ITUNES_BOOTSTRAPPER_H #define ITUNES_BOOTSTRAPPER_H #include "AbstractFileBootstrapper.h" /** * @author Jono Cole * @brief Bootstrap using information from the iTunes Music Library.xml * file. */ class iTunesBootstrapper : public AbstractFileBootstrapper { Q_OBJECT public: iTunesBootstrapper( QObject* parent = NULL ); void bootStrap(); signals: void trackProcessed( int, Track ); private: class ITunesDevice* m_iTunesDatabase; }; #endif ================================================ FILE: app/client/CommandReciever/CommandReciever.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef COMMANDRECIEVER_H #define COMMANDRECIEVER_H #include #include #include #include #include class CommandReciever : public QObject, public unicorn::UnicornApplicationDelegateCommandObserver { Q_OBJECT public: explicit CommandReciever(QObject *parent = 0); ~CommandReciever(); bool artworkDownloaded() const; QPixmap getArtwork() const; Track track() const; private: QString trackTitle() const; QString artist() const; QString album() const; int duration(); QPixmap artwork(); bool loved(); private slots: void onFinished( const class QPixmap& image ); void onTrackSpooled( const Track& track ); void onStopped(); private: QPointer m_trackImageFetcher; QPixmap m_pixmap; bool m_artworkDownloaded; }; #endif // COMMANDRECIEVER_H ================================================ FILE: app/client/CommandReciever/CommandReciever.mm ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include "../Application.h" #include "CommandReciever.h" #import @interface LastfmPlayPauseCommand: NSScriptCommand { } @end @interface LastfmNextCommand: NSScriptCommand { } @end @interface LastfmPrevCommand: NSScriptCommand { } @end @interface LastfmLoveCommand: NSScriptCommand { } @end @implementation LastfmPlayPauseCommand - (id)performDefaultImplementation { aApp->playAction()->trigger(); return nil; } @end @implementation LastfmNextCommand - (id)performDefaultImplementation { return nil; } @end @implementation LastfmPrevCommand - (id)performDefaultImplementation { // do nothing for the back button return nil; } @end @implementation LastfmLoveCommand - (id)performDefaultImplementation { aApp->loveAction()->trigger(); return nil; } @end @implementation LastfmBanCommand - (id)performDefaultImplementation { return nil; } @end @implementation NSData (LastfmAdditions) + (id)scriptingLastfmImageWithDescriptor:(NSAppleEventDescriptor *)descriptor { if ( [descriptor descriptorType] == typeType && [descriptor typeCodeValue] == 'msng' ) { return nil; } if ( [descriptor descriptorType] != typeTIFF ) { descriptor = [descriptor coerceToDescriptorType: typeTIFF]; if (descriptor == nil) { return nil; } } return [descriptor data]; } - (id)scriptingLastfmImageDescriptor { return [NSAppleEventDescriptor descriptorWithDescriptorType: typeTIFF data: self]; } @end CommandReciever::CommandReciever( QObject *parent ) :QObject( parent ), m_artworkDownloaded( false ) { bool success = QDir::home().mkdir( "Library/Application Support/Airfoil/" ); success = QDir::home().mkdir( "Library/Application Support/Airfoil/RemoteControl/" ); success = QDir::home().mkdir( "Library/Application Support/Airfoil/TrackTitles/" ); // make sure the scripts are copied success = QFile::copy( QApplication::applicationDirPath() + "/../Resources/dacp.fm.last.Scrobbler.scpt", QDir::home().filePath( "Library/Application Support/Airfoil/RemoteControl/dacp.fm.last.Scrobbler.scpt" ) ); success = QFile::copy( QApplication::applicationDirPath() + "/../Resources/fm.last.Scrobbler.scpt", QDir::home().filePath( "Library/Application Support/Airfoil/TrackTitles/fm.last.Scrobbler.scpt" ) ); aApp->delegate()->setCommandObserver( this ); } bool CommandReciever::artworkDownloaded() const { return m_artworkDownloaded; } Track CommandReciever::track() const { if ( m_trackImageFetcher ) return m_trackImageFetcher->track(); return Track(); } void CommandReciever::onTrackSpooled( const Track& track ) { // if the track has changed or stopped playing get rid of the track fetcher if ( m_trackImageFetcher ) { if ( m_trackImageFetcher->track() != track || track.isNull() ) { delete m_trackImageFetcher; m_pixmap = QPixmap(); } } // if we haven't fetched the new track image yet then do it now if ( !m_trackImageFetcher && !track.isNull() ) { m_artworkDownloaded = false; m_trackImageFetcher = new TrackImageFetcher( track, Track::MegaImage ); connect( m_trackImageFetcher, SIGNAL(finished(QPixmap)), SLOT(onFinished(QPixmap))); m_trackImageFetcher->startAlbum(); } } void CommandReciever::onStopped() { delete m_trackImageFetcher; } void CommandReciever::onFinished( const QPixmap& pixmap ) { m_artworkDownloaded = true; m_pixmap = pixmap; } QPixmap CommandReciever::getArtwork() const { return m_pixmap; } QString CommandReciever::trackTitle() const { Track t = track(); QString string; if ( !t.isNull() && artworkDownloaded() ) string = t.title(); return string; } QString CommandReciever::artist() const { Track t = track(); QString string; if ( !t.isNull() && artworkDownloaded() ) string = t.artist(); return string; } QString CommandReciever::album() const { Track t = track(); QString string; if ( !t.isNull() && artworkDownloaded() ) string = t.album(); return string; } int CommandReciever::duration() { Track t = track(); if ( !t.isNull() && artworkDownloaded() ) return t.duration(); return 0; } QPixmap CommandReciever::artwork() { Track t = track(); if ( !t.isNull() ) { QPixmap pixmap = getArtwork(); if ( !pixmap.isNull() && artworkDownloaded() ) return pixmap; } return QPixmap(); } bool CommandReciever::loved() { Track t = track(); if ( !t.isNull() && artworkDownloaded() ) return t.isLoved(); return false; } CommandReciever::~CommandReciever() { } ================================================ FILE: app/client/Dialogs/BetaDialog.cpp ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include "lib/unicorn/DesktopServices.h" #include "../Application.h" #include "BetaDialog.h" #include "ui_BetaDialog.h" BetaDialog::BetaDialog(QWidget *parent) : QDialog(parent), ui(new Ui::BetaDialog) { ui->setupUi(this); setAttribute( Qt::WA_DeleteOnClose ); ui->description->setText( "This is a beta version of the new Last.fm Desktop App. Please be gentle!" ); if ( aApp->currentSession().user().type() == lastfm::User::TypeStaff ) { ui->feedback->setText( "If you've' noticed a problem, create a Jira issue and we'll fix it immediately. Thanks!" ); } else { ui->createIssue->setText( tr( "Visit Audioscrobbler Beta group" ) ); ui->feedback->setText( "If you've noticed a problem, please visit our group and leave us some feedback. Thanks!" ); } connect( ui->createIssue, SIGNAL(clicked()), SLOT(createIssue()) ); } void BetaDialog::createIssue() { if ( aApp->currentSession().user().type() == lastfm::User::TypeStaff ) unicorn::DesktopServices::openUrl( QUrl( "https://jira.last.fm/secure/CreateIssue.jspa?pid=10011&issuetype=1&Create=Create" ) ); else unicorn::DesktopServices::openUrl( QUrl( "http://www.last.fm/group/Audioscrobbler+Beta" ) ); } BetaDialog::~BetaDialog() { delete ui; } ================================================ FILE: app/client/Dialogs/BetaDialog.h ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef BETADIALOG_H #define BETADIALOG_H #include namespace Ui { class BetaDialog; } class BetaDialog : public QDialog { Q_OBJECT public: explicit BetaDialog(QWidget *parent = 0); ~BetaDialog(); private slots: void createIssue(); private: Ui::BetaDialog *ui; }; #endif // BETADIALOG_H ================================================ FILE: app/client/Dialogs/BetaDialog.ui ================================================ BetaDialog 0 0 352 290 Beta TextLabel true TextLabel true Create Issue Qt::Vertical 20 0 ================================================ FILE: app/client/Dialogs/DiagnosticsDialog.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "lib/unicorn/UnicornCoreApplication.h" #include "ui_DiagnosticsDialog.h" #include "DiagnosticsDialog.h" #include "../Services/ScrobbleService/ScrobbleService.h" #include "../MediaDevices/DeviceScrobbler.h" #include "common/c++/Logger.h" #include #include #include #include #include #include #include #include DiagnosticsDialog::DiagnosticsDialog( QWidget *parent ) : QDialog( parent ), ui( new Ui::DiagnosticsDialog ), m_ipod_log( 0 ) { ui->setupUi( this ); setAttribute( Qt::WA_DeleteOnClose ); ui->cached->header()->setResizeMode( QHeaderView::Stretch ); ui->fingerprints->header()->setResizeMode( QHeaderView::Stretch ); m_delay = new DelayedLabelText( ui->subs_status ); ui->tabs->removeTab( 0 ); ui->tabs->removeTab( 0 ); #ifdef Q_WS_X11 ui->tabs->removeTab( 0 ); #endif #ifdef Q_OS_MAC ui->subs_status->setAttribute( Qt::WA_MacSmallSize ); ui->subs_cache_count->setAttribute( Qt::WA_MacSmallSize ); ui->fingerprints_title->setAttribute( Qt::WA_MacSmallSize ); ui->cached->setAttribute( Qt::WA_MacSmallSize ); ui->cached->setAttribute( Qt::WA_MacShowFocusRect, false ); ui->fingerprints->setAttribute( Qt::WA_MacSmallSize ); ui->fingerprints->setAttribute( Qt::WA_MacShowFocusRect, false ); QFont f = ui->ipod_log->font(); f.setPixelSize( 10 ); ui->ipod_log->setFont( f ); #endif connect( qApp, SIGNAL(scrobblePointReached( Track )), SLOT(onScrobblePointReached()), Qt::QueuedConnection ); // queued because otherwise cache isn't filled yet connect( ui->ipod_scrobble_button, SIGNAL(clicked()), SLOT(onScrobbleIPodClicked()) ); connect( ui->logs_button, SIGNAL(clicked()), SLOT(onSendLogsClicked()) ); onScrobblePointReached(); #ifndef Q_WS_X11 QString path = unicorn::CoreApplication::log( "iPodScrobbler" ).absoluteFilePath(); // we seek to the end below, but then twiddly's logger pretruncates the file // which then means our seeked position is beyond the file's end, and we // thus don't show any log output #ifdef WIN32 Logger::truncate( (wchar_t*) path.utf16() ); #else QByteArray const cpath = QFile::encodeName( path ); Logger::truncate( cpath.data() ); #endif m_ipod_log = new QFile( path, this ); m_ipod_log->open( QIODevice::ReadOnly ); m_ipod_log->seek( m_ipod_log->size() ); ui->ipod_log->clear(); QTimer* timer = new QTimer( this ); timer->setInterval( 10 ); connect( timer, SIGNAL(timeout()), SLOT(poll()) ); timer->start(); #endif } DiagnosticsDialog::~DiagnosticsDialog() { delete ui; } /* static QString scrobblerStatusText( int const i ) { using lastfm::Audioscrobbler; #define tr QObject::tr switch (i) { case Audioscrobbler::ErrorBadSession: return tr( "Your session expired, it is being renewed." ); case Audioscrobbler::ErrorBannedClientVersion: return tr( "Your client too old, you must upgrade." ); case Audioscrobbler::ErrorInvalidSessionKey: return tr( "Your username or password is incorrect" ); case Audioscrobbler::ErrorBadTime: return tr( "Your timezone or date are incorrect" ); case Audioscrobbler::ErrorThreeHardFailures: return tr( "The submissions server is down" ); case Audioscrobbler::Connecting: return tr( "Connecting to Last.fm..." ); case Audioscrobbler::Scrobbling: return tr( "Scrobbling..." ); case Audioscrobbler::TracksScrobbled: case Audioscrobbler::Handshaken: return tr( "Ready" ); } #undef tr return ""; } */ void DiagnosticsDialog::scrobbleActivity( int /*msg*/ ) { /* m_delay->add( scrobblerStatusText( msg ) ); if (msg == Audioscrobbler::TracksScrobbled) QTimer::singleShot( 1000, this, SLOT(onScrobblePointReached()) ); switch (msg) { case Audioscrobbler::ErrorBadSession: //TODO flashing case Audioscrobbler::Connecting: //NOTE we only get this on startup ui->subs_light->setColor( Qt::yellow ); break; case Audioscrobbler::Handshaken: case Audioscrobbler::Scrobbling: case Audioscrobbler::TracksScrobbled: ui->subs_light->setColor( Qt::green ); break; case Audioscrobbler::ErrorBannedClientVersion: case Audioscrobbler::ErrorInvalidSessionKey: case Audioscrobbler::ErrorBadTime: case Audioscrobbler::ErrorThreeHardFailures: ui->subs_light->setColor( Qt::red ); break; } */ } void DiagnosticsDialog::onScrobblePointReached() { ScrobbleCache cache( lastfm::ws::Username ); QList items; foreach (Track t, cache.tracks()) items.append( new QTreeWidgetItem( QStringList() << t.artist() << t.title() << t.album() ) ); ui->cached->clear(); ui->cached->insertTopLevelItems( 0, items ); if (items.count()) ui->subs_cache_count->setText( tr("%n locally cached track(s)", "", items.count() ) ); else ui->subs_cache_count->clear(); } void DiagnosticsDialog::fingerprinted( const Track& t ) { new QTreeWidgetItem( ui->fingerprints, QStringList() << t.artist() << t.title() << t.album() ); } void DiagnosticsDialog::poll() { QTextStream s( m_ipod_log ); while (!s.atEnd()) ui->ipod_log->appendPlainText( s.readLine() ); } void DiagnosticsDialog::onScrobbleIPodClicked() { bool const isManual = ( ui->ipod_type->currentIndex() == 1 ); DeviceScrobbler::DoTwiddlyResult doTwiddlyResult = ScrobbleService::instance().deviceScrobbler()->doTwiddle( isManual ); switch ( doTwiddlyResult ) { case DeviceScrobbler::AlreadyRunning: ui->ipod_log->appendPlainText( "ALREADY SCROBBLING IPOD..." ); break; case DeviceScrobbler::ITunesNotRunning: ui->ipod_log->appendPlainText( "ITUNES NOT RUNNING!" ); break; case DeviceScrobbler::ITunesPluginNotInstalled: ui->ipod_log->appendPlainText( "ITUNES PLUGIN NOT INSTALLED!" ); break; default: // don't say anything; it was DoTwiddlyResult::Started break; } } void DiagnosticsDialog::onSendLogsClicked() { //TODO SendLogsDialog( this ).exec(); } ================================================ FILE: app/client/Dialogs/DiagnosticsDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef DIAGNOSTICS_DIALOG_H #define DIAGNOSTICS_DIALOG_H #include #include #include #include #include #include //Qt enums #include namespace Ui { class DiagnosticsDialog; } class DiagnosticsDialog : public QDialog { Q_OBJECT public: DiagnosticsDialog( QWidget *parent = 0 ); ~DiagnosticsDialog(); public slots: void fingerprinted( const Track& ); void scrobbleActivity( int ); private slots: void onScrobblePointReached(); private: void scrobbleIPod( bool isManual = false ); QString diagnosticInformation(); private slots: void onScrobbleIPodClicked(); void onSendLogsClicked(); void poll(); private: Ui::DiagnosticsDialog* ui; class DelayedLabelText* m_delay; QFile* m_ipod_log; }; #include class DelayedLabelText : public QObject { Q_OBJECT QList texts; QTimer m_timer; public: DelayedLabelText( QLabel* parent ) : QObject( parent ) { m_timer.setInterval( 2000 ); connect( &m_timer, SIGNAL(timeout()), SLOT(timeout()) ); } void add( QString text ) { QLabel* label = static_cast(parent()); if (m_timer.isActive()) { if (texts.isEmpty() || texts.last() != text) texts += text; return; } label->setText( text ); m_timer.start(); } private slots: void timeout() { if (texts.size()) static_cast(parent())->setText( texts.takeFirst() ); if (texts.isEmpty()) m_timer.stop(); } }; #endif //DIAGNOSTICS_DIALOG_H ================================================ FILE: app/client/Dialogs/DiagnosticsDialog.ui ================================================ DiagnosticsDialog 0 0 502 485 Diagnostics 0 0 false 0 Scrobbling 5 -1 0 0 This is an easter egg! Qt::Horizontal 20 2 false 0 0 false true false true 3 Artist Track Album Fingerprinting 75 true Recently Fingerprinted Tracks 0 0 false true false true 3 Artist Track Album iPod Scrobbling 0 0 iTunes automatically manages my iPod I manually manage my iPod Scrobble iPod 300 Courier New true 0 Logs false <a href='http://status.last.fm'>http://status.last.fm</a> Qt::RichText true Qt::Horizontal QSizePolicy::Expanding 0 0 &Close true StatusLight QWidget
    lib/unicorn/widgets/StatusLight.h
    1
    close_button clicked() DiagnosticsDialog accept() 471 476 502 336
    ================================================ FILE: app/client/Dialogs/LicensesDialog.cpp ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "lib/unicorn/widgets/Label.h" #include "LicensesDialog.h" #include "ui_LicensesDialog.h" LicensesDialog::LicensesDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LicensesDialog) { ui->setupUi(this); setAttribute( Qt::WA_DeleteOnClose ); QString licenseText; licenseText.append( "
    " ); licenseText.append( "

    Third-Party Licenses

    " ); licenseText.append( "

    The Last.fm Desktop App wouldn't be such a thing of beauty without the frameworks and software listed below. A big shoutout from everyone at Last.HQ, and a tip-of-the-hat to all the developers involved in bringing these great open source projects to life.

    " ); // Qt licenseText.append( "
    " ); licenseText.append( "

    Qt

    " ); licenseText.append( "
      " ); licenseText.append( "
    • " + unicorn::Label::anchor( "http://qt.nokia.com", "Qt" ) + "
    • "); licenseText.append( "
    • " + unicorn::Label::anchor( "http://qt.nokia.com/products/licensing/", "Qt License" ) + "
    • "); licenseText.append( "
    • " + unicorn::Label::anchor( "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html", "GNU Lesser General Public License (LGPL) version 2.1" ) + "
    • "); licenseText.append( "
    " ); #ifdef Q_OS_MAC // Sparkle (updating framework) licenseText.append( "
    " ); licenseText.append( "

    Sparkle

    " ); licenseText.append( "
      " ); licenseText.append( "
    • " + unicorn::Label::anchor( "http://sparkle.andymatuschak.org/", "Sparkle" ) + "
    • "); licenseText.append( "
    • " + unicorn::Label::anchor( "https://github.com/andymatuschak/Sparkle/blob/master/License.txt", "Sparkle License" ) + "
    • "); licenseText.append( "
    " ); licenseText.append( "

    Copyright (c) 2006 Andy Matuschak

    " ); licenseText.append( "

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

    " ); licenseText.append( "

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    " ); licenseText.append( "

    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    " ); #endif #ifdef Q_OS_WIN // Winsparkle licenseText.append( "
    " ); licenseText.append( "

    winsparkle

    " ); licenseText.append( "
      " ); licenseText.append( "
    • " + unicorn::Label::anchor( "https://github.com/vslavik/winsparkle/blob/master/COPYING", "Winsparkle License" ) + "
    • " ); licenseText.append( "
    " ); licenseText.append( "

    Copyright (c) 2009-2010 Vaclav Slavik

    " ); licenseText.append( "

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

    " ); licenseText.append( "

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    " ); licenseText.append( "

    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    " ); #endif #ifdef Q_OS_MAC // Growl licenseText.append( "
    " ); licenseText.append( "

    Growl

    " ); licenseText.append( "
      " ); licenseText.append( "
    • " + unicorn::Label::anchor( "http://growl.info", "Growl" ) + "
    • " ); licenseText.append( "
    • " + unicorn::Label::anchor( "http://growl.info/documentation/developer/bsd-license.txt", "Growl License" ) + "
    • " ); licenseText.append( "
    " ); licenseText.append( "

    Copyright (c) The Growl Project, 2004

    " ); licenseText.append( "

    All rights reserved.

    " ); licenseText.append( "

    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

    " ); licenseText.append( "
      " ); licenseText.append( "
    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    2. " ); licenseText.append( "
    3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    4. " ); licenseText.append( "
    5. Neither the name of Growl nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    6. " ); licenseText.append( "
    " ); licenseText.append( "

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    " ); #endif // Breakpad licenseText.append( "
    " ); licenseText.append( "

    Breakpad

    " ); licenseText.append( "
      " ); licenseText.append( "
    • " + unicorn::Label::anchor( "http://code.google.com/p/google-breakpad/", "Breakpad" ) + "
    • " ); licenseText.append( "
    • " + unicorn::Label::anchor( "http://code.google.com/p/google-breakpad/source/browse/trunk/COPYING", "Breakpad License" ) + "
    • " ); licenseText.append( "
    " ); licenseText.append( "

    Copyright (c) 2006, Google Inc.

    " ); licenseText.append( "

    All rights reserved.

    " ); licenseText.append( "

    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

    " ); licenseText.append( "
      " ); licenseText.append( "
    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    2. " ); licenseText.append( "
    3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    4. " ); licenseText.append( "
    5. Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    6. " ); licenseText.append( "
    " ); licenseText.append( "

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    " ); licenseText.append( "
    " ); licenseText = QString( "%2" ).arg( QColor( 0xd71005 ).name(), licenseText ); ui->textBrowser->setText( licenseText ); } LicensesDialog::~LicensesDialog() { delete ui; } ================================================ FILE: app/client/Dialogs/LicensesDialog.h ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef LICENCEDIALOG_H #define LICENCEDIALOG_H #include namespace Ui { class LicensesDialog; } class LicensesDialog : public QDialog { Q_OBJECT public: explicit LicensesDialog(QWidget *parent = 0); ~LicensesDialog(); private: Ui::LicensesDialog *ui; }; #endif // LICENCEDIALOG_H ================================================ FILE: app/client/Dialogs/LicensesDialog.ui ================================================ LicensesDialog 0 0 480 503 Licenses 0 0 QFrame::NoFrame true ================================================ FILE: app/client/Fingerprinter/AacSource.cpp ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp Portions Copyright 2003-2005 M. Bakker, Nero AG, http://www.nero.com - Adapted from main.c found in the FAAD2 source tarball. This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #include "AacSource.h" #include "AacSource_p.h" #include #include #include #include #include #include #include #include //////////////////////////////////////////////////////////////////////// // // AAC_File // //////////////////////////////////////////////////////////////////////// AAC_File::AAC_File(const QString& fileName, int headerType) : m_fileName(fileName) , m_inBuf(NULL) , m_inBufSize(0) , m_decoder(0) , m_overflow(static_cast(malloc( sizeof(unsigned char) * 1024 ))) , m_overflowSize(0) , m_header(headerType) { } AAC_File::~AAC_File() { // common if ( m_decoder ) { NeAACDecClose( m_decoder ); m_decoder = NULL; } if ( m_inBuf ) { free( m_inBuf ); m_inBufSize = 0; m_inBuf = NULL; } if ( m_overflow ) { free( m_overflow ); m_overflowSize = 0; m_overflow = NULL; } } //////////////////////////////////////////////////////////////////////// // // AAC with ADTS or ADIF headers // //////////////////////////////////////////////////////////////////////// #define MAX_CHANNELS 6 // Output will get mixed down to 2 channels #define ADTS_HEADER_SIZE 8 static int adts_sample_rates[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0, 0 }; AAC_ADTS_File::AAC_ADTS_File( const QString& fileName, int headerType ) : AAC_File(fileName, headerType) , m_file( NULL ) , m_adifSamplerate( 0 ) , m_adifChannels( 0 ) { } AAC_ADTS_File::~AAC_ADTS_File() { if ( m_file ) { fclose( m_file ); } } void AAC_ADTS_File::fillBuffer( FILE*& fp, unsigned char*& buf, size_t& bufSize, const size_t bytesConsumed ) { size_t bread; if ( bytesConsumed > 0 ) { if ( bufSize ) memmove( (void*)buf, (void*)(buf + bytesConsumed), bufSize*sizeof(unsigned char) ); bread = fread( (void*)(buf + bufSize), 1, bytesConsumed, fp ); bufSize += bread; if ( bufSize > 3 ) { if ( memcmp( buf, "TAG", 3 ) == 0 ) bufSize = 0; } if ( bufSize > 11 ) { if ( memcmp( buf, "LYRICSBEGIN", 11 ) == 0 ) bufSize = 0; } if ( bufSize > 8 ) { if ( memcmp( buf, "APETAGEX", 8 ) == 0 ) bufSize = 0; } } } void AAC_ADTS_File::parse( FILE*& fp, unsigned char*& buf, size_t& bufSize, int &bitrate, double &length ) { unsigned int frames, frame_length = 0; int t_framelength = 0; int samplerate = 0; double frames_per_sec, bytes_per_frame; // Read all frames to ensure correct time and bitrate for ( frames = 0; /* */; frames++ ) { fillBuffer( fp, buf, bufSize, frame_length ); if ( bufSize > 7 ) { /* check syncword */ if ( !( (buf[0] == 0xFF) && ((buf[1] & 0xF6) == 0xF0) ) ) break; if ( frames == 0 ) samplerate = adts_sample_rates[ (buf[2] & 0x3c) >> 2 ]; frame_length = ( ((buf[3] & 0x3) << 11) | ((buf[4]) << 3) | (buf[5] >> 5) ); t_framelength += frame_length - ADTS_HEADER_SIZE; if ( frame_length > bufSize ) break; bufSize -= frame_length; } else { break; } } frames_per_sec = samplerate / 1024.0; if ( frames != 0 ) bytes_per_frame = t_framelength / frames; else bytes_per_frame = 0; bitrate = static_cast(8 * bytes_per_frame * frames_per_sec + 0.5); if ( frames_per_sec != 0 ) length = frames / frames_per_sec; else length = 1; } int32_t AAC_ADTS_File::commonSetup( FILE*& fp, NeAACDecHandle& decoder, unsigned char*& buf, size_t& bufSize, unsigned long& samplerate, unsigned char& channels ) { samplerate = 0; channels = 0; fp = fopen(QFile::encodeName(m_fileName), "rb" ); if( !fp ) { std::cerr << "ERROR: Failed to open " << strerror( errno ) << std::endl; return -1; } if ( !(buf = static_cast( malloc(FAAD_MIN_STREAMSIZE*MAX_CHANNELS)) ) ) { std::cerr << "Memory allocation error" << std::endl; fclose ( fp ); return -1; } memset( buf, 0, FAAD_MIN_STREAMSIZE*MAX_CHANNELS ); bufSize = fread( buf, 1, FAAD_MIN_STREAMSIZE * MAX_CHANNELS, fp ); int tagsize = 0; if ( !memcmp( buf, "ID3", 3 ) ) { /* high bit is not used */ tagsize = (buf[6] << 21) | (buf[7] << 14) | (buf[8] << 7) | (buf[9] << 0); tagsize += 10; bufSize -= tagsize; fillBuffer( fp, buf, bufSize, tagsize ); } decoder = NeAACDecOpen(); /* Set configuration */ NeAACDecConfigurationPtr config; config = NeAACDecGetCurrentConfiguration(decoder); config->outputFormat = FAAD_FMT_16BIT; config->downMatrix = 1; // Turn 5.1 channels into 2 NeAACDecSetConfiguration( decoder, config); int32_t initval = 0; if ((initval = NeAACDecInit(decoder, buf, FAAD_MIN_STREAMSIZE*MAX_CHANNELS, &samplerate, &channels)) < 0) { std::cerr << "Error: could not set up AAC decoder" << std::endl; if ( buf ) free( buf ); buf = NULL; NeAACDecClose( decoder ); decoder = NULL; fclose( fp ); fp = NULL; } return initval; } bool AAC_ADTS_File::init() { unsigned long initSamplerate = 0; unsigned char initChannels = 0; int32_t initval = commonSetup( m_file, m_decoder, m_inBuf, m_inBufSize, initSamplerate, initChannels ); if ( initval >= 0 ) { m_inBufSize -= initval; fillBuffer( m_file, m_inBuf, m_inBufSize, initval ); // These two only needed for skipping AAC ADIF files m_adifSamplerate = initSamplerate; m_adifChannels = initChannels; return true; } throw std::runtime_error( "ERROR: Could not initialize AAC file reader!" ); return false; } /*QString AAC_ADTS_File::getMbid() { char out[MBID_BUFFER_SIZE]; int const r = getMP3_MBID(QFile::encodeName(m_fileName), out); if ( r == 0 ) return QString::fromLatin1( out ); return QString(); }*/ void AAC_ADTS_File::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) { long fileread; unsigned long initSamplerate; unsigned char initChannels; double initLength = 0; unsigned char *tempBuf = NULL; size_t tempBufSize; FILE *fp = NULL; NeAACDecHandle decoder = NULL; commonSetup( fp, decoder, tempBuf, tempBufSize, initSamplerate, initChannels ); long origpos = ftell( fp ); fseek( fp, 0, SEEK_END ); fileread = ftell( fp ); fseek( fp, origpos, SEEK_SET ); if ( (tempBuf[0] == 0xFF) && ((tempBuf[1] & 0xF6) == 0xF0) ) { parse( fp, tempBuf, tempBufSize, bitrate, initLength ); } else if (memcmp(tempBuf, "ADIF", 4) == 0) { int skip_size = (tempBuf[4] & 0x80) ? 9 : 0; bitrate = ((tempBuf[4 + skip_size] & 0x0F)<<19) | (tempBuf[5 + skip_size]<<11) | (tempBuf[6 + skip_size]<<3) | (tempBuf[7 + skip_size] & 0xE0); if ( fileread != 0) { initLength = static_cast(fileread) * 8 / bitrate + 0.5; } } lengthSecs = static_cast(initLength); nchannels = initChannels; samplerate = initSamplerate; if ( decoder ) NeAACDecClose( decoder ); if ( fp ) fclose( fp ); if ( tempBuf ) free( tempBuf ); } void AAC_ADTS_File::skip( const int mSecs ) { if ( m_header == AAC_ADTS ) { // As AAC is VBR we need to check all ADTS headers to enable seeking... // There is no other solution unsigned char header[8]; unsigned int frameCount, frameLength; double seconds = 0; // We need to find the ATDS syncword so rewind to the beginning // of the unprocessed data. if ( m_inBufSize > 0 ) { fseek ( m_file, -m_inBufSize, SEEK_CUR ); m_inBufSize = 0; } for( frameCount = 1; seconds * 1000 < mSecs; frameCount++ ) { if ( fread( header, 1, ADTS_HEADER_SIZE, m_file ) != ADTS_HEADER_SIZE ) { break; } if ( !strncmp( (char*)header, "ID3", 3 ) ) { // high bit is not used unsigned char rest[2]; fread( rest, 1, 2, m_file ); int tagsize = (header[6] << 21) | (header[7] << 14) | (rest[0] << 7) | (rest[1] << 0); fseek( m_file, tagsize, SEEK_CUR ); fread( header, 1, ADTS_HEADER_SIZE, m_file ); } if ( !((header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0)) ) { std::cerr << "Error: Bad frame header; file may be corrupt!" << std::endl; break; } int samplerate = adts_sample_rates[ (header[2] & 0x3c) >> 2 ]; frameLength = ( ( header[3] & 0x3 ) << 11 ) | ( header[4] << 3 ) | ( header[5] >> 5 ); if ( samplerate > 0 ) seconds += 1024.0 / samplerate; else { std::cerr << "Error: Bad frame header; file may be corrupt!" << std::endl; break; } if ( fseek( m_file, frameLength - ADTS_HEADER_SIZE, SEEK_CUR ) == -1 ) break; } m_inBufSize = fread( m_inBuf, 1, FAAD_MIN_STREAMSIZE * MAX_CHANNELS, m_file ); } else if ( m_header == AAC_ADIF ) { // AAC ADIF is even worse. There's only the one header at the // beginning of the file. If you want to skip forward, you have to // decode block by block and check how far along you are. Lovely, eh? unsigned long totalSamples = 0; void *sampleBuffer = NULL; do { NeAACDecFrameInfo frameInfo; sampleBuffer = NeAACDecDecode(m_decoder, &frameInfo, m_inBuf, static_cast(m_inBufSize) ); totalSamples += frameInfo.samples; if ( frameInfo.bytesconsumed > 0 ) { m_inBufSize -= frameInfo.bytesconsumed; fillBuffer( m_file, m_inBuf, m_inBufSize, frameInfo.bytesconsumed ); } if ( totalSamples >= ( mSecs * m_adifSamplerate * m_adifChannels / 1000 ) ) break; } while ( sampleBuffer != NULL ); } } void AAC_ADTS_File::postDecode(unsigned long bytesConsumed) { m_inBufSize -= bytesConsumed; fillBuffer( m_file, m_inBuf, m_inBufSize, bytesConsumed ); } //////////////////////////////////////////////////////////////////////// // // AAC in an MP4 wrapper // //////////////////////////////////////////////////////////////////////// uint32_t read_callback( void *user_data, void *buffer, uint32_t length ) { return static_cast(fread( buffer, 1, length, static_cast(user_data) )); } uint32_t seek_callback( void *user_data, uint64_t position ) { return fseek( static_cast(user_data), static_cast(position), SEEK_SET ); } AAC_MP4_File::AAC_MP4_File( const QString& fileName, int headerType ) : AAC_File(fileName, headerType) , m_mp4AudioTrack( -1 ) , m_mp4SampleId( 0 ) , m_mp4File ( NULL ) , m_mp4cb ( NULL ) { } int32_t AAC_MP4_File::readSample() { unsigned int bsize; int32_t rc = mp4ff_read_sample( m_mp4File, m_mp4AudioTrack, m_mp4SampleId, &m_inBuf, &bsize ); m_inBufSize = bsize; // Not necessarily an error. Could just mean end of file. //if ( rc == 0 ) // std::cerr << "Reading samples failed." << std::endl; return rc; } int32_t AAC_MP4_File::getTrack( const mp4ff_t *f ) { // find AAC track int32_t numTracks = mp4ff_total_tracks( f ); for ( int32_t i = 0; i < numTracks; i++ ) { unsigned char *buff = NULL; unsigned int buff_size = 0; mp4AudioSpecificConfig mp4ASC; mp4ff_get_decoder_config( f, i, &buff, &buff_size ); if ( buff ) { int8_t rc = NeAACDecAudioSpecificConfig( buff, buff_size, &mp4ASC ); free( buff ); if ( rc < 0 ) continue; return i; } } // can't decode this, probably DRM return -1; } bool AAC_MP4_File::commonSetup( NeAACDecHandle& decoder, mp4ff_callback_t*& cb, FILE*& fp, mp4ff_t*& mp4, int32_t& audioTrack ) { fp = fopen(QFile::encodeName(m_fileName), "rb"); if ( !fp ) { throw std::runtime_error( "Error: failed to open AAC file!" ); return false; } decoder = NeAACDecOpen(); // Set configuration NeAACDecConfigurationPtr config; config = NeAACDecGetCurrentConfiguration( decoder ); config->outputFormat = FAAD_FMT_16BIT; config->downMatrix = 1; // Turn 5.1 channels into 2 NeAACDecSetConfiguration( decoder, config ); // initialise the callback structure cb = static_cast( malloc( sizeof(mp4ff_callback_t) ) ); cb->read = read_callback; cb->seek = seek_callback; cb->user_data = fp; mp4 = mp4ff_open_read( cb ); if ( !mp4 ) { // unable to open file free( cb ); cb = NULL; NeAACDecClose( decoder ); decoder = NULL; fclose( fp ); fp = NULL; throw std::runtime_error( "Error: failed to set up AAC decoder!" ); return false; } if ( ( audioTrack = getTrack( mp4 )) < 0 ) { free( cb ); cb = NULL; NeAACDecClose( decoder ); decoder = NULL; fclose( fp ); fp = NULL; mp4ff_close( mp4 ); mp4 = NULL; audioTrack = 0; throw std::runtime_error( "Error: Unable to find an audio track. Is the file DRM protected?" ); return false; } return true; } /*QString AAC_MP4_File::getMbid() { int j = mp4ff_meta_get_num_items( m_mp4File ); if ( j > 0 ) { int k; for ( k = 0; k < j; k++ ) { char *tag = NULL, *item = NULL; if ( mp4ff_meta_get_by_index( m_mp4File, k, &item, &tag ) ) { if ( item != NULL && tag != NULL ) { QString key(item); if ( key.toLower() == "musicbrainz track id" ) { QString ret(tag); free( item ); free( tag ); return ret; } free( item ); free( tag ); } } } } return QString(); }*/ void AAC_MP4_File::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) { FILE* fp = NULL; mp4ff_callback_t *cb = NULL; NeAACDecHandle decoder = NULL; mp4ff_t* mp4 = NULL; int32_t audioTrack; bool success = commonSetup( decoder, cb, fp, mp4, audioTrack ); if ( success ) { // get basic file info mp4AudioSpecificConfig mp4ASC; unsigned char* buffer = NULL; unsigned int buffer_size = 0; double f = 1024.0; unsigned int framesize = 1024; int32_t samples = mp4ff_num_samples( mp4, audioTrack ); if ( buffer ) { if ( NeAACDecAudioSpecificConfig(buffer, buffer_size, &mp4ASC) >= 0 ) { if ( mp4ASC.frameLengthFlag == 1 ) framesize = 960; if ( mp4ASC.sbr_present_flag == 1 ) framesize *= 2; if ( mp4ASC.sbr_present_flag == 1 ) f = f * 2.0; } free( buffer ); } samplerate = mp4ff_get_sample_rate( mp4, audioTrack ); if ( samplerate > 0 ) lengthSecs = static_cast(samples * f / samplerate + 0.5); bitrate = mp4ff_get_avg_bitrate( mp4, audioTrack ); nchannels = mp4ff_get_channel_count( mp4, audioTrack ); mp4ff_close( mp4 ); NeAACDecClose( decoder ); free( cb ); fclose( fp ); } } bool AAC_MP4_File::init() { FILE* fp = NULL; bool success = commonSetup( m_decoder, m_mp4cb, fp, m_mp4File, m_mp4AudioTrack ); if ( !success ) return false; unsigned char* buffer = NULL; unsigned int buffer_size = 0; unsigned long samplerate; unsigned char channels; mp4ff_get_decoder_config( m_mp4File, m_mp4AudioTrack, &buffer, &buffer_size ); if( NeAACDecInit2( m_decoder, buffer, buffer_size, &samplerate, &channels) < 0 ) { // If some error initializing occured, skip the file if ( fp ) fclose( fp ); throw std::runtime_error( "Error: unable to initialize AAC decoder library!" ); return false; } if ( buffer ) free( buffer ); return true; } void AAC_MP4_File::postDecode(unsigned long) { free( m_inBuf ); m_inBuf = NULL; m_mp4SampleId++; } void AAC_MP4_File::skip( const int mSecs ) { double dur = 0.0; int f = 1; unsigned char *buff = NULL; unsigned int buff_size = 0; uint32_t totalSamples = mp4ff_num_samples( m_mp4File, m_mp4AudioTrack ); mp4AudioSpecificConfig mp4ASC; mp4ff_get_decoder_config( m_mp4File, m_mp4AudioTrack, &buff, &buff_size ); if ( buff ) { int8_t rc = NeAACDecAudioSpecificConfig( buff, buff_size, &mp4ASC ); free( buff ); if ( rc >= 0 && mp4ASC.sbr_present_flag == 1 ) f = 2; // I think the f multiplier is needed here. while ( dur * 1000.0 * f / static_cast(mp4ASC.samplingFrequency) < mSecs && m_mp4SampleId < totalSamples ) { dur += mp4ff_get_sample_duration( m_mp4File, m_mp4AudioTrack, m_mp4SampleId ); m_mp4SampleId++; } } else std::cerr << "Error: could not skip " << mSecs << " milliseconds" << std::endl; } AAC_MP4_File::~AAC_MP4_File() { if ( m_mp4File ) mp4ff_close( m_mp4File ); if ( m_mp4cb ) { free( m_mp4cb ); } } //////////////////////////////////////////////////////////////////////// // // AacSource // //////////////////////////////////////////////////////////////////////// AacSource::AacSource() : m_eof( false ) , m_aacFile( NULL ) {} AacSource::~AacSource() { delete m_aacFile; } int AacSource::checkHeader() { FILE *fp = NULL; unsigned char header[10]; // check for mp4 file fp = fopen(QFile::encodeName(m_fileName), "rb"); if ( !fp ) { std::cerr << "Error: failed to open " << strerror( errno ) << std::endl; return AAC_File::AAC_UNKNOWN; } fread( header, 1, 10, fp ); // MP4 headers if ( !memcmp( &header[4], "ftyp", 4 ) ) { fclose( fp ); return AAC_File::AAC_MP4; } // Skip id3 tags int tagsize = 0; if ( !memcmp( header, "ID3", 3 ) ) { /* high bit is not used */ tagsize = (header[6] << 21) | (header[7] << 14) | (header[8] << 7) | (header[9] << 0); tagsize += 10; fseek( fp, tagsize, SEEK_SET ); fread( header, 1, 10, fp ); } // Check for ADTS OR ADIF headers if ( (header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0) ) { fclose( fp ); return AAC_File::AAC_ADTS; } else if (memcmp(header, "ADIF", 4) == 0) { fclose( fp ); return AAC_File::AAC_ADIF; } fclose( fp ); return AAC_File::AAC_UNKNOWN; } void AacSource::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) { // get the header plus some other stuff.. m_aacFile->getInfo( lengthSecs, samplerate, bitrate, nchannels ); } void AacSource::init(const QString& fileName) { m_fileName = fileName; int headerType = checkHeader(); if ( headerType != AAC_File::AAC_UNKNOWN ) { if ( headerType == AAC_File::AAC_MP4 ) m_aacFile = new AAC_MP4_File(m_fileName, headerType); else m_aacFile = new AAC_ADTS_File( m_fileName, headerType ); } if ( m_aacFile ) m_aacFile->init(); else throw std::runtime_error( "ERROR: No suitable AAC decoder found!" ); } /*QString AacSource::getMbid() { QString mbid = m_aacFile->getMbid(); return mbid; }*/ void AacSource::skip( const int mSecs ) { if ( mSecs < 0 || !m_aacFile->m_decoder ) return; m_aacFile->skip( mSecs ); } void AacSource::skipSilence(double silenceThreshold /* = 0.0001 */) { if ( !m_aacFile->m_decoder ) return; silenceThreshold *= static_cast( std::numeric_limits::max() ); for (;;) { if ( m_aacFile->m_header == AAC_File::AAC_MP4 ) { if ( !static_cast(m_aacFile)->readSample() ) break; } NeAACDecFrameInfo frameInfo; void* sampleBuffer = NeAACDecDecode(m_aacFile->m_decoder, &frameInfo, m_aacFile->m_inBuf, static_cast(m_aacFile->m_inBufSize) ); m_aacFile->postDecode( frameInfo.bytesconsumed ); if ( frameInfo.error > 0 ) { break; } else if ( frameInfo.samples > 0 ) { double sum = 0; int16_t *buf = static_cast(sampleBuffer); switch ( frameInfo.channels ) { case 1: for (size_t j = 0; j < frameInfo.samples; ++j) sum += abs( buf[j] ); break; case 2: for (size_t j = 0; j < frameInfo.samples; j+=2) sum += abs( (buf[j] >> 1) + (buf[j+1] >> 1) ); break; } if ( (sum >= silenceThreshold * static_cast(frameInfo.samples/frameInfo.channels) ) ) break; } } } int AacSource::updateBuffer( signed short *pBuffer, size_t bufferSize ) { size_t nwrit = 0; //number of samples written to the output buffer if ( m_aacFile->m_overflowSize > 0 ) { size_t samples_to_use = bufferSize < m_aacFile->m_overflowSize ? bufferSize : m_aacFile->m_overflowSize; memcpy( pBuffer, m_aacFile->m_overflow, samples_to_use * sizeof(signed short) ); nwrit += samples_to_use; m_aacFile->m_overflowSize -= samples_to_use; memmove( (void*)(m_aacFile->m_overflow), (void*)(m_aacFile->m_overflow + samples_to_use*sizeof(signed short)), samples_to_use*sizeof(signed short) ); } if ( !m_aacFile->m_decoder ) return 0; for (;;) { signed short* pBufferIt = pBuffer + nwrit; void* sampleBuffer; assert( nwrit <= bufferSize ); if ( m_aacFile->m_header == AAC_File::AAC_MP4 ) { if ( !static_cast(m_aacFile)->readSample() ) { m_eof = true; return static_cast(nwrit); } } NeAACDecFrameInfo frameInfo; sampleBuffer = NeAACDecDecode(m_aacFile->m_decoder, &frameInfo, m_aacFile->m_inBuf, static_cast(m_aacFile->m_inBufSize) ); size_t samples_to_use = (bufferSize - nwrit) < frameInfo.samples ? bufferSize-nwrit : frameInfo.samples; if ( samples_to_use > 0 && sampleBuffer != NULL ) { memcpy( pBufferIt, sampleBuffer, samples_to_use * sizeof(signed short) ); nwrit += samples_to_use; } if ( samples_to_use < frameInfo.samples ) { m_aacFile->m_overflow = static_cast(realloc( m_aacFile->m_overflow, (frameInfo.samples - samples_to_use) * sizeof(signed short) ) ); memcpy( m_aacFile->m_overflow, static_cast(sampleBuffer) + samples_to_use, (frameInfo.samples - samples_to_use) * sizeof(signed short) ); m_aacFile->m_overflowSize = frameInfo.samples - samples_to_use; } m_aacFile->postDecode( frameInfo.bytesconsumed ); if ( sampleBuffer == NULL ) { m_eof = true; break; } if ( frameInfo.error > 0 ) { std::cerr << "Error: " << NeAACDecGetErrorMessage(frameInfo.error) << std::endl; break; } if ( nwrit == bufferSize ) break; } return static_cast(nwrit); } ================================================ FILE: app/client/Fingerprinter/AacSource.h ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #ifndef __AAC_SOURCE_H__ #define __AAC_SOURCE_H__ #include class AacSource : public lastfm::FingerprintableSource { public: AacSource(); ~AacSource(); virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels); virtual void init(const QString& fileName); virtual int updateBuffer(signed short* pBuffer, size_t bufferSize); virtual void skip(const int mSecs); virtual void skipSilence(double silenceThreshold = 0.0001); virtual bool eof() const { return m_eof; } private: int checkHeader(); QString m_fileName; bool m_eof; class AAC_File *m_aacFile; }; #endif ================================================ FILE: app/client/Fingerprinter/AacSource_p.h ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #include #include class AAC_File { public: AAC_File(const QString&, int headerType); virtual ~AAC_File(); virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) = 0; virtual bool init() = 0; //virtual QString getMbid() = 0; virtual void skip( const int mSecs ) = 0; virtual void postDecode(unsigned long) = 0; enum HeaderType { AAC_UNKNOWN = 0, AAC_ADIF, AAC_ADTS, AAC_MP4 }; QString m_fileName; unsigned char *m_inBuf; size_t m_inBufSize; NeAACDecHandle m_decoder; unsigned char *m_overflow; size_t m_overflowSize; int m_header; }; class AAC_MP4_File : public AAC_File { public: AAC_MP4_File(const QString&, int headerType = AAC_MP4 ); ~AAC_MP4_File(); virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ); virtual bool init(); //virtual QString getMbid(); virtual void skip( const int mSecs ); virtual void postDecode(unsigned long); int32_t readSample(); private: bool commonSetup( NeAACDecHandle& handle, mp4ff_callback_t*& cb, FILE*& fp, mp4ff_t*& mp4, int32_t& audioTrack ); virtual int32_t getTrack( const mp4ff_t* f ); int32_t m_mp4AudioTrack; uint32_t m_mp4SampleId; mp4ff_t *m_mp4File; mp4ff_callback_t *m_mp4cb; }; class AAC_ADTS_File : public AAC_File { public: AAC_ADTS_File( const QString& fileName, int headerType ); ~AAC_ADTS_File(); virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ); virtual bool init(); //virtual QString getMbid(); virtual void skip( const int mSecs ); virtual void postDecode(unsigned long bytesconsumed ); private: int32_t commonSetup( FILE*& fp, NeAACDecHandle& decoder, unsigned char*& buf, size_t& bufSize, unsigned long& samplerate, unsigned char& channels ); void parse( FILE*& fp, unsigned char*& buf, size_t& bufSize, int &bitrate, double &length ); void fillBuffer( FILE*& fp, unsigned char*& buf, size_t& bufSize, const size_t m_bytesConsumed ); FILE* m_file; // These two only needed for skipping AAC ADIF files uint32_t m_adifSamplerate; int m_adifChannels; }; ================================================ FILE: app/client/Fingerprinter/FlacSource.cpp ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #include "FlacSource.h" #include #include #include #include #include #include #include #include FLAC__StreamDecoderWriteStatus FlacSource::_write_callback(const FLAC__StreamDecoder *, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) { assert(client_data != NULL); FlacSource *instance = reinterpret_cast(client_data); assert(instance != NULL); return instance->write_callback(frame, buffer); } FLAC__StreamDecoderWriteStatus FlacSource::write_callback(const FLAC__Frame *frame, const FLAC__int32 * const buffer[]) { m_outBufLen = 0; if ( m_outBuf ) { size_t i; for(i = 0; i < frame->header.blocksize; i++) { switch ( m_channels ) { case 1: m_outBuf[m_outBufLen] = (FLAC__int16)buffer[0][i]; // mono m_outBufLen++; break; case 2: m_outBuf[m_outBufLen] = (FLAC__int16)buffer[0][i]; // left channel m_outBuf[m_outBufLen+1] = (FLAC__int16)buffer[1][i]; // right channel m_outBufLen += 2; break; } } m_samplePos += frame->header.blocksize; } return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } // --------------------------------------------------------------------- void FlacSource::_metadata_callback(const FLAC__StreamDecoder *, const FLAC__StreamMetadata *metadata, void *client_data) { assert(client_data != NULL); FlacSource *instance = reinterpret_cast(client_data); assert(instance != NULL); instance->metadata_callback(metadata); } void FlacSource::metadata_callback( const FLAC__StreamMetadata *metadata ) { switch ( metadata->type ) { case FLAC__METADATA_TYPE_STREAMINFO: m_channels = metadata->data.stream_info.channels; m_totalSamples = metadata->data.stream_info.total_samples; m_samplerate = metadata->data.stream_info.sample_rate; m_bps = metadata->data.stream_info.bits_per_sample; m_maxFrameSize = metadata->data.stream_info.max_framesize; break; case FLAC__METADATA_TYPE_VORBIS_COMMENT: m_commentData = FLAC__metadata_object_clone(metadata); break; default: break; } } // --------------------------------------------------------------------- void FlacSource::_error_callback(const FLAC__StreamDecoder *, FLAC__StreamDecoderErrorStatus status, void *client_data) { assert(client_data != NULL); FlacSource *instance = reinterpret_cast(client_data); assert(instance != NULL); instance->error_callback(status); } void FlacSource::error_callback(FLAC__StreamDecoderErrorStatus status) { std::cerr << "Got FLAC error: " << FLAC__StreamDecoderErrorStatusString[status] << std::endl; } // --------------------------------------------------------------------- FlacSource::FlacSource() : m_decoder( 0 ) , m_fileSize( 0 ) , m_outBuf( 0 ) , m_outBufLen( 0 ) , m_outBufPos( 0 ) , m_samplePos( 0 ) , m_maxFrameSize( 0 ) , m_commentData( 0 ) , m_bps( 0 ) , m_channels( 0 ) , m_samplerate( 0 ) , m_totalSamples( 0 ) , m_eof( false ) { } // --------------------------------------------------------------------- FlacSource::~FlacSource() { if ( m_decoder ) { FLAC__stream_decoder_finish( m_decoder ); FLAC__stream_decoder_delete( m_decoder ); } if ( m_commentData ) FLAC__metadata_object_delete( m_commentData ); if ( m_outBuf ) free( m_outBuf ); } // --------------------------------------------------------------------- void FlacSource::init(const QString& fileName) { m_fileName = fileName; if ( !m_decoder ) { FILE *f = fopen(QFile::encodeName(m_fileName), "rb" ); if ( f ) { // Need to check which init call to use; flac doesn't do that for us unsigned char header[35]; bool isOgg = false; fread( header, 1, 35, f ); if ( memcmp(header, "OggS", 4) == 0 && memcmp(&header[29], "FLAC", 4) == 0 ) isOgg = true; // getInfo() will need this to calculate bitrate fseek( f, 0, SEEK_END ); m_fileSize = ftell(f); rewind( f ); m_decoder = FLAC__stream_decoder_new(); FLAC__stream_decoder_set_metadata_respond(m_decoder, FLAC__METADATA_TYPE_STREAMINFO); FLAC__stream_decoder_set_metadata_respond(m_decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); int init_status; if ( FLAC_API_SUPPORTS_OGG_FLAC && isOgg ) init_status = FLAC__stream_decoder_init_ogg_FILE( m_decoder, f, _write_callback, _metadata_callback, _error_callback, this ); else init_status = FLAC__stream_decoder_init_FILE( m_decoder, f, _write_callback, _metadata_callback, _error_callback, this ); if(init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) return; FLAC__stream_decoder_process_until_end_of_metadata( m_decoder ); m_outBuf = static_cast(malloc( sizeof(signed short)*m_maxFrameSize)); if ( m_bps != 16 ) { FLAC__stream_decoder_finish( m_decoder ); FLAC__stream_decoder_delete( m_decoder ); FLAC__metadata_object_delete( m_commentData ); m_decoder = 0; m_commentData = 0; throw std::runtime_error( "ERROR: only 16 bit FLAC files are currently supported!" ); } } else throw std::runtime_error( "ERROR: cannot load FLAC file!" ); } } // --------------------------------------------------------------------- /*QString FlacSource::getMbid() { if ( m_commentData ) { FLAC__StreamMetadata_VorbisComment *vc; vc = &m_commentData->data.vorbis_comment; for ( unsigned int i = 0; i < vc->num_comments; ++i ) { QByteArray key( (char*)(vc->comments[i].entry), vc->comments[i].length ); if ( key.left(20).toLower() == "musicbrainz_trackid=" ) { QString val = key.mid(20); return val; } } } return QString(); }*/ // --------------------------------------------------------------------- void FlacSource::getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels) { lengthSecs = 0; samplerate = 0; bitrate = 0; nchannels = 0; if ( m_decoder ) { samplerate = m_samplerate; nchannels = m_channels; if ( samplerate > 0 ) lengthSecs = static_cast( static_cast(m_totalSamples)/m_samplerate + 0.5); // Calcuate bitrate if ( lengthSecs > 0 ) { FLAC__Metadata_SimpleIterator *it = FLAC__metadata_simple_iterator_new(); FLAC__metadata_simple_iterator_init( it, QFile::encodeName(m_fileName), true, true ); while( !FLAC__metadata_simple_iterator_is_last( it ) ) { FLAC__metadata_simple_iterator_next( it ); } off_t audioOffset = FLAC__metadata_simple_iterator_get_block_offset( it ) + FLAC__metadata_simple_iterator_get_block_length( it ); FLAC__metadata_simple_iterator_delete( it ); bitrate = static_cast( static_cast(m_fileSize - audioOffset) * 8 / lengthSecs + 0.5 ); } } } // --------------------------------------------------------------------- void FlacSource::skip( const int mSecs ) { FLAC__uint64 absSample = mSecs * m_samplerate / 1000 + m_samplePos; if ( !FLAC__stream_decoder_seek_absolute(m_decoder, absSample) ) FLAC__stream_decoder_reset( m_decoder ); m_outBufLen = 0; } // --------------------------------------------------------------------- void FlacSource::skipSilence(double silenceThreshold /* = 0.0001 */) { silenceThreshold *= static_cast( std::numeric_limits::max() ); for ( ;; ) { double sum = 0; bool result = FLAC__stream_decoder_process_single( m_decoder ); // there was a fatal read if ( !result ) break; switch ( m_channels ) { case 1: for (size_t j = 0; j < m_outBufLen; ++j) sum += abs( m_outBuf[j] ); break; case 2: for ( size_t j = 0; j < m_outBufLen; j+=2 ) sum += abs( (m_outBuf[j] >> 1) + (m_outBuf[j+1] >> 1) ); break; } if ( (sum >= silenceThreshold * static_cast(m_outBufLen) ) ) break; } m_outBufLen = 0; } // --------------------------------------------------------------------- int FlacSource::updateBuffer( signed short *pBuffer, size_t bufferSize ) { size_t nwrit = 0; for ( ;; ) { size_t samples_to_use = std::min (bufferSize - nwrit, m_outBufLen - m_outBufPos); signed short* pBufferIt = pBuffer + nwrit; nwrit += samples_to_use; assert( nwrit <= bufferSize ); memcpy( pBufferIt, m_outBuf + m_outBufPos, sizeof(signed short)*samples_to_use ); if ( samples_to_use < m_outBufLen - m_outBufPos ) m_outBufPos = samples_to_use; else { m_outBufPos = 0; bool result = FLAC__stream_decoder_process_single( m_decoder ); // there was a fatal read if ( !result ) { std::cerr << "Fatal error decoding FLAC" << std::endl; return 0; } else if ( FLAC__stream_decoder_get_state( m_decoder ) == FLAC__STREAM_DECODER_END_OF_STREAM ) { m_eof = true; break; } } if ( nwrit == bufferSize ) return static_cast(nwrit); } return static_cast(nwrit); } // ----------------------------------------------------------------------------- ================================================ FILE: app/client/Fingerprinter/FlacSource.h ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #ifndef __FLAC_SOURCE_H__ #define __FLAC_SOURCE_H__ #include #include #include class FlacSource : public lastfm::FingerprintableSource { public: FlacSource(); virtual ~FlacSource(); virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels); virtual void init(const QString& fileName); // return a chunk of PCM data from the FLAC file virtual int updateBuffer(signed short* pBuffer, size_t bufferSize); virtual void skip(const int mSecs); virtual void skipSilence(double silenceThreshold = 0.0001); //QString getMbid(); bool eof() const { return m_eof; } private: static FLAC__StreamDecoderWriteStatus _write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data); static void _metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data); static void _error_callback(const ::FLAC__StreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *client_data); FLAC__StreamDecoderWriteStatus write_callback(const FLAC__Frame *frame, const FLAC__int32 * const buffer[]); void metadata_callback( const FLAC__StreamMetadata *metadata ); void error_callback(FLAC__StreamDecoderErrorStatus status); FLAC__StreamDecoder *m_decoder; QString m_fileName; size_t m_fileSize; short *m_outBuf; size_t m_outBufLen; size_t m_outBufPos; FLAC__uint64 m_samplePos; unsigned m_maxFrameSize; FLAC__StreamMetadata* m_commentData; unsigned m_bps; unsigned m_channels; unsigned m_samplerate; FLAC__uint64 m_totalSamples; bool m_eof; }; #endif ================================================ FILE: app/client/Fingerprinter/MadSource.cpp ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #include #include #include #include #include #include #include #include #include #include "MadSource.h" #undef max // was definded in mad using namespace std; // ----------------------------------------------------------- MadSource::MadSource() : m_pMP3_Buffer ( new unsigned char[m_MP3_BufferSize+MAD_BUFFER_GUARD] ) {} // ----------------------------------------------------------- MadSource::~MadSource() { if ( m_inputFile.isOpen() ) { m_inputFile.close(); mad_synth_finish(&m_mad_synth); mad_frame_finish(&m_mad_frame); mad_stream_finish(&m_mad_stream); } if (m_pMP3_Buffer) delete[] m_pMP3_Buffer; } // --------------------------------------------------------------------- inline short f2s(mad_fixed_t f) { /* A fixed point number is formed of the following bit pattern: * * SWWWFFFFFFFFFFFFFFFFFFFFFFFFFFFF * MSB LSB * S ==> Sign (0 is positive, 1 is negative) * W ==> Whole part bits * F ==> Fractional part bits * * This pattern contains MAD_F_FRACBITS fractional bits, one * should alway use this macro when working on the bits of a fixed * point number. It is not guaranteed to be constant over the * different platforms supported by libmad. * * The signed short value is formed, after clipping, by the least * significant whole part bit, followed by the 15 most significant * fractional part bits. Warning: this is a quick and dirty way to * compute the 16-bit number, madplay includes much better * algorithms. */ /* Clipping */ if(f >= MAD_F_ONE) return(SHRT_MAX); if(f <= -MAD_F_ONE) return(-SHRT_MAX); /* Conversion. */ f = f >> (MAD_F_FRACBITS-15); return (signed short)f; } // --------------------------------------------------------------------- string MadSource::MadErrorString(const mad_error& error) { switch(error) { /* Generic unrecoverable errors. */ case MAD_ERROR_BUFLEN: return("input buffer too small (or EOF)"); case MAD_ERROR_BUFPTR: return("invalid (null) buffer pointer"); case MAD_ERROR_NOMEM: return("not enough memory"); /* Frame header related unrecoverable errors. */ case MAD_ERROR_LOSTSYNC: return("lost synchronization"); case MAD_ERROR_BADLAYER: return("reserved header layer value"); case MAD_ERROR_BADBITRATE: return("forbidden bitrate value"); case MAD_ERROR_BADSAMPLERATE: return("reserved sample frequency value"); case MAD_ERROR_BADEMPHASIS: return("reserved emphasis value"); /* Recoverable errors */ case MAD_ERROR_BADCRC: return("CRC check failed"); case MAD_ERROR_BADBITALLOC: return("forbidden bit allocation value"); case MAD_ERROR_BADSCALEFACTOR: return("bad scalefactor index"); case MAD_ERROR_BADFRAMELEN: return("bad frame length"); case MAD_ERROR_BADBIGVALUES: return("bad big_values count"); case MAD_ERROR_BADBLOCKTYPE: return("reserved block_type"); case MAD_ERROR_BADSCFSI: return("bad scalefactor selection info"); case MAD_ERROR_BADDATAPTR: return("bad main_data_begin pointer"); case MAD_ERROR_BADPART3LEN: return("bad audio data length"); case MAD_ERROR_BADHUFFTABLE: return("bad Huffman table select"); case MAD_ERROR_BADHUFFDATA: return("Huffman data overrun"); case MAD_ERROR_BADSTEREO: return("incompatible block_type for JS"); /* Unknown error. This switch may be out of sync with libmad's * defined error codes. */ default: return("Unknown error code"); } } // ----------------------------------------------------------------------------- bool MadSource::isRecoverable(const mad_error& error, bool log) { if (MAD_RECOVERABLE (error)) { /* Do not print a message if the error is a loss of * synchronization and this loss is due to the end of * stream guard bytes. (See the comments marked {3} * supra for more informations about guard bytes.) */ if (error != MAD_ERROR_LOSTSYNC /*|| mad_stream.this_frame != pGuard */ && log) { qWarning() << "Recoverable frame level error: " << QString::fromStdString( MadErrorString(error) ); } return true; } else { if (error == MAD_ERROR_BUFLEN) return true; else { QDataStream ss; ss << "Unrecoverable frame level error: " << QString::fromStdString( MadErrorString (error) ); throw "ss."; } } return false; } // ----------------------------------------------------------- void MadSource::init(const QString& fileName) { m_inputFile.setFileName( m_fileName = fileName ); bool fine = m_inputFile.open( QIODevice::ReadOnly ); if ( !fine ) { throw std::runtime_error ("Cannot load mp3 file!"); } mad_stream_init(&m_mad_stream); mad_frame_init (&m_mad_frame); mad_synth_init (&m_mad_synth); mad_timer_reset(&m_mad_timer); m_pcmpos = m_mad_synth.pcm.length; } // ----------------------------------------------------------------------------- /*QString MadSource::getMbid() { char out[MBID_BUFFER_SIZE]; int const r = getMP3_MBID( QFile::encodeName( m_fileName ), out ); if (r == 0) return QString::fromLatin1( out ); return QString(); }*/ void MadSource::getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) { // get the header plus some other stuff.. QFile inputFile(m_fileName); bool fine = inputFile.open( QIODevice::ReadOnly ); if ( !fine ) { throw std::runtime_error ("ERROR: Cannot load file for getInfo!"); return; } unsigned char* pMP3_Buffer = new unsigned char[m_MP3_BufferSize+MAD_BUFFER_GUARD]; mad_stream madStream; mad_header madHeader; mad_timer_t madTimer; mad_stream_init(&madStream); mad_timer_reset(&madTimer); double avgSamplerate = 0; double avgBitrate = 0; double avgNChannels = 0; int nFrames = 0; while ( fetchData( inputFile, pMP3_Buffer, m_MP3_BufferSize, madStream) ) { if ( mad_header_decode(&madHeader, &madStream) != 0 ) { if ( isRecoverable(madStream.error) ) continue; else break; } mad_timer_add(&madTimer, madHeader.duration); avgSamplerate += madHeader.samplerate; avgBitrate += madHeader.bitrate; if ( madHeader.mode == MAD_MODE_SINGLE_CHANNEL ) ++avgNChannels; else avgNChannels += 2; ++nFrames; } inputFile.close(); mad_stream_finish(&madStream); mad_header_finish(&madHeader); delete[] pMP3_Buffer; lengthSecs = static_cast(madTimer.seconds); samplerate = static_cast( (avgSamplerate/nFrames) + 0.5 ); bitrate = static_cast( (avgBitrate/nFrames) + 0.5 ); nchannels = static_cast( (avgNChannels/nFrames) + 0.5 ); } // ----------------------------------------------------------- bool MadSource::fetchData( QFile& mp3File, unsigned char* pMP3_Buffer, const int MP3_BufferSize, mad_stream& madStream ) { unsigned char *pReadStart = NULL; unsigned char *pGuard = NULL; if ( madStream.buffer == NULL || madStream.error == MAD_ERROR_BUFLEN ) { size_t readSize; size_t remaining; /* {2} libmad may not consume all bytes of the input * buffer. If the last frame in the buffer is not wholly * contained by it, then that frame's start is pointed by * the next_frame member of the Stream structure. This * common situation occurs when mad_frame_decode() fails, * sets the stream error code to MAD_ERROR_BUFLEN, and * sets the next_frame pointer to a non NULL value. (See * also the comment marked {4} bellow.) * * When this occurs, the remaining unused bytes must be * put back at the beginning of the buffer and taken in * account before refilling the buffer. This means that * the input buffer must be large enough to hold a whole * frame at the highest observable bit-rate (currently 448 * kb/s). XXX=XXX Is 2016 bytes the size of the largest * frame? (448000*(1152/32000))/8 */ if (madStream.next_frame != NULL) { remaining = madStream.bufend - madStream.next_frame; memmove (pMP3_Buffer, madStream.next_frame, remaining); pReadStart = pMP3_Buffer + remaining; readSize = MP3_BufferSize - remaining; } else { readSize = MP3_BufferSize; pReadStart = pMP3_Buffer; remaining = 0; } readSize = mp3File.read( reinterpret_cast(pReadStart), readSize ); // nothing else to read! if (readSize <= 0) return false; if ( mp3File.atEnd() ) { pGuard = pReadStart + readSize; memset (pGuard, 0, MAD_BUFFER_GUARD); readSize += MAD_BUFFER_GUARD; } // Pipe the new buffer content to libmad's stream decoder facility. mad_stream_buffer( &madStream, pMP3_Buffer, static_cast(readSize + remaining)); madStream.error = MAD_ERROR_NONE; } return true; } // ----------------------------------------------------------------------------- void MadSource::skipSilence(double silenceThreshold /* = 0.0001 */) { mad_frame madFrame; mad_synth madSynth; mad_frame_init(&madFrame); mad_synth_init (&madSynth); silenceThreshold *= static_cast( numeric_limits::max() ); for (;;) { if ( !fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream) ) break; if ( mad_frame_decode(&madFrame, &m_mad_stream) != 0 ) { if ( isRecoverable(m_mad_stream.error) ) continue; else break; } mad_synth_frame (&madSynth, &madFrame); double sum = 0; switch (madSynth.pcm.channels) { case 1: for (size_t j = 0; j < madSynth.pcm.length; ++j) sum += abs(f2s(madSynth.pcm.samples[0][j])); break; case 2: for (size_t j = 0; j < madSynth.pcm.length; ++j) sum += abs(f2s( (madSynth.pcm.samples[0][j] >> 1) + (madSynth.pcm.samples[1][j] >> 1))); break; } if ( (sum >= silenceThreshold * madSynth.pcm.length) ) break; } mad_frame_finish(&madFrame); } // ----------------------------------------------------------------------------- void MadSource::skip(const int mSecs) { if ( mSecs <= 0 ) return; mad_header madHeader; mad_header_init(&madHeader); for (;;) { if (!fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream)) break; if ( mad_header_decode(&madHeader, &m_mad_stream) != 0 ) { if ( isRecoverable(m_mad_stream.error) ) continue; else break; } mad_timer_add(&m_mad_timer, madHeader.duration); if ( mad_timer_count(m_mad_timer, MAD_UNITS_MILLISECONDS) >= mSecs ) break; } mad_header_finish(&madHeader); } // ----------------------------------------------------------- int MadSource::updateBuffer(signed short* pBuffer, size_t bufferSize) { size_t nwrit = 0; //number of samples written to the output buffer for (;;) { // get a (valid) frame // m_pcmpos == 0 could mean two things // - we have completely decoded a frame, but the output buffer is still // not full (it would make more sense for pcmpos == pcm.length(), but // the loop assigns pcmpos = 0 at the end and does it this way! // - we are starting a stream if ( m_pcmpos == m_mad_synth.pcm.length ) { if ( !fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream) ) { break; // nothing else to read } // decode the frame if (mad_frame_decode (&m_mad_frame, &m_mad_stream)) { if ( isRecoverable(m_mad_stream.error) ) continue; else break; } // if (mad_frame_decode (&madFrame, &madStream)) mad_timer_add (&m_mad_timer, m_mad_frame.header.duration); mad_synth_frame (&m_mad_synth, &m_mad_frame); m_pcmpos = 0; } size_t samples_for_mp3 = m_mad_synth.pcm.length - m_pcmpos; size_t samples_for_buf = bufferSize - nwrit; signed short* pBufferIt = pBuffer + nwrit; size_t i = 0, j = 0; switch( m_mad_synth.pcm.channels ) { case 1: { size_t samples_to_use = min (samples_for_mp3, samples_for_buf); for (i = 0; i < samples_to_use; ++i ) pBufferIt[i] = f2s( m_mad_synth.pcm.samples[0][i+m_pcmpos] ); } j = i; break; case 2: for (; i < samples_for_mp3 && j < samples_for_buf ; ++i, j+=2 ) { pBufferIt[j] = f2s( m_mad_synth.pcm.samples[0][i+m_pcmpos] ); pBufferIt[j+1] = f2s( m_mad_synth.pcm.samples[1][i+m_pcmpos] ); } break; default: cerr << "wtf kind of mp3 has " << m_mad_synth.pcm.channels << " channels??\n"; break; } m_pcmpos += i; nwrit += j; assert( nwrit <= bufferSize ); if (nwrit == bufferSize) return static_cast(nwrit); } return static_cast(nwrit); } // ----------------------------------------------------------------------------- ================================================ FILE: app/client/Fingerprinter/MadSource.h ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #ifndef __MP3_SOURCE_H__ #define __MP3_SOURCE_H__ #include #include #include #include #include #include class MadSource : public lastfm::FingerprintableSource { public: MadSource(); ~MadSource(); virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels); virtual void init(const QString& fileName); virtual int updateBuffer(signed short* pBuffer, size_t bufferSize); virtual void skip(const int mSecs); virtual void skipSilence(double silenceThreshold = 0.0001); virtual bool eof() const { return m_inputFile.atEnd(); } private: static bool fetchData( QFile& mp3File, unsigned char* pMP3_Buffer, const int MP3_BufferSize, mad_stream& madStream ); static bool isRecoverable(const mad_error& error, bool log = false); static std::string MadErrorString(const mad_error& error); struct mad_stream m_mad_stream; struct mad_frame m_mad_frame; mad_timer_t m_mad_timer; struct mad_synth m_mad_synth; QFile m_inputFile; unsigned char* m_pMP3_Buffer; static const int m_MP3_BufferSize = (5*8192); QString m_fileName; size_t m_pcmpos; }; #endif ================================================ FILE: app/client/Fingerprinter/VorbisSource.cpp ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #include "VorbisSource.h" #include #include #include #include #include #include #include // These specify the output format static const int wordSize = 2; // 16 bit output static const int isSigned = 1; #if __BIG_ENDIAN__ static const int isBigEndian = 1; #else static const int isBigEndian = 0; #endif VorbisSource::VorbisSource() : m_channels( 0 ) , m_samplerate( 0 ) , m_eof( false ) { memset( &m_vf, 0, sizeof(m_vf) ); } // --------------------------------------------------------------------- VorbisSource::~VorbisSource() { // ov_clear() also closes the file ov_clear( &m_vf ); } // --------------------------------------------------------------------- void VorbisSource::init(const QString& fileName) { m_fileName = fileName; if ( m_vf.datasource ) { std::cerr << "Warning: file already appears to be open"; return; } FILE *fp = fopen(QFile::encodeName(m_fileName), "rb" ); if( !fp ) throw std::runtime_error( "ERROR: Cannot open ogg file!" ); // See the warning about calling ov_open on Windows if ( ov_test_callbacks( fp, &m_vf, NULL, 0, OV_CALLBACKS_DEFAULT ) < 0 ) { fclose( fp ); throw std::runtime_error( "ERROR: This is not an ogg vorbis file!" ); } ov_test_open( &m_vf ); // Don't fingerprint files with more than one logical bitstream // They most likely contain more than one track if ( ov_streams( &m_vf ) != 1 ) throw std::runtime_error( "ERROR: ogg file contains multiple bitstreams" ); m_channels = ov_info( &m_vf, 0 )->channels; m_samplerate = static_cast(ov_info( &m_vf, 0 )->rate); m_eof = false; } void VorbisSource::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels) { // stream info nchannels = ov_info( &m_vf, -1 )->channels; samplerate = static_cast(ov_info( &m_vf, -1 )->rate); lengthSecs = static_cast(ov_time_total( &m_vf, -1 ) + 0.5); bitrate = static_cast(ov_bitrate( &m_vf, -1 )); } // --------------------------------------------------------------------- void VorbisSource::skip( const int mSecs ) { if ( mSecs < 0 ) return; double ts = mSecs / 1000.0 + ov_time_tell( &m_vf ); ov_time_seek( &m_vf, ts ); } // --------------------------------------------------------------------- void VorbisSource::skipSilence(double silenceThreshold /* = 0.0001 */) { silenceThreshold *= static_cast( std::numeric_limits::max() ); char sampleBuffer[4096]; int bs = 0; for (;;) { long charReadBytes = ov_read( &m_vf, sampleBuffer, 4096, isBigEndian, wordSize, isSigned, &bs ); // eof if ( !charReadBytes ) { m_eof = true; break; } if ( charReadBytes < 0 ) { // a bad bit of data: OV_HOLE || OV_EBADLINK continue; } else if ( charReadBytes > 0 ) { double sum = 0; char *buf = sampleBuffer; switch ( m_channels ) { case 1: for (long j = 0; j < charReadBytes/wordSize; j++) sum += abs( buf[j] ); break; case 2: for (long j = 0; j < charReadBytes/wordSize; j+=2) sum += abs( (buf[j] >> 1) + (buf[j+1] >> 1) ); break; } if ( sum >= silenceThreshold * static_cast(charReadBytes/wordSize/m_channels) ) break; } } } // --------------------------------------------------------------------- int VorbisSource::updateBuffer( signed short *pBuffer, size_t bufferSize ) { char* buf = new char[ bufferSize * wordSize ]; int bs = 0; size_t charwrit = 0; //number of samples written to the output buffer for (;;) { long charReadBytes = ov_read( &m_vf, buf, static_cast(bufferSize * wordSize - charwrit), isBigEndian, wordSize, isSigned, &bs ); if ( !charReadBytes ) { m_eof = true; break; // nothing else to read } // Don't really need this though since we're excluding files that have // more than one logical bitstream if ( bs != 0 ) { vorbis_info *vi = ov_info( &m_vf, -1 ); if ( m_channels != vi->channels || m_samplerate != vi->rate ) { std::cerr << "Files that change channel parameters or samplerate are currently not supported" << std::endl; return 0; } } if( charReadBytes < 0 ) { std::cerr << "Warning: corrupt section of data, attempting to continue..." << std::endl; continue; } char* pBufferIt = reinterpret_cast(pBuffer) + charwrit; charwrit += charReadBytes; assert( charwrit <= bufferSize * wordSize ); memcpy( pBufferIt, buf, charReadBytes ); if (charwrit == bufferSize * wordSize) return static_cast(charwrit/wordSize); } delete[] buf; return static_cast(charwrit/wordSize); } // ----------------------------------------------------------------------------- ================================================ FILE: app/client/Fingerprinter/VorbisSource.h ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #ifndef __VORBIS_SOURCE_H__ #define __VORBIS_SOURCE_H__ #include #include class VorbisSource : public lastfm::FingerprintableSource { public: VorbisSource(); ~VorbisSource(); virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels); virtual void init(const QString& fileName); virtual int updateBuffer(signed short* pBuffer, size_t bufferSize); virtual void skip(const int mSecs); virtual void skipSilence(double silenceThreshold = 0.0001); virtual bool eof() const { return m_eof; } private: OggVorbis_File m_vf; QString m_fileName; int m_channels; int m_samplerate; bool m_eof; }; #endif ================================================ FILE: app/client/Last.fm Scrobbler.css ================================================ SlidingStackedWidget { qproperty-speed: 200; qproperty-easingCurve: OutCirc; } FirstRunWizard { background-repeat: none; background-color: white; background-image: url(":/bg_clouds.png"); } FirstRunWizard QLabel#welcome { color: #333; font-size: 14px; margin: 30px 30px 0px; } FirstRunWizard QLabel#title { color: #d51007; font-size: 16px; font-weight: bold; margin: 30px; } WizardPage QLabel { color: #333; font-size: 14px; } FirstRunWizard QStackedWidget { margin: 0px 40px; } FirstRunWizard QAbstractButton { background-color: transparent; border: none; font-size: 11px; color: #333; } FirstRunWizard #authUrl { border: none; background: transparent; color: #333; } /* QWizard::NextButton */ /* QWizard::FinishButton */ /* QWizard::CommitButton */ FirstRunWizard QAbstractButton#next, FirstRunWizard QAbstractButton#finish { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #cc4d25, stop:1 #a02b1e); font-size: 14px; color: white; padding: 10px; border: 1px solid #711a11; border-radius: 3px; } FirstRunWizard QAbstractButton#next:hover, FirstRunWizard QAbstractButton#finish:hover { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #e34729, stop:1 #b23d21); } FirstRunWizard QAbstractButton#next:pressed, FirstRunWizard QAbstractButton#finish:pressed { background-color: #9f2a1d; } /* QWizard::BackButton */ /* QWizard::CancelButton */ /* QWizard::HelpButton */ /* QWizard::CustomButton1 */ FirstRunWizard QAbstractButton#custom { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #b8b8b8, stop:1 #a3a3a3); font-size: 14px; color: white; padding: 10px; border: 1px solid #888; border-radius: 3px; } FirstRunWizard QAbstractButton#custom:hover { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #cdcdcd, stop:1 #b6b6b6); } FirstRunWizard QAbstractButton#custom:pressed { background-color: #a2a2a2; } LoginPage QLabel#image { qproperty-pixmap: url(":/graphic_connect.png"); } AccessPage QLabel#image { qproperty-pixmap: url(":/graphic_access.png"); } PluginsPage QLabel#image { qproperty-pixmap: url(":/graphic_plugins.png"); } PluginsInstallPage QLabel#image { qproperty-pixmap: url(":/graphic_plugins.png"); } PluginsBootstrapPage QLabel#image { qproperty-pixmap: url(":/graphic_connect.png"); } PluginsBootstrapProgressPage QLabel#image { qproperty-pixmap: url(":/graphic_connect.png"); } TourScrobblesPage QLabel#image { qproperty-pixmap: url(":/graphic_scrobbles.png"); } TourMetadataPage QLabel#image { qproperty-pixmap: url(":/graphic_metadata.png"); } TourRadioPage QLabel#image { qproperty-pixmap: url(":/graphic_radio.png"); } TourLocationPage QLabel#image { qproperty-pixmap: url(":/graphic_access.png"); } TourFinishPage QLabel#image { qproperty-pixmap: url(":/graphic_finish.png"); } PluginsPage QCheckBox, BootstrapPage QRadioButton { qproperty-iconSize: 44px; } PluginsPage QCheckBox#itw, BootstrapPage QRadioButton#itw { qproperty-icon: url(":/player_itunes_64.png"); } PluginsPage QCheckBox#wa2, BootstrapPage QRadioButton#wa2 { qproperty-icon: url(":/player_winamp_64.png"); } PluginsPage QCheckBox#wmp, BootstrapPage QRadioButton#wmp { qproperty-icon: url(":/player_wmp_64.png"); } PluginsPage QCheckBox#foo3, BootstrapPage QRadioButton#foo3 { qproperty-icon: url(":/player_foobar_64.png"); } MessageBar { border: none; } MessageBar { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #E4F1FF, stop:1 #C3CEDA); border-bottom: 1px solid #3A3D41; } MessageBar#radio { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #FFF7CA, stop:1 #DAD3AD); border-bottom: 1px solid #413F33; } MessageBar QLabel#icon { background-repeat: none; background-color: transparent; background-repeat: no-repeat; background-position: center; color: transparent; min-width: 19px; min-height: 19px; } MessageBar QLabel#icon { background-image: url(":/message_info.png"); } MessageBar#radio QLabel#icon { background-image: url(":/message_error.png"); } MessageBar QPushButton#close { background-repeat: none; background-color: transparent; background-repeat: no-repeat; background-position: center; color: transparent; background-image: url(":/message_close.png"); min-width: 17px; min-height: 17px; } SideBar { border: none; background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #2d2d2d, stop:0.925 #2d2d2d, stop:1 #232323); padding: 0px; margin: 0px; min-width: 76px; } SideBar QAbstractButton { text-align: bottom; background-color: transparent; background-repeat: no-repeat; background-position: center; font-size: 11px; color: #888888; border: none; height: 67px; width: 70px; padding-bottom: 13px; /* the height and bottom-padding should add up to 80px */ } SideBar QAbstractButton:hover, SideBar QAbstractButton:checked { color: #ffffff; } SideBar QAbstractButton#nowPlaying { background-image: url(":/tab_now_playing_REST.png"); } SideBar QAbstractButton#nowPlaying:hover { background-image: url(":/tab_now_playing_HOVER.png"); } SideBar QAbstractButton#nowPlaying:checked { background-image: url(":/tab_now_playing_ACTIVE.png"); } SideBar QAbstractButton#scrobbles { background-image: url(":/tab_scrobbles_REST.png"); } SideBar QAbstractButton#scrobbles:hover { background-image: url(":/tab_scrobbles_HOVER.png"); } SideBar QAbstractButton#scrobbles:checked { background-image: url(":/tab_scrobbles_ACTIVE.png"); } SideBar QAbstractButton#profile { background-image: url(":/tab_profile_REST.png"); } SideBar QAbstractButton#profile:hover { background-image: url(":/tab_profile_HOVER.png"); } SideBar QAbstractButton#profile:checked { background-image: url(":/tab_profile_ACTIVE.png"); } SideBar QAbstractButton#friends { background-image: url(":/tab_friends_REST.png"); } SideBar QAbstractButton#friends:hover { background-image: url(":/tab_friends_HOVER.png"); } SideBar QAbstractButton#friends:checked { background-image: url(":/tab_friends_ACTIVE.png"); } SideBar QAbstractButton#radio { background-image: url(":/tab_radio_REST.png"); } SideBar QAbstractButton#radio:hover { background-image: url(":/tab_radio_HOVER.png"); } SideBar QAbstractButton#radio:checked { background-image: url(":/tab_radio_ACTIVE.png"); } QFrame#splitter1, QFrame#splitter2, QFrame#splitter3, QFrame#splitter4, QFrame#splitter5 { border: none; border-right: 1px solid white; border-left: 1px solid #cdcdcd; min-width: 0px; max-width: 0px; margin: 0px 20px; } QFrame#volumeSplitter1, QFrame#volumeSplitter2 { border: none; border-right: 1px solid #eee; border-left: 1px solid #bbb; min-width: 0px; max-width: 0px; margin: 0px 20px; } PlaybackControlsWidget QFrame#progressFrame QFrame#volumeSplitter1, PlaybackControlsWidget QFrame#progressFrame QFrame#volumeSplitter2 { margin: 0px; } PlaybackControlsWidget QPushButton#volume { padding: 0px 3px; } QFrame#splitter, QFrame#hsplitter1, QFrame#hsplitter2 { border: none; border-bottom: 1px solid white; border-top: 1px solid #cdcdcd; min-height: 0px; max-height: 0px; margin: 10px 0px; } MetadataWidget QFrame#hsplitter1, MetadataWidget QFrame#hsplitter2 { margin: 0px; } NothingPlayingWidget { border: none; } NothingPlayingWidget QLabel { color: #333; font-size: 12px; } NothingPlayingWidget QFrame#contents { border: none; background-image: url(":/start_bg.png"); background-repeat: none; padding: 20px; margin: 0px; } NothingPlayingWidget QLabel#start { font-size: 16px; font-weight: bold; } NothingPlayingWidget QLabel#top { color: #333; font-size: 12px; font-weight: bold; background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #dbdbdb, stop:1 #c1c1c1); border: none; border-bottom: 1px solid #989898; min-height: 38px; margin: 0px; padding-left: 20px; } NothingPlayingWidget QLabel#scrobble { margin: 10px 0px 20px; } NothingPlayingWidget QAbstractButton#itunes, NothingPlayingWidget QAbstractButton#applemusic, NothingPlayingWidget QAbstractButton#wmp, NothingPlayingWidget QAbstractButton#winamp, NothingPlayingWidget QAbstractButton#foobar { background-color: transparent; background-repeat: no-repeat; background-position: left; text-align: center left; border: none; height: 44px; width: 44px; margin: 0px; padding-left: 50px; } NothingPlayingWidget QAbstractButton#itunes { background-image: url(":/control_bar_scrobble_itunes.png"); } NothingPlayingWidget QAbstractButton#applemusic { background-image: url(":/control_bar_scrobble_applemusic.png"); } NothingPlayingWidget QAbstractButton#wmp { background-image: url(":/control_bar_scrobble_wmp.png"); } NothingPlayingWidget QAbstractButton#winamp { background-image: url(":/control_bar_scrobble_winamp.png"); } NothingPlayingWidget QAbstractButton#foobar { background-image: url(":/control_bar_scrobble_foobar.png"); } MetadataWidget QLabel#context { margin: 20px 20px; padding: 15px 15px; background-color: #ffffff; border: 1px solid #cdcdcd; border-radius: 5px; font-size: 12px; } ProfileWidget QLabel#userBlurb { margin: 12px 0px; padding: 15px 15px; background-color: #ffffff; border: 1px solid #cdcdcd; border-radius: 5px; font-size: 12px; } QuickStartWidget { border: none; margin: 14px 0px 10px 0px; } MetadataWidget { border: none; } MetadataWidget QFrame#scrollAreaWidgetContents { border: none; } MetadataWidget QPushButton#back { background-image: url(":/control_bar_back.png"); background-repeat: none; background-position: center left; background-origin: padding; color: #333; font-size: 12px; text-align: center left; background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #dbdbdb, stop:1 #c1c1c1); border: none; border-bottom: 1px solid #989898; border-left: 20px solid transparent; min-height: 38px; max-height: 38px; padding: 0px 0px 0px 25px; } MetadataWidget QFrame#trackDetails { border: none; margin: 20px 20px 0px 20px; } MetadataWidget HttpImageWidget#albumImage { color: transparent; background-color: white; qproperty-scaledContents: true; border: 1px solid #aaaaaa; min-width: 126px; min-height: 126px; max-width: 126px; max-height: 126px; padding: 4px; margin-right: 20px; } MetadataWidget QLabel#trackTitle { color: #333; font-size: 16px; font-weight: bold; } MetadataWidget QLabel#trackArtist { color: #333; } MetadataWidget ScrobbleControls { border: none; } MetadataWidget QLabel#trackAlbum { font-size: 12px; } MetadataWidget QFrame#trackTagsFrame { margin: 0px 25px 10px; } MetadataWidget QFrame#artistTagsFrame { margin: 0px 5px 10px; } MetadataWidget QLabel#trackPopTags, MetadataWidget QLabel#artistPopTags, MetadataWidget QLabel#trackYourTags, MetadataWidget QLabel#artistYourTags { font-size: 11px; } MetadataWidget QLabel#similarArtists { font-size: 16px; font-weight: bold; padding: 0px 2px 10px; border: none; } MetadataWidget QFrame#artistStats { padding: 20px 40px 20px 5px; } MetadataWidget QLabel#trackPlays, MetadataWidget QLabel#trackListeners, MetadataWidget QLabel#trackUserPlays, MetadataWidget QLabel#artistPlays, MetadataWidget QLabel#artistListeners, MetadataWidget QLabel#artistUserPlays { color: #333; font-size: 16px; font-weight: bold; } MetadataWidget QLabel#trackPlaysLabel, MetadataWidget QLabel#trackListenersLabel, MetadataWidget QLabel#trackUserPlaysLabel, MetadataWidget QLabel#artistPlaysLabel, MetadataWidget QLabel#artistListenersLabel, MetadataWidget QLabel#artistUserPlaysLabel { color: #898989; font-size: 10px; font-weight: normal; } MetadataWidget QFrame#content { background-color: red; } MetadataWidget QFrame#artistFrame { background-color: #dedede; padding: 20px 20px 20px; margin: 0px; } MetadataWidget QLabel#artistArtist { font-size: 16px; font-weight: bold; color: #333; padding: 0px 0px 5px; } MetadataWidget BioWidget#artistBio { background-color: #dedede; border: none; font-size: 12px; color: #333; } MetadataWidget QFrame#similarArtistFrame { margin-right: 20px; } MetadataWidget QFrame#similarArtistFrame QLabel { padding: 2px; border: 1px solid #aaaaaa; background-color: white; margin: 0px 10px 30px; color: #333; text-align: bottom left; font-size: 10px; background-origin: margin; min-width: 64px; max-width: 64px; max-height: 64px; max-height: 64px; } MetadataWidget PlayableItemWidget { color: white; background-color: transparent; min-height: 49px; max-height: 49px; qproperty-style: ThreePart; } ScrobblesListWidget { border: none; } ScrobblesWidget #noScrobbles { border: none; } ScrobblesWidget #noScrobbles QLabel { color: #333; } ScrobblesWidget #noScrobbles QLabel#title { font-weight: bold; font-size: 16px; } ScrobblesWidget #noScrobbles QLabel#description { font-size: 12px; } RefreshButton, QPushButton#more { background-position: center left; background-repeat: none; padding: 0px 10%; background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #eeeeee, stop:1 #e2e2e2); color: #777; border: none; border-bottom: 1px solid #a6a6a6; min-height: 38px; max-height: 38px; } RefreshButton:hover, QPushButton#more:hover { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #fcfcfc, stop:1 #e5e5e5); } RefreshButton:pressed, QPushButton#more:pressed { background-color: #d6d6d6; } TrackWidget { border: none; border-top: 1px solid white; border-bottom: 1px solid #aaa; background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #eeeeee, stop:1 #dddddd); } TrackWidget QFrame#frame { border: none; padding: 10px 20px; } TrackWidget:hover { background-color: #eeeeee; } TrackWidget:pressed { border-top: 1px solid #d6d6d6; background-color: #d6d6d6; } TrackWidget#nowPlaying { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #f2efc0, stop:1 #fffcca); } TrackWidget#nowPlaying:hover { background-color: #fffcca; } TrackWidget QLabel#trackTitle { font-weight: bold; font-size: 13px; } TrackWidget QLabel#artist { font-size: 13px; } TrackWidget QLabel#timestamp { font-size: 11px; } TrackWidget QPushButton#love, TrackWidget QPushButton#tag, TrackWidget QPushButton#share, TrackWidget QPushButton#buy { color: transparent; border: none; background-repeat: no-repeat; background-color: transparent; margin: 1px 0px 0px 0px; } TrackWidget QPushButton#love { background-image: url(":/meta_love_OFF_REST.png"); width: 21px; height: 18px; } TrackWidget QPushButton#love:hover { background-image: url(":/meta_love_OFF_HOVER.png"); } TrackWidget QPushButton#love:pressed { background-image: url(":/meta_love_OFF_PRESS.png"); } TrackWidget QPushButton#love:checked { background-image: url(":/meta_love_ON_REST.png"); } TrackWidget QPushButton#love:checked:hover { background-image: url(":/meta_love_ON_HOVER.png"); } TrackWidget QPushButton#love:checked:pressed { background-image: url(":/meta_love_ON_PRESS.png"); } TrackWidget QPushButton#tag { background-image: url(":/meta_tag_REST.png"); width: 18px; height: 18px; } TrackWidget QPushButton#tag:hover { background-image: url(":/meta_tag_HOVER.png"); } TrackWidget QPushButton#tag:pressed { background-image: url(":/meta_tag_PRESS.png"); } TrackWidget QPushButton#share { background-image: url(":/meta_share_REST.png"); width: 21px; height: 18px; } TrackWidget QPushButton#share:hover { background-image: url(":/meta_share_HOVER.png"); } TrackWidget QPushButton#share:pressed { background-image: url(":/meta_share_PRESS.png"); } TrackWidget QPushButton#buy { background-image: url(":/meta_buy_REST.png"); width: 21px; height: 18px; } TrackWidget QPushButton#buy:hover { background-image: url(":/meta_buy_HOVER.png"); } TrackWidget QPushButton#buy:pressed { background-image: url(":/meta_buy_PRESS.png"); } TrackWidget QPushButton#buy::menu-indicator { image: none; } FriendListWidget QListWidget { background-color: #eeeeee; border: none; } FriendListWidget QListWidget::item { border: none; } FriendListWidget QListWidget::item:first { border: none; } FriendListWidget QListWidget::item:last { border: none; } FriendListWidget QWidget#filterBackground { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #dbdbdb, stop:1 #c1c1c1); border-bottom: 1px solid #989898; } FriendListWidget QLineEdit { margin: 16px; border: 1px solid #989898; border-radius: 5px; padding: 5px; selection-background-color: darkgray; font-size: 14px; } FriendListWidget QListView::item:selected { background-color: #dddddd; } ScrobbleConfirmationDialog QLabel#invalidScrobbleWarning { color: red; } QuickStartWidget QLineEdit { border: 1px solid #cdcdcd; border-radius: 5px; padding: 10px 10px; selection-background-color: darkgray; font-size: 17px; } QuickStartWidget QPushButton { background-image: url(":/radio_play_large_REST.png"); background-color: transparent; background-repeat: no-repeat; color: transparent; border: none; height: 58px; width: 58px; margin: 0px 0px 0px 10px; } QuickStartWidget QPushButton:hover { background-image: url(":/radio_play_large_HOVER.png"); } QuickStartWidget QPushButton:pressed { background-image: url(":/radio_play_large_PRESS.png"); } QuickStartWidget QLabel#whyNotTry { margin: 2px 0px 2px 1px; font-size: 12px; } QScrollArea { border: none; } ProfileWidget { border: none; margin: 0px 20px; } RadioWidget QFrame#section { border: none; } ProfileWidget QFrame#user { border: none; margin: 20px 0px 10px; } FriendWidget { margin: 0px 20px; padding: 10px 0px; border: none; border-top: 1px solid white; border-bottom: 1px solid #cdcdcd; } FriendWidget QFrame#nowListening, FriendWidget QFrame#groupBox { border: 1px solid lightgray; border-radius: 5px; padding: 6px 10px; } FriendWidget QFrame#nowListening { background-color: #fffcca; } FriendWidget QFrame#groupBox { background-color: #dedede; } FriendWidget AvatarWidget#avatar, ProfileWidget AvatarWidget#avatar, ProfileArtistWidget QLabel#artistImage, ShareDialog HttpImageWidget#icon, TagDialog HttpImageWidget#icon, UserManagerWidget AvatarWidget#image, TrackWidget HttpImageWidget#albumArt{ padding: 2px; background-color: white; color: transparent; border: 1px solid #aaaaaa; min-width: 64px; min-height: 64px; max-width: 64px; max-height: 64px; margin: 0px 20px 8px 0px; } TrackWidget HttpImageWidget#albumArt { margin: 0px 10px 0px 0px; } ProfileWidget AvatarWidget#avatar { padding: 4px; min-width: 126px; min-height: 126px; max-width: 126px; max-height: 126px; } ProfileArtistWidget QLabel#artistImage { max-height: 52px; min-height: 52px; } FriendWidget QLabel#lastTrack { font-weight: bold; font-size: 12px; color: #333; } ProfileWidget QLabel#name { font-size: 16px; color: #333; } ProfileWidget QLabel#scrobbleCount, ProfileWidget QLabel#lovedCount { font-size: 14px; color: #333; } ProfileWidget QLabel#scrobbles, ProfileWidget QLabel#loved, ProfileWidget QLabel#infoString { font-size: 11px; color: #898989; } FriendWidget QLabel#username { font-weight: bold; font-size: 14px; color: #333; } FriendWidget QLabel#userDetails { font-size: 11px; color: #898989; } FriendWidget QLabel#timestamp { font-size: 11px; color: #333; } FriendListWidget QLabel#noFriends { color: #333; } FriendListWidget QWidget#noFriendsPage { border: none; margin: 20px; } FriendListWidget PushButton { color: #333; text-align: center center; font-weight: bold; background-color: transparent; min-height: 49px; max-height: 49px; } FriendWidget QLabel#equaliser, TrackWidget QLabel#equaliser { margin-right: 5px; } RadioWidget { border: none; margin: 0px 20px; } RadioWidget PlayableItemWidget, FriendWidget PlayableItemWidget, ProfileWidget PlayableItemWidget { background-color: transparent; background-repeat: no-repeat; border: none; text-align: top left; font-weight: bold; font-size: 14px; color: #333; height: 40px; padding: 3px 0px 3px 54px; margin: 5px 0px; } ProfileArtistWidget { border: none; margin: 10px 0px; } ProfileArtistWidget QLabel#artistImage { margin: 0px 20px 0px 0px; } ProfileArtistWidget QLabel#artistName { color: #333; font-size: 14px; font-weight: bold; } ProfileArtistWidget QLabel#plays { color: white; font-size: 11px; font-weight: bold; min-height: 20px; max-height: 20px; padding-left: 5px; } RadioWidget PlayableItemWidget#library { background-image: url(":/radio_library_REST.png"); } RadioWidget PlayableItemWidget#library:hover { background-image: url(":/radio_library_HOVER.png"); } RadioWidget PlayableItemWidget#library:pressed { background-image: url(":/radio_library_PRESS.png"); } RadioWidget PlayableItemWidget#mix { background-image: url(":/radio_mix_REST.png"); } RadioWidget PlayableItemWidget#mix:hover { background-image: url(":/radio_mix_HOVER.png"); } RadioWidget PlayableItemWidget#mix:pressed { background-image: url(":/radio_mix_PRESS.png"); } RadioWidget PlayableItemWidget#rec { background-image: url(":/radio_rec_REST.png"); } RadioWidget PlayableItemWidget#rec:hover { background-image: url(":/radio_rec_HOVER.png"); } RadioWidget PlayableItemWidget#rec:pressed { background-image: url(":/radio_rec_PRESS.png"); } RadioWidget PlayableItemWidget#friends, RadioWidget PlayableItemWidget#neighbours { background-image: url(":/radio_friends_REST.png"); } RadioWidget PlayableItemWidget#friends:hover, RadioWidget PlayableItemWidget#neighbours:hover { background-image: url(":/radio_friends_HOVER.png"); } RadioWidget PlayableItemWidget#friends:pressed, RadioWidget PlayableItemWidget#neighbours:pressed { background-image: url(":/radio_friends_PRESS.png"); } RadioWidget PlayableItemWidget#station, FriendWidget PlayableItemWidget, ProfileWidget PlayableItemWidget { background-image: url(":/radio_play_small_REST.png"); font-size: 12px; max-height: 24px; margin: 0px; padding: 0px; } RadioWidget PlayableItemWidget#station { qproperty-style: DescriptionElide; padding-left: 30px; text-align: left center; } FriendWidget PlayableItemWidget, ProfileWidget PlayableItemWidget { qproperty-style: DescriptionNone; color: transparent; max-width: 24px; } RadioWidget PlayableItemWidget#station:hover, FriendWidget PlayableItemWidget:hover, ProfileWidget PlayableItemWidget:hover { background-image: url(":/radio_play_small_HOVER.png"); } RadioWidget PlayableItemWidget#station:pressed, FriendWidget PlayableItemWidget:pressed, ProfileWidget PlayableItemWidget :pressed { background-image: url(":/radio_play_small_PRESS.png"); } RadioWidget QLabel#title, ProfileWidget QLabel#weekTitle, ProfileWidget QLabel#overallTitle { color: #333; padding: 0px 0px 10px; border: none; font-weight: bold; font-size: 15px; } RadioWidget QFrame#nonSubFrame { padding: 20px 10px 0px; background-repeat: none; background-image: url(":/subscribe_bg.png"); background-position: center top; } RadioWidget QFrame#nonSubFrame QLabel#title { color: #333; font-size: 18px; } RadioWidget QLabel#headphones { color: transparent; background-repeat: none; background-image: url(":/subscribe_radio.png"); min-width: 218px; min-height: 144px; margin: 20px 0px 30px; } RadioWidget QLabel#description { color: #333; font-weight: bold; font-size: 13px; margin-bottom: 20px; } RadioWidget PushButton { color: #333; text-align: center center; font-weight: bold; font-size: 14px; background-color: transparent; min-height: 49px; max-height: 49px; } RadioWidget PushButton#subscribe { color: #fff; qproperty-dark: true; } PlaybackControlsWidget { border: none; } PlaybackControlsWidget QLabel#icon { color: transparent; min-width: 44px; min-height: 44px; margin: 0px 14px; qproperty-alignment: AlignCenter; } PlaybackControlsWidget QFrame#statusFrame { padding: 13px 0px; } PlaybackControlsWidget QFrame#statusFrame QLabel { color: white; font-size: 12px; } PlaybackControlsWidget QFrame#statusFrame QLabel#device { font-weight: bold; font-size: 14px; } PlaybackControlsWidget[scrobbleTrack="false"] QFrame#details { max-height: 64px; min-height: 64px; } PlaybackControlsWidget[scrobbleTrack="false"] QFrame#details { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #3B3B3B, stop:0 #545454); border: none; border-bottom: 1px solid #272727; } PlaybackControlsWidget[scrobbleTrack="true"] QFrame#details { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #c1c1c1, stop:0 #dbdbdb); border: none; border-bottom: 1px solid #9f9f9f; } PlaybackControlsWidget[scrobbleTrack="true"] QFrame#statusFrame QLabel { color: #333; } PlaybackControlsWidget QFrame#buttons { margin: 0px 20px 0px 0px; } PlaybackControlsWidget QPushButton { background-color: transparent; background-repeat: no-repeat; border: none; color: transparent; height: 28px; } PlaybackControlsWidget QPushButton#love { background-image: url(":/controls_love_OFF_REST.png"); max-width: 29px; min-width: 29px; } PlaybackControlsWidget QPushButton#love:hover {background-image: url(":/controls_love_OFF_HOVER.png");} PlaybackControlsWidget QPushButton#love:pressed {background-image: url(":/controls_love_OFF_PRESS.png");} PlaybackControlsWidget QPushButton#love:checked {background-image: url(":/controls_love_ON_REST.png");} PlaybackControlsWidget QPushButton#love:checked:hover {background-image: url(":/controls_love_ON_HOVER.png");} PlaybackControlsWidget QPushButton#love:checked:pressed {background-image: url(":/controls_love_ON_PRESS.png");} PlaybackControlsWidget QPushButton#ban { background-image: url(":/controls_ban_REST.png"); max-width: 28px; min-width: 28px; } PlaybackControlsWidget QPushButton#ban:hover {background-image: url(":/controls_ban_HOVER.png");} PlaybackControlsWidget QPushButton#ban:pressed {background-image: url(":/controls_ban_PRESS.png");} PlaybackControlsWidget QPushButton#play { background-image: url(":/controls_play_REST.png"); max-width: 25px; min-width: 25px; } PlaybackControlsWidget QPushButton#play:hover {background-image: url(":/controls_play_HOVER.png");} PlaybackControlsWidget QPushButton#play:pressed {background-image: url(":/controls_play_PRESS.png");} PlaybackControlsWidget QPushButton#play:checked {background-image: url(":/controls_pause_REST.png");} PlaybackControlsWidget QPushButton#play:checked:hover {background-image: url(":/controls_pause_HOVER.png");} PlaybackControlsWidget QPushButton#play:checked:pressed {background-image: url(":/controls_pause_PRESS.png");} PlaybackControlsWidget QPushButton#skip { background-image: url(":/controls_skip_REST.png"); max-width: 39px; min-width: 39px; } PlaybackControlsWidget QPushButton#skip:hover { background-image: url(":/controls_skip_HOVER.png"); } PlaybackControlsWidget QPushButton#skip:pressed { background-image: url(":/controls_skip_PRESS.png"); } PlaybackControlsWidget QPushButton#back { background-image: url(":/controls_playlist_skip_back_REST.png"); max-width: 39px; min-width: 39px; } PlaybackControlsWidget QPushButton#back:hover { background-image: url(":/controls_playlist_skip_back_HOVER.png"); } PlaybackControlsWidget QPushButton#back:pressed { background-image: url(":/controls_playlist_skip_back_PRESS.png"); } PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#love {background-image: url(":/controls_playlist_love_OFF_REST.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#love:hover {background-image: url(":/controls_playlist_love_OFF_HOVER.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#love:pressed {background-image: url(":/controls_playlist_love_OFF_PRESS.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#love:checked {background-image: url(":/controls_playlist_love_ON_REST.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#love:checked:hover {background-image: url(":/controls_playlist_love_ON_HOVER.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#love:checked:pressed {background-image: url(":/controls_playlist_love_ON_PRESS.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#play {background-image: url(":/controls_playlist_play_REST.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#play:hover {background-image: url(":/controls_playlist_play_HOVER.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#play:pressed {background-image: url(":/controls_playlist_play_PRESS.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#play:checked {background-image: url(":/controls_playlist_pause_REST.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#play:checked:hover {background-image: url(":/controls_playlist_pause_HOVER.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#play:checked:pressed {background-image: url(":/controls_playlist_pause_PRESS.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#skip {background-image: url(":/controls_playlist_skip_REST.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#skip:hover { background-image: url(":/controls_playlist_skip_HOVER.png"); } PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#skip:pressed { background-image: url(":/controls_playlist_skip_PRESS.png"); } PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#playlist { background-image: url(":/controls_playlist_playlist_HOVER.png"); max-width: 28px; min-width: 28px; } PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#playlist:hover {background-image: url(":/controls_playlist_playlist_PRESS.png");} PlaybackControlsWidget[scrobbleTrack="true"] QPushButton#playlist:pressed {background-image: url(":/controls_playlist_playlist_REST.png");} PlaybackControlsWidget QFrame#progressFrame { border: none; border-top: 1px solid #e5e5e5; border-bottom: 1px solid #9b9b9b; background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #cccccc, stop:0 #e5e5e5); padding: 0px 20px; max-height: 24px; min-height: 24px; } PlaybackControlsWidget QFrame#progressFrame QLabel#message, PlaybackControlsWidget QFrame#progressFrame QLabel#time { font-size: 11px; color: #333; } ScrobbleControls QPushButton { background-color: transparent; color: transparent; background-repeat: no-repeat; background-position: center; margin: 0px; padding: 3px 0px; width: 20px; } ScrobbleControls QPushButton#love { background-image: url(":/meta_love_OFF_REST.png"); } ScrobbleControls QPushButton#love:hover { background-image: url(":/meta_love_OFF_HOVER.png"); } ScrobbleControls QPushButton#love:pressed { background-image: url(":/meta_love_OFF_PRESS.png"); } ScrobbleControls QPushButton#love:checked { background-image: url(":/meta_love_ON_REST.png"); } ScrobbleControls QPushButton#love:checked:hover { background-image: url(":/meta_love_ON_HOVER.png"); } ScrobbleControls QPushButton#love:checked:pressed { background-image: url(":/meta_love_ON_PRESS.png"); } ScrobbleControls QPushButton#tag { background-image: url(":/meta_tag_REST.png"); } ScrobbleControls QPushButton#tag:hover { background-image: url(":/meta_tag_HOVER.png"); } ScrobbleControls QPushButton#tag:pressed { background-image: url(":/meta_tag_PRESS.png"); } ScrobbleControls QPushButton#share { background-image: url(":/meta_share_REST.png"); } ScrobbleControls QPushButton#share:menu-indicator { background: transparent; } ScrobbleControls QPushButton#share:hover { background-image: url(":/meta_share_HOVER.png"); } ScrobbleControls QPushButton#share:pressed { background-image: url(":/meta_share_PRESS.png"); } ScrobbleControls QPushButton#buy { background-image: url(":/meta_buy_REST.png"); } ScrobbleControls QPushButton#buy:menu-indicator { background: transparent; } ScrobbleControls QPushButton#buy:hover { background-image: url(":/meta_buy_HOVER.png"); } ScrobbleControls QPushButton#buy:pressed { background-image: url(":/meta_buy_PRESS.png"); } BannerWidget { background-color: #dedede; margin: 6px 7px 0px 3px; } BannerWidget HttpImageWidget { border: 1px solid #aaaaaa; background-color: white; padding: 4px; } BannerWidgetPrivate { background-color: red; color: white; font-size: 11px; } ShareDialog QFrame#what, TagDialog QFrame#what { border: 1px solid #DBDBDB; border-radius: 5px; background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #ABABAB, stop:0.04 #E6E6E6, stop:1 #E6E6E6); padding: 18px; margin: 18px 0px; } ShareDialog > QRadioButton { margin-left: 10px; font-weight: bold; } ShareDialog > QLabel, ShareDialog > QCheckBox, ShareDialog > QRadioButton, TagDialog > QLabel, TagDialog > QCheckBox, TagDialog > QRadioButton { font-size: 12px; font-weight: bold; } ShareDialog QLabel#with { margin-bottom: 10px; font-size: 12px; } ShareDialog QLabel#messageTitle { margin-top: 16px; margin-bottom: 10px; } ItemSelectorWidget { background-color: white; } ItemSelectorWidget QLineEdit { padding: 1px 2px 2px 3px; margin: 2px; } ItemSelectorWidget QLabel { padding: 2px 3px; margin: 2px 0px 2px 2px; background-color: #DAE7F9; font-weight: bold; border: 1px solid #99BEEE; border-radius: 9px; min-width: 12px; } ShareDialog QTextEdit { margin-bottom: 16px; } ShareDialog QLabel#characterLimit { font-size: 11px; font-weight: normal; } ShareDialog QLabel#characterLimit[xerror="true"] { color: red; } ShareDialog QLabel#characterLimit, ShareDialog QCheckBox { margin-bottom: 16px; } StatusBar QFrame { border: none; background-color: transparent; margin: 1px 0px 2px; } StatusBar QPushButton#cog { border: 1px solid #cdcdcd; border-radius: 3px; background-repeat: no-repeat; background-position: center; background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #ffffff, stop:1 #e5e5e5); background-image: url(":/settings_REST.png"); width: 30px; height: 22px; margin: 0px 10px; } StatusBar QPushButton#cog:hover { background-image: url(":/settings_HOVER.png"); } StatusBar QPushButton#cog:pressed { background-image: url(":/settings_PRESS.png"); } StatusBar QLabel#scrobbleIcon { qproperty-pixmap: url(":/scrobble_OFF.png"); } PlaybackControlsWidget QSlider { padding: 0px 2px; } PlaybackControlsWidget VolumeSlider { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #cccccc, stop:0 #e5e5e5); } PlaybackControlsWidget Phonon--VolumeSlider { min-width: 60px; max-width: 60px; background-image: none; background-repeat: none; qproperty-muteVisible: false; height: 16px; } PlaybackControlsWidget QSlider::groove:horizontal { padding: 0px -7px; height: 16px; border-image: url(":/progress_slot_OFF.png") 0px 3px 0px 3px stretch; border-left-width: 3px; border-right-width: 3px; } PlaybackControlsWidget QSlider::sub-page:horizontal { border-image: url(":/progress_slot_ON.png") 0px 3px 0px 3px stretch; border-left-width: 3px; border-right-width: 3px; } PlaybackControlsWidget QProgressBar#progress { border-image: url(":/progress_slot_OFF.png") 0px 3px 0px 3px stretch; border-left-width: 3px; border-right-width: 3px; padding: 0px -3px; max-height: 16px; } PlaybackControlsWidget QProgressBar#progress::chunk { border-image: url(":/progress_slot_ON.png") 0px 3px 0px 3px stretch; border-left-width: 3px; border-right-width: 3px; } PlaybackControlsWidget QFrame#progressFrame QProgressBar#scrobbleMeter { background-image: url(":/scrobble_progress_OFF.png"); background-repeat: none; background-color: transparent; background-position: inherit center; min-height: 16px; max-height: 16px; width: 30px; border: none; qproperty-textVisible: false; } PlaybackControlsWidget QFrame#progressFrame QProgressBar#scrobbleMeter:chunk { background-image: url(":/scrobble_progress_ON.png"); min-height: 16px; max-height: 16px; background-repeat: none; } PlaybackControlsWidget QSlider::handle:horizontal { background-repeat: no-repeat; background-position: center; width: 16px; height: 16px; background-image: url(":/volume_knob_REST.png"); } PlaybackControlsWidget QSlider::handle:horizontal:pressed { background-image: url(":/volume_knob_PRESS.png"); } StatusBar QFrame#permanentWidget { margin: 0px 10px; } QDialog#AboutDialog QFrame#frame { margin: 10px 20px 20px; } QDialog#AboutDialog QLabel { font-size: 11px; } QDialog#AboutDialog QLabel#as { border: none; background-repeat: no-repeat; background-position: center; qproperty-pixmap: url(":/as.png"); qproperty-scaledContents: true; max-height: 64px; max-width: 64px; } IpodSettingsWidget unicorn--Label { font-size: 12px; color: #333; } ================================================ FILE: app/client/MainWindow.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "MainWindow.h" #include "Application.h" #include "Services/ScrobbleService.h" #include "Services/AnalyticsService.h" #include "MediaDevices/DeviceScrobbler.h" #include "lib/unicorn/dialogs/CloseAppsDialog.h" #include "../Widgets/ProfileWidget.h" #include "../Widgets/FriendListWidget.h" #include "../Widgets/ScrobbleControls.h" #include "../Widgets/NowPlayingStackedWidget.h" #include "../Widgets/ScrobblesWidget.h" #include "../Widgets/SideBar.h" #include "../Widgets/StatusBar.h" #include "../Widgets/TitleBar.h" #include "../Widgets/PlaybackControlsWidget.h" #include "../Widgets/NowPlayingWidget.h" #include "lib/unicorn/widgets/DataBox.h" #include "lib/unicorn/widgets/MessageBar.h" #include "lib/unicorn/widgets/GhostWidget.h" #include "lib/unicorn/widgets/UserToolButton.h" #include "lib/unicorn/widgets/MessageBar.h" #include "lib/unicorn/widgets/UserMenu.h" #include "lib/unicorn/qtwin.h" #include "lib/unicorn/widgets/SlidingStackedWidget.h" #include "lib/listener/PlayerConnection.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "lib/unicorn/Updater/Updater.h" #endif #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/DesktopServices.h" #ifdef Q_OS_WIN32 #include "lib/unicorn/plugins/PluginList.h" #endif #ifdef Q_OS_MAC void qt_mac_set_dock_menu(QMenu *menu); #endif const QString CONFIG_URL = "https://cdn.last.fm/client/config.xml"; MainWindow::MainWindow( QMenuBar* menuBar ) :unicorn::MainWindow( menuBar ) { hide(); #ifdef Q_OS_MAC setUnifiedTitleAndToolBarOnMac( true ); #else setMenuBar( menuBar ); #endif setCentralWidget(new QWidget); QVBoxLayout* layout = new QVBoxLayout( centralWidget() ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 0 ); layout->addWidget( ui.messageBar = new MessageBar( this ) ); QHBoxLayout* h = new QHBoxLayout(); h->setContentsMargins( 0, 0, 0, 0 ); h->setSpacing( 0 ); layout->addLayout( h ); h->addWidget( ui.sideBar = new SideBar( this ) ); h->addWidget( ui.stackedWidget = new unicorn::SlidingStackedWidget( this ) ); connect( ui.sideBar, SIGNAL(currentChanged(int)), ui.stackedWidget, SLOT(slide(int))); ui.stackedWidget->addWidget( ui.nowPlaying = new NowPlayingStackedWidget(this) ); ui.nowPlaying->setObjectName( "nowPlaying" ); ui.stackedWidget->addWidget( ui.scrobbles = new ScrobblesWidget( this ) ); ui.scrobbles->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::MinimumExpanding ); connect( ui.stackedWidget, SIGNAL(currentChanged(int)), ui.scrobbles, SLOT(onCurrentChanged(int)) ); ui.stackedWidget->addWidget( ui.profileScrollArea = new QScrollArea( this ) ); ui.profileScrollArea->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); ui.profileScrollArea->setWidget( ui.profile = new ProfileWidget(this) ); ui.profileScrollArea->setWidgetResizable( true ); ui.profile->setObjectName( "profile" ); connect( ui.stackedWidget, SIGNAL(currentChanged(int)), ui.profile, SLOT(onCurrentChanged(int)) ); ui.stackedWidget->addWidget( ui.friends = new FriendListWidget(this) ); ui.friends->setObjectName( "friends" ); connect( ui.stackedWidget, SIGNAL(currentChanged(int)), ui.friends, SLOT(onCurrentChanged(int)) ); ui.statusBar = new StatusBar( this ); ui.statusBar->setObjectName( "StatusBar" ); ui.statusBar->setSizeGripEnabled( false ); setWindowTitle( applicationName() ); setUnifiedTitleAndToolBarOnMac( true ); connect( &ScrobbleService::instance(), SIGNAL( trackStarted(lastfm::Track, lastfm::Track) ), SLOT( onTrackStarted(lastfm::Track, lastfm::Track) ) ); connect( &ScrobbleService::instance(), SIGNAL( paused() ), SLOT( onPaused() ) ); connect( &ScrobbleService::instance(), SIGNAL( resumed() ), SLOT( onResumed() ) ); connect( &ScrobbleService::instance(), SIGNAL( stopped() ), SLOT( onStopped() ) ); connect( &ScrobbleService::instance(), SIGNAL(foundIPodScrobbles(QList)), SLOT(onFoundScrobbles(QList))); new QShortcut( Qt::Key_Space, this, SLOT(onSpace()) ); //for some reason some of the stylesheet is not being applied properly unless reloaded //here. StyleSheets see very flaky to me. :s aApp->refreshStyleSheet(); setMinimumWidth( 540 ); setMaximumWidth( 800 ); setStatusBar( ui.statusBar ); // This is the default window size it will get changed // by finishUi if the app has ever been opened before resize( 565, 710 ); finishUi(); #ifdef Q_OS_WIN32 m_pluginList = new unicorn::PluginList( this ); QTimer::singleShot( 1000, this, SLOT(checkUpdatedPlugins()) ); #endif setupMenuBar(); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) m_updater = new Updater( this ); #endif #ifdef Q_OS_MAC QMenu* dockMenu = new QMenu(); ui.nowPlaying->nowPlaying()->playbackControls()->addToMenu( *dockMenu ); qt_mac_set_dock_menu( dockMenu ); #endif if (aApp->tray()) { ui.nowPlaying->nowPlaying()->playbackControls()->addToMenu( *aApp->tray()->contextMenu(), aApp->tray()->contextMenu()->actions()[3] ); } connect( lastfm::nam()->get( QNetworkRequest( CONFIG_URL ) ), SIGNAL(finished()), SLOT(onConfigRetrieved()) ); } void MainWindow::showMessage( const QString& message, const QString& id, int timeout ) { ui.messageBar->show( message, id, timeout ); } QString MainWindow::applicationName() { return QCoreApplication::applicationName(); } #ifdef Q_OS_WIN32 void MainWindow::checkUpdatedPlugins() { if ( m_pluginList->updatedList().count() > 0 ) { // one of the plugins has been updated so ask if they want to install them if ( QMessageBoxBuilder( this ).setText( tr( "There are updates to your media player plugins. Would you like to install them now?" ) ) .setTitle( "Updates to media player plugins" ) .setButtons( QMessageBox::Yes | QMessageBox::No ) .exec() == QMessageBox::Yes ) { unicorn::CloseAppsDialog* closeApps = new unicorn::CloseAppsDialog( m_pluginList->updatedList(), this ); if ( closeApps->result() != QDialog::Accepted ) closeApps->exec(); else closeApps->deleteLater(); if ( closeApps->result() == QDialog::Accepted ) { bool error = false; foreach ( unicorn::IPluginInfo* info, m_pluginList->updatedList() ) { info->setVerbose( false ); if ( !info->doInstall() ) error = true; info->setVerbose( true ); } if ( error ) { // Tell the user that QMessageBoxBuilder( this ).setTitle( tr( "Plugin install error", "", m_pluginList->updatedList().count() ) ) .setIcon( QMessageBox::Information ) .setText( tr( "

    There was an error updating your plugin(s).

    " "

    Please try again later.

    ", "", m_pluginList->updatedList().count() ) ) .setButtons( QMessageBox::Ok ) .exec(); } else { // Tell the user that QMessageBoxBuilder( this ).setTitle( tr( "Plugin(s) installed!", "", m_pluginList->updatedList().count() ) ) .setIcon( QMessageBox::Information ) .setText( tr( "

    Your plugin(s) ha(s|ve) been installed.

    " "

    You're now ready to scrobble with your media player(s)

    ", "", m_pluginList->updatedList().count() ) ) .setButtons( QMessageBox::Ok ) .exec(); } } else { // The user didn't close their media players QMessageBoxBuilder( this ).setTitle( tr( "Your plugins haven't been installed" ) ) .setIcon( QMessageBox::Warning ) .setText( tr( "You can install them later through the file menu" ) ) .setButtons( QMessageBox::Ok ) .exec(); } } } } #endif QString MainWindow::currentCategory() const { return ui.sideBar->currentCategory(); } void MainWindow::setupMenuBar() { /// File menu (should only show on non-mac) QMenu* fileMenu = appMenuBar()->addMenu( tr( "File" ) ); #ifdef Q_OS_WIN32 QMenu* pluginMenu = fileMenu->addMenu( tr( "Install plugins" ) ); foreach ( unicorn::IPluginInfo* info, m_pluginList->supportedList() ) { info->setVerbose( true ); pluginMenu->addAction( info->name(), info, SLOT(doInstall())); } #endif QAction* quit = fileMenu->addAction( tr("&Quit"), qApp, SLOT(quit()) ); quit->setMenuRole( QAction::QuitRole ); #ifdef Q_OS_WIN quit->setShortcut( Qt::ALT + Qt::Key_F4 ); #else quit->setShortcut( Qt::CTRL + Qt::Key_Q ); #endif /// View QMenu* viewMenu = appMenuBar()->addMenu( tr("View") ); ui.sideBar->addToMenu( *viewMenu ); viewMenu->addSeparator(); viewMenu->addAction( tr( "My Last.fm Profile" ), this, SLOT(onVisitProfile()), Qt::CTRL + Qt::Key_P ); /// Scrobbles QMenu* scrobblesMenu = appMenuBar()->addMenu( tr("Scrobbles") ); scrobblesMenu->addAction( tr( "Refresh" ), ui.scrobbles, SLOT(refresh()), Qt::CTRL + Qt::SHIFT + Qt::Key_R ); /// Controls QMenu* controlsMenu = appMenuBar()->addMenu( tr("Controls") ); ui.nowPlaying->nowPlaying()->playbackControls()->addToMenu( *controlsMenu ); /// Account appMenuBar()->addMenu( new UserMenu( this ) )->setText( tr( "Account" ) ); /// Tools (should only show on non-mac) QMenu* toolsMenu = appMenuBar()->addMenu( tr("Tools") ); #ifndef Q_WS_X11 QAction* c4u = toolsMenu->addAction( tr("Check for Updates"), this, SLOT(checkForUpdates()) ); c4u->setMenuRole( QAction::ApplicationSpecificRole ); #endif QAction* prefs = toolsMenu->addAction( tr("Options"), this, SLOT(onPrefsTriggered()) ); prefs->setMenuRole( QAction::PreferencesRole ); /// Window QMenu* windowMenu = appMenuBar()->addMenu( tr("Window") ); windowMenu->addAction( tr( "Minimize" ), this, SLOT(onMinimizeTriggered()), Qt::CTRL + Qt::Key_M ); windowMenu->addAction( tr( "Zoom" ), this, SLOT(onZoomTriggered()) ); //windowMenu->addSeparator(); //windowMenu->addAction( tr( "Last.fm" ), this, SLOT(onZoomTriggered()) ); windowMenu->addSeparator(); windowMenu->addAction( tr( "Bring All to Front" ), this, SLOT(onBringAllToFrontTriggered()) ); /// Help QMenu* helpMenu = appMenuBar()->addMenu( tr("Help") ); QAction* about = helpMenu->addAction( tr("About"), aApp, SLOT(onAboutTriggered()) ); about->setMenuRole( QAction::AboutRole ); helpMenu->addSeparator(); helpMenu->addAction( tr("FAQ"), aApp, SLOT(onFaqTriggered()) ); helpMenu->addAction( tr("Forums"), aApp, SLOT(onForumsTriggered()) ); helpMenu->addSeparator(); helpMenu->addAction( tr("Tour"), aApp, SLOT(onTourTriggered()) ); helpMenu->addSeparator(); helpMenu->addAction( tr("Show Licenses"), aApp, SLOT(onLicensesTriggered()) ); #ifndef Q_WS_X11 // it's only the scrobble log tab at the moment so no use on linux helpMenu->addSeparator(); helpMenu->addAction( tr("Diagnostics"), aApp, SLOT(onDiagnosticsTriggered()) ); #endif } void MainWindow::onSpace() { aApp->playAction()->trigger(); } void MainWindow::onConfigRetrieved() { } void MainWindow::onVisitProfile() { unicorn::DesktopServices::openUrl( aApp->currentSession().user().www() ); AnalyticsService::instance().sendEvent( aApp->currentCategory(), LINK_CLICKED, "ProfileURLClicked"); } void MainWindow::showEvent(QShowEvent *) { if ( m_preferences ) m_preferences->show(); m_menuBar->show(); m_menuBar->activateWindow(); #ifdef Q_OS_MAC if ( !m_installer ) { m_installer = new unicorn::ITunesPluginInstaller( this ); QTimer::singleShot( 1000, m_installer, SLOT(install()) ); } #endif } void MainWindow::hideEvent(QHideEvent *) { if ( m_preferences ) m_preferences->hide(); } void MainWindow::onPrefsTriggered() { if ( !m_preferences ) m_preferences = new PreferencesDialog( 0, this ); m_preferences->show(); m_preferences->activateWindow(); m_preferences->adjustSize(); AnalyticsService::instance().sendEvent( aApp->currentCategory(), BASIC_SETTINGS, "SettingsOpened"); } void MainWindow::onDiagnosticsTriggered() { if ( !m_diagnostics ) m_diagnostics = new DiagnosticsDialog( this ); m_diagnostics->show(); m_diagnostics->activateWindow(); } void MainWindow::setBetaUpdates( bool betaUpdates ) { #if defined(Q_OS_MAC) || defined(Q_OS_WIN) if ( m_updater ) m_updater->setBetaUpdates( betaUpdates ); #endif } void MainWindow::onMinimizeTriggered() { setWindowState( Qt::WindowMinimized ); } void MainWindow::onZoomTriggered() { setWindowState( Qt::WindowMaximized ); } void MainWindow::onBringAllToFrontTriggered() { return; } void MainWindow::checkForUpdates() { #if defined(Q_OS_MAC) || defined(Q_OS_WIN) m_updater->checkForUpdates(); #endif } void MainWindow::onTrackStarted( const lastfm::Track& t, const lastfm::Track& /*previous*/ ) { m_currentTrack = t; setWindowTitle( tr( "%1 - %2" ).arg( t.toString(), applicationName() ) ); } void MainWindow::onStopped() { m_currentTrack = Track(); setWindowTitle( applicationName() ); } void MainWindow::onResumed() { setWindowTitle( tr( "%1 - %2" ).arg( m_currentTrack.toString(), applicationName() ) ); } void MainWindow::onPaused() { setWindowTitle( tr( "%1" ).arg( applicationName() ) ); } void MainWindow::onFoundScrobbles( const QList& tracks ) { if ( tracks.count() > 0 ) { ui.messageBar->addTracks( tracks ); int count = 0; foreach ( const lastfm::Track& track, ui.messageBar->tracks() ) count += track.extra( "playCount" ).toInt(); ui.messageBar->show( tr( "%n play(s) ha(s|ve) been scrobbled from a device", "", count ), "ipod" ); } } void MainWindow::addWinThumbBarButton( QAction* thumbButtonAction ) { m_buttons.append( thumbButtonAction ); } void MainWindow::addWinThumbBarButtons( QList& thumbButtonActions ) { foreach ( QAction* button, m_buttons ) thumbButtonActions.append( button ); } ================================================ FILE: app/client/MainWindow.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef METADATA_WINDOW_H_ #define METADATA_WINDOW_H_ #include #include #include #include "lib/unicorn/UnicornMainWindow.h" #ifdef Q_OS_MAC #include "lib/unicorn/plugins/ITunesPluginInstaller.h" #endif #include "Settings/PreferencesDialog.h" #include "Dialogs/DiagnosticsDialog.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) namespace unicorn { class Updater; class PluginList; } using unicorn::Updater; #endif using lastfm::XmlQuery; class QAbstractButton; class QTabBar; class QLabel; class ScrobbleControls; class FirstRunWizard; class MessageBar; class SlideOverLayout; class UserToolButton; class IpodDevice; class MainWindow : public unicorn::MainWindow { Q_OBJECT struct{ class TitleBar* titleBar; class StatusBar* statusBar; class MessageBar* messageBar; class SideBar* sideBar; class QStackedWidget* stackedWidget; class NowPlayingStackedWidget* nowPlaying; class ScrobblesWidget* scrobbles; class QScrollArea* profileScrollArea; QWidget* profile; class QScrollArea* friendsScrollArea; QWidget* friends; } ui; public: MainWindow( QMenuBar* ); const Track& currentTrack() const{ return m_currentTrack; } void addWinThumbBarButton( QAction* ); void setBetaUpdates( bool betaUpdates ); QString currentCategory() const; signals: void trackGotInfo(XmlQuery); void albumGotInfo(XmlQuery); void artistGotInfo(XmlQuery); void artistGotEvents(XmlQuery); void trackGotTopFans(XmlQuery); void trackGotTags(XmlQuery); void finished(); public slots: void onPrefsTriggered(); void onDiagnosticsTriggered(); void onMinimizeTriggered(); void onZoomTriggered(); void onBringAllToFrontTriggered(); void showMessage( const QString& message, const QString& id = "", int timeout = -1 /*seconds*/ ); private slots: void onVisitProfile(); void onTrackStarted(const lastfm::Track&, const lastfm::Track&); void onStopped(); void onPaused(); void onResumed(); void checkForUpdates(); void onSpace(); void onConfigRetrieved(); #ifdef Q_OS_WIN32 void checkUpdatedPlugins(); #endif // iPod scrobbling things void onFoundScrobbles( const QList& tracks ); private: void setCurrentWidget( QWidget* ); void addWinThumbBarButtons( QList& ); void setupMenuBar(); void showEvent(QShowEvent *); void hideEvent(QHideEvent *); static QString applicationName(); private: Track m_currentTrack; class ActivityListItem* m_currentActivity; QList m_buttons; QPointer m_preferences; QPointer m_diagnostics; #if defined(Q_OS_MAC) || defined(Q_OS_WIN) QPointer m_updater; #endif #ifdef Q_OS_WIN QPointer m_pluginList; #endif #ifdef Q_WS_MAC QPointer m_installer; #endif }; #endif //METADATA_WINDOW_H_ ================================================ FILE: app/client/MediaDevices/DeviceScrobbler.cpp ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "lib/unicorn/dialogs/ScrobbleConfirmationDialog.h" #include "lib/unicorn/UnicornApplication.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include "../Application.h" #include "lib/unicorn/widgets/Label.h" #include "lib/unicorn/dialogs/CloseAppsDialog.h" #include "IpodDevice.h" #include "DeviceScrobbler.h" #include "../Services/ScrobbleService/ScrobbleService.h" #ifdef Q_WS_X11 #include #endif #include #include #include #ifdef Q_OS_MAC // Check for iTunes playcount difference once every 3 minutes // (usually takes about 1 sec on Mac) #define BACKGROUND_CHECK_INTERVAL 3 * 60 * 1000 #else // On Windows the iPod scrobble check can take around 90 seconds // for a fairly large library, so only run it every 30 minutes #define BACKGROUND_CHECK_INTERVAL 30 * 60 * 1000 #endif QString getIpodMountPath(); DeviceScrobbler::DeviceScrobbler( QObject *parent ) :QObject( parent ) { connect( this, SIGNAL(error(QString)), aApp, SIGNAL(error(QString))); m_twiddlyTimer = new QTimer( this ); connect( m_twiddlyTimer, SIGNAL(timeout()), SLOT(twiddle()) ); m_twiddlyTimer->start( BACKGROUND_CHECK_INTERVAL ); // run once 3 seconds after starting up QTimer::singleShot( 3 * 1000, this, SLOT(twiddle()) ); } DeviceScrobbler::~DeviceScrobbler() { if ( m_confirmDialog ) m_confirmDialog->deleteLater(); // make sure we terminate twiddly we could be clsoed due // to the installer and twiddly would stop install working if ( m_twiddly ) m_twiddly->deleteLater(); } bool DeviceScrobbler::isITunesPluginInstalled() { #ifdef Q_OS_WIN QSettings settings( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Last.fm\\Client\\Plugins\\itw", QSettings::NativeFormat ); QFile pluginFile( settings.value( "Path" ).toString() ); return pluginFile.exists(); #else return true; #endif } void DeviceScrobbler::twiddle() { doTwiddle( false ); } DeviceScrobbler::DoTwiddlyResult DeviceScrobbler::doTwiddle( bool manual ) { #ifndef Q_WS_X11 if ( unicorn::CloseAppsDialog::isITunesRunning() ) { if ( isITunesPluginInstalled() ) { if ( m_twiddly ) { qWarning() << "m_twiddly already running. Early out."; return AlreadyRunning; } //"--device diagnostic --vid 0000 --pid 0000 --serial UNKNOWN QStringList args; if ( sender() ) args << "--device" << "background"; else args << "--device" << "diagnostic"; args << "--vid" << "0000"; args << "--pid" << "0000"; if ( manual ) { args << "--serial" << "manual"; args += "--manual"; } else args << "--serial" << "automatic"; m_twiddly = new QProcess( this ); connect( m_twiddly, SIGNAL(finished( int, QProcess::ExitStatus )), SLOT(onTwiddlyFinished( int, QProcess::ExitStatus )) ); connect( m_twiddly, SIGNAL(error( QProcess::ProcessError )), SLOT(onTwiddlyError( QProcess::ProcessError )) ); #ifdef Q_OS_WIN m_twiddly->start( QDir( QCoreApplication::applicationDirPath() ).absoluteFilePath( "iPodScrobbler.exe" ), args ); #else m_twiddly->start( QDir( QCoreApplication::applicationDirPath() ).absoluteFilePath( "../Helpers/iPodScrobbler" ), args ); #endif return Started; } else return ITunesPluginNotInstalled; } #endif // Q_WS_X11 return ITunesNotRunning; } void DeviceScrobbler::onTwiddlyFinished( int exitCode, QProcess::ExitStatus exitStatus ) { qDebug() << exitCode << exitStatus; m_twiddly->deleteLater(); } void DeviceScrobbler::onTwiddlyError( QProcess::ProcessError error ) { qDebug() << error; m_twiddly->deleteLater(); } void DeviceScrobbler::checkCachedIPodScrobbles() { QStringList files; // check if there are any iPod scrobbles in its folder QDir scrobblesDir = lastfm::dir::runtimeData(); if ( scrobblesDir.cd( "devices" ) ) { QDirIterator iterator( scrobblesDir, QDirIterator::Subdirectories ); while ( iterator.hasNext() ) { iterator.next(); if ( iterator.fileInfo().isFile() ) { QString filename = iterator.fileName(); if ( filename.endsWith(".xml") ) files << iterator.fileInfo().absoluteFilePath(); } } } scrobbleIpodFiles( files ); } void DeviceScrobbler::handleMessage( const QStringList& message ) { int pos = message.indexOf( "--twiddly" ); const QString& action = message[ pos + 1 ]; if( action == "complete" ) twiddled( message ); else if ( action == "incompatible-plugin" ) emit error( tr( "Device scrobbling disabled - incompatible iTunes plugin - %1" ) .arg( unicorn::Label::anchor( "plugin", tr("please update" ) ) ) ); } void DeviceScrobbler::iPodDetected( const QStringList& /*arguments*/ ) { } void DeviceScrobbler::twiddled( const QStringList& arguments ) { // iPod scrobble time! QString iPodPath = arguments[ arguments.indexOf( "--ipod-path" ) + 1 ]; if ( !arguments.contains( "no-tracks-found" ) ) scrobbleIpodFiles( QStringList( iPodPath ) ); } void DeviceScrobbler::scrobbleIpodFiles( const QStringList& files ) { qDebug() << files; bool removeFiles = false; if ( unicorn::OldeAppSettings().deviceScrobblingEnabled() ) { QList scrobbles = scrobblesFromFiles( files ); // TODO: fix the root cause of this problem // If there are more than 4000 scrobbles we assume there was an error with the // iPod scrobbling diff checker so discard these scrobbles. // 4000 because 16 waking hours a day, for two weeks, with 3.5 minute songs if ( scrobbles.count() >= 4000 ) removeFiles = true; else { if ( scrobbles.count() > 0 ) { if ( unicorn::AppSettings().alwaysAsk() || scrobbles.count() >= 200 ) // always get them to check scrobbles over 200 { if ( !m_confirmDialog ) { m_confirmDialog = new ScrobbleConfirmationDialog( scrobbles, aApp->mainWindow() ); connect( m_confirmDialog, SIGNAL(finished(int)), SLOT(onScrobblesConfirmationFinished(int)) ); } else m_confirmDialog->addTracks( scrobbles ); // add the files so it can delete them when the user has decided what to do m_confirmDialog->addFiles( files ); m_confirmDialog->show(); } else { // sort the iPod scrobbles before caching them if ( scrobbles.count() > 1 ) qSort ( scrobbles.begin(), scrobbles.end() ); emit foundScrobbles( scrobbles ); // we're scrobbling them so remove the source files removeFiles = true; } } else // there were no scrobbles in the files so remove them removeFiles = true; } } else // device scrobbling is disabled so remove these files removeFiles = true; if ( removeFiles ) foreach ( QString file, files ) QFile::remove( file ); } QList DeviceScrobbler::scrobblesFromFiles( const QStringList& files ) { QList scrobbles; foreach ( const QString file, files ) { QFile iPodScrobbleFile( file ); if ( iPodScrobbleFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) { QDomDocument iPodScrobblesDoc; iPodScrobblesDoc.setContent( &iPodScrobbleFile ); QDomNodeList tracks = iPodScrobblesDoc.elementsByTagName( "track" ); for ( int i(0) ; i < tracks.count() ; ++i ) { lastfm::Track track( tracks.at(i).toElement() ); // don't add tracks to the list if they don't have an artist // don't add podcasts to the list if podcast scrobbling is off // don't add videos to the list (well, videos that aren't "music video") // don't add tracks if they are in excluded folders if ( !track.artist().isNull() && ( unicorn::UserSettings().value( "podcasts", true ).toBool() || !track.isPodcast() ) && !track.isVideo() && !ScrobbleService::isDirExcluded( track ) ) scrobbles << track; } } } return scrobbles; } void DeviceScrobbler::onScrobblesConfirmationFinished( int result ) { if ( result == QDialog::Accepted ) { QList scrobbles = m_confirmDialog->tracksToScrobble(); // sort the iPod scrobbles before caching them if ( scrobbles.count() > 1 ) qSort ( scrobbles.begin(), scrobbles.end() ); emit foundScrobbles( scrobbles ); unicorn::AppSettings().setAlwaysAsk( !m_confirmDialog->autoScrobble() ); } // delete all the iPod scrobble files whether it was accepted or not foreach ( const QString file, m_confirmDialog->files() ) QFile::remove( file ); m_confirmDialog->deleteLater(); } #ifdef Q_WS_X11 void DeviceScrobbler::onScrobbleIpodTriggered() { if ( iPod ) { qDebug() << "deleting ipod..."; delete iPod; } qDebug() << "here"; iPod = new IpodDeviceLinux; QString path; bool autodetectionSuceeded = true; if ( !iPod->autodetectMountPath() ) { path = getIpodMountPath(); iPod->setMountPath( path ); autodetectionSuceeded = false; } if ( autodetectionSuceeded || !path.isEmpty() ) { connect( iPod, SIGNAL( scrobblingCompleted( int ) ), this, SLOT( scrobbleIpodTracks( int ) ) ); connect( iPod, SIGNAL( calculatingScrobbles( int ) ), this, SLOT( onCalculatingScrobbles( int ) ) ); connect( iPod, SIGNAL( errorOccurred() ), this, SLOT( onIpodScrobblingError() ) ); iPod->fetchTracksToScrobble(); } } QString getIpodMountPath() { QString path = ""; QFileDialog dialog( 0, QObject::tr( "Where is your iPod mounted?" ), "/" ); dialog.setOption( QFileDialog::ShowDirsOnly, true ); dialog.setFileMode( QFileDialog::Directory ); //The following lines are to make sure the QFileDialog looks native. QString backgroundColor( "transparent" ); dialog.setStyleSheet( "QDockWidget QFrame{ background-color: " + backgroundColor + "; }" ); if ( dialog.exec() ) { path = dialog.selectedFiles()[ 0 ]; } return path; } void DeviceScrobbler::onCalculatingScrobbles( int trackCount ) { qApp->setOverrideCursor( Qt::WaitCursor ); } void DeviceScrobbler::scrobbleIpodTracks( int trackCount ) { qApp->restoreOverrideCursor(); qDebug() << trackCount << " new tracks to scrobble."; bool bootStrapping = false; if ( iPod->lastError() != IpodDeviceLinux::NoError && !iPod->isDeviceKnown() ) { bootStrapping = true; qDebug() << "Should we save it?"; int result = QMessageBoxBuilder( 0 ) .setIcon( QMessageBox::Question ) .setTitle( tr( "Scrobble iPod" ) ) .setText( tr( "Do you want to associate the device %1 to your audioscrobbler user account?" ).arg( iPod->deviceName() ) ) .setButtons( QMessageBox::Yes | QMessageBox::No ) .exec(); if ( result == QMessageBox::Yes ) { iPod->associateDevice(); QMessageBoxBuilder( 0 ) .setIcon( QMessageBox::Information ) .setTitle( tr( "Scrobble iPod" ) ) .setText( tr( "Device successfully associated to your user account. " "From now on you can scrobble the tracks you listen on this device." ) ) .exec(); } else { IpodDeviceLinux::deleteDeviceHistory( qobject_cast( qApp )->currentSession().user().name(), iPod->deviceId() ); } } QList tracks = iPod->tracksToScrobble(); if ( tracks.count() ) { if ( !bootStrapping ) { if( unicorn::UserSettings().value( "confirmIpodScrobbles", false ).toBool() ) { qDebug() << "showing confirm dialog"; ScrobbleConfirmationDialog confirmDialog( tracks ); if ( confirmDialog.exec() == QDialog::Accepted ) { tracks = confirmDialog.tracksToScrobble(); // sort the iPod scrobbles before caching them if ( tracks.count() > 1 ) qSort ( tracks.begin(), tracks.end() ); emit foundScrobbles( tracks ); } } else { // sort the iPod scrobbles before caching them if ( tracks.count() > 1 ) qSort ( tracks.begin(), tracks.end() ); emit foundScrobbles( tracks ); QMessageBoxBuilder( 0 ) .setIcon( QMessageBox::Information ) .setTitle( tr( "Scrobble iPod" ) ) .setText( tr( "%1 tracks scrobbled." ).arg( tracks.count() ) ) .exec(); } } } else if ( !iPod->lastError() ) { QMessageBoxBuilder( 0 ) .setIcon( QMessageBox::Information ) .setTitle( tr( "Scrobble iPod" ) ) .setText( tr( "No tracks to scrobble since your last sync." ) ) .exec(); qDebug() << "No tracks to scrobble"; } delete iPod; iPod = 0; } void DeviceScrobbler::onIpodScrobblingError() { qDebug() << "iPod Error"; qApp->restoreOverrideCursor(); QString path; switch( iPod->lastError() ) { case IpodDeviceLinux::AutodetectionError: //give it another try qDebug() << "giving another try"; path = getIpodMountPath(); if ( !path.isEmpty() ) { iPod->setMountPath( path ); iPod->fetchTracksToScrobble(); } break; case IpodDeviceLinux::AccessError: QMessageBoxBuilder( 0 ) .setIcon( QMessageBox::Critical ) .setTitle( tr( "Scrobble iPod" ) ) .setText( tr( "The iPod database could not be opened." ) ) .exec(); delete iPod; iPod = 0; break; case IpodDeviceLinux::UnknownError: QMessageBoxBuilder( 0 ) .setIcon( QMessageBox::Critical ) .setTitle( tr( "Scrobble iPod" ) ) .setText( tr( "An unknown error occurred while trying to access the iPod database." ) ) .exec(); delete iPod; iPod = 0; break; default: qDebug() << "untracked error:" << iPod->lastError(); } } #endif ================================================ FILE: app/client/MediaDevices/DeviceScrobbler.h ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef DEVICE_SCROBBLER_H_ #define DEVICE_SCROBBLER_H_ #include #include #include "lib/unicorn/UnicornSession.h" #include "lib/unicorn/UnicornSettings.h" #include #include "IpodDevice.h" #ifdef Q_WS_X11 #include #include "IpodDevice_linux.h" #endif using unicorn::Session; class ScrobbleConfirmationDialog; class DeviceScrobbler : public QObject { Q_OBJECT public: enum DoTwiddlyResult { Started, AlreadyRunning, ITunesNotRunning, ITunesPluginNotInstalled }; public: explicit DeviceScrobbler( QObject* parent = 0 ); ~DeviceScrobbler(); DoTwiddlyResult doTwiddle( bool manual ); signals: void foundScrobbles( const QList& tracks ); void error( const QString& message ); public slots: #ifdef Q_WS_X11 void onScrobbleIpodTriggered(); #endif private slots: #ifdef Q_WS_X11 void onCalculatingScrobbles( int trackCount ); void scrobbleIpodTracks( int trackCount ); void onIpodScrobblingError(); #endif void twiddle(); void onTwiddlyFinished( int, QProcess::ExitStatus ); void onTwiddlyError( QProcess::ProcessError ); void onScrobblesConfirmationFinished( int result ); void checkCachedIPodScrobbles(); public: void handleMessage( const QStringList& ); void iPodDetected( const QStringList& arguments ); private: #ifdef Q_WS_X11 QPointer iPod; #endif bool isITunesPluginInstalled(); void twiddled( const QStringList& arguments ); void scrobbleIpodFiles( const QStringList& files ); QList scrobblesFromFiles( const QStringList& files ); lastfm::User associatedUser( QString deviceId ); private: QPointer m_twiddly; QTimer* m_twiddlyTimer; QPointer m_confirmDialog; }; #endif //DEVICE_SCROBBLER_H_ ================================================ FILE: app/client/MediaDevices/IpodDevice.cpp ================================================ /* Copyright 2005-2010 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/UnicornSession.h" #include #include #include #include #include #include #include "IpodDevice.h" IpodDevice::IpodDevice( const QString& deviceId, const QString& deviceName ) : m_deviceId( deviceId ), m_deviceName( deviceName ) {} QString IpodDevice::deviceId() const { return m_deviceId; } QString IpodDevice::deviceName() const { return m_deviceName; } void IpodDevice::setSetting( QString key, QVariant value ) { // Find the setting for this QList roster = unicorn::Settings().userRoster(); bool found = false; foreach( lastfm::User user, roster ) { unicorn::UserSettings us( user.name() ); int count = us.beginReadArray( "associatedDevices" ); for ( int i = 0; i < count; i++ ) { us.setArrayIndex( i ); if ( us.value( "deviceId" ).toString() == m_deviceId ) { us.setValue( key, value ); found = true; break; } } us.endArray(); if ( found ) break; } } QVariant IpodDevice::setting( QString key, QVariant defaultValue ) { QVariant value; // Find the setting for this QList roster = unicorn::Settings().userRoster(); bool found = false; foreach( lastfm::User user, roster ) { unicorn::UserSettings us( user.name() ); int count = us.beginReadArray( "associatedDevices" ); for ( int i = 0; i < count; i++ ) { us.setArrayIndex( i ); if ( us.value( "deviceId" ).toString() == m_deviceId ) { value = us.value( key, defaultValue ); found = true; break; } } us.endArray(); if ( found ) break; } return value; } ================================================ FILE: app/client/MediaDevices/IpodDevice.h ================================================ /* Copyright 2005-2010 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef IPOD_DEVICE_H #define IPOD_DEVICE_H #include "MediaDevice.h" class IpodDevice: public MediaDevice { Q_OBJECT public: // DO NOT CHANGE THIS: IT WILL MESS UP IPOD SCROBBLE SETTINGS IpodDevice( const QString& deviceId, const QString& deviceName); virtual QString deviceId() const; virtual QString deviceName() const; #ifdef Q_WS_X11 /** * @return The mount path of the device. */ virtual QString mountPath() const{ return QString(); } #endif private: void setSetting( QString key, QVariant value ); QVariant setting( QString key, QVariant defaultValue ); private: QString m_deviceId; QString m_deviceName; }; #endif // IPOD_DEVICE_H ================================================ FILE: app/client/MediaDevices/IpodDevice_linux.cpp ================================================ /* Copyright 2005-2010 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "Application.h" #include "IpodDevice_linux.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/UnicornSession.h" #include #include #include #include #include #include extern "C" { #include #include } IpodTracksFetcher::IpodTracksFetcher( Itdb_iTunesDB *itdb, QSqlDatabase scrobblesdb, const QString& tableName, const QString& ipodModel ) { m_itdb = itdb; m_tableName = tableName; m_scrobblesdb = scrobblesdb; m_ipodModel = ipodModel; } void IpodTracksFetcher::run() { fetchTracks(); exec(); } void IpodTracksFetcher::fetchTracks() { GList *cur; for ( cur = m_itdb->tracks; cur; cur = cur->next ) { Itdb_Track *iTrack = ( Itdb_Track * )cur->data; if ( !iTrack ) continue; int newPlayCount = iTrack->playcount - previousPlayCount( iTrack ); QDateTime time; time.setTime_t( iTrack->time_played ); if ( time.toTime_t() == 0 ) continue; QDateTime prevPlayTime = previousPlayTime( iTrack ); //this logic takes into account that sometimes the itdb track play count is not //updated correctly (or libgpod doesn't get it right), //so we rely on the track play time too, which seems to be right most of the time if ( ( iTrack->playcount > 0 && newPlayCount > 0 ) || time > prevPlayTime ) { Track lstTrack; setTrackInfo( lstTrack, iTrack ); if ( newPlayCount == 0 ) newPlayCount++; //add the track to the list as many times as the updated playcount. for ( int i = 0; i < newPlayCount; i++ ) { m_tracksToScrobble.append( lstTrack ); } commit( iTrack ); } } qDebug() << "tracks fetching finished"; exit(); } void IpodTracksFetcher::setTrackInfo( Track& lstTrack, Itdb_Track* iTrack ) { MutableTrack( lstTrack ).setArtist( QString::fromUtf8( iTrack->artist ) ); MutableTrack( lstTrack ).setAlbum( QString::fromUtf8( iTrack->album ) ); MutableTrack( lstTrack ).setTitle( QString::fromUtf8( iTrack->title ) ); MutableTrack( lstTrack ).setSource( Track::MediaDevice ); if ( iTrack->mediatype & ITDB_MEDIATYPE_PODCAST ) MutableTrack( lstTrack ).setPodcast( true ); QDateTime t; t.setTime_t( iTrack->time_played ); MutableTrack( lstTrack ).setTimeStamp( t ); MutableTrack( lstTrack ).setDuration( iTrack->tracklen / 1000 ); // set duration in seconds MutableTrack( lstTrack ).setExtra( "playerName", "iPod " + m_ipodModel ); } void IpodTracksFetcher::commit( Itdb_Track* iTrack ) { QSqlQuery query( m_scrobblesdb ); QString sql = "REPLACE INTO " + m_tableName + " ( playcount, lastplaytime, id ) VALUES( %1, %2, %3 )"; query.exec( sql.arg( iTrack->playcount ).arg( iTrack->time_played ).arg( iTrack->id ) ); if( query.lastError().type() != QSqlError::NoError ) qWarning() << query.lastError().text(); } uint IpodTracksFetcher::previousPlayCount( Itdb_Track* track ) const { QSqlQuery query( m_scrobblesdb ); QString sql = "SELECT playcount FROM " + m_tableName + " WHERE id=" + QString::number( track->id ); query.exec( sql ); if( query.next() ) return query.value( 0 ).toUInt(); return 0; } QDateTime IpodTracksFetcher::previousPlayTime( Itdb_Track* track ) const { QSqlQuery query( m_scrobblesdb ); QString sql = "SELECT lastplaytime FROM " + m_tableName + " WHERE id=" + QString::number( track->id ); query.exec( sql ); if( query.next() ) return QDateTime::fromTime_t( query.value( 0 ).toUInt() ); return QDateTime::fromTime_t( 0 ); } IpodDeviceLinux::IpodDeviceLinux() : m_itdb( 0 ) , m_mpl( 0 ) , m_tf( 0 ) , m_autodetected( false ) , m_error( NoError ) {} IpodDeviceLinux::~IpodDeviceLinux() { if ( m_itdb ) { itdb_free( m_itdb ); itdb_playlist_free( m_mpl ); } delete m_tf; } bool IpodDeviceLinux::deleteDeviceHistory( QString username, QString deviceId ) { QString const name = DB_NAME; QSqlDatabase db = QSqlDatabase::database( name ); bool b = false; if ( !db.isValid() ) { db = QSqlDatabase::addDatabase( "QSQLITE", name ); db.setDatabaseName( lastfm::dir::runtimeData().filePath( name + ".db" ) ); } db.open(); QSqlQuery q( db ); b = q.exec( "DROP TABLE " + username + "_" + deviceId ); if ( !b ) qWarning() << q.lastError().text(); return b; } bool IpodDeviceLinux::deleteDevicesHistory() { QString const name = DB_NAME; QString filePath = lastfm::dir::runtimeData().filePath( name + ".db" ); return QFile::remove( filePath ); } QSqlDatabase IpodDeviceLinux::database() const { QString const name = DB_NAME; QSqlDatabase db = QSqlDatabase::database( name ); if ( !db.isValid() ) { db = QSqlDatabase::addDatabase( "QSQLITE", name ); db.setDatabaseName( lastfm::dir::runtimeData().filePath( name + ".db" ) ); db.open(); } if( !db.tables().contains( tableName() ) ) { QSqlQuery q( db ); qDebug() << "table name: " << tableName(); bool b = q.exec( "CREATE TABLE " + tableName() + " ( " "id INTEGER PRIMARY KEY, " "playcount INTEGER, " "lastplaytime INTEGER )" ); if ( !b ) qWarning() << q.lastError().text(); } return db; } void IpodDeviceLinux::open() { QByteArray _mountpath = QFile::encodeName( mountPath() ); const char* mountpath = _mountpath.data(); qDebug() << "mount path: " << mountPath(); m_itdb = itdb_new(); itdb_set_mountpoint( m_itdb, mountpath ); m_mpl = itdb_playlist_new( "iPod", false ); itdb_playlist_set_mpl( m_mpl ); GError* err = 0; m_itdb = itdb_parse( mountpath, &err ); if ( err ) throw tr( "The iPod database could not be opened." ); if( m_deviceId.isEmpty() ) { const Itdb_IpodInfo* iPodInfo = itdb_device_get_ipod_info( m_itdb->device ); const gchar* ipodModel = itdb_info_get_ipod_model_name_string( iPodInfo->ipod_model ); m_ipodModel = QString( ipodModel ); m_deviceId = m_ipodModel.section( ' ', 0, 0 ) + "_" + QString::number( m_itdb->id ); } } const QList& IpodDeviceLinux::tracksToScrobble() const { return m_tracksToScrobble; } void IpodDeviceLinux::fetchTracksToScrobble() { try { open(); } catch ( QString &error ) { qDebug() << "Error initializing the device:" << error; if ( m_autodetected ) { m_error = AutodetectionError; } else { m_error = AccessError; } emit errorOccurred(); return; } emit calculatingScrobbles( itdb_tracks_number( m_itdb ) ); m_tf = new IpodTracksFetcher( m_itdb, database(), tableName(), m_ipodModel ); connect( m_tf, SIGNAL( finished() ), this, SLOT( onFinished() ) ); m_tf->start(); } void IpodDeviceLinux::onFinished() { m_error = NoError; m_tracksToScrobble = m_tf->tracksToScrobble(); emit scrobblingCompleted( m_tracksToScrobble.count() ); } QString IpodDeviceLinux::deviceName() const { QStringList devPath = mountPath().split( "/", QString::SkipEmptyParts ); if ( !devPath.isEmpty() ) return devPath.last(); return QString(); } QString IpodDeviceLinux::tableName() const { audioscrobbler::Application* app = qobject_cast( qApp ); if ( app ) { return app->currentSession().user().name() + "_" + m_deviceId; } return QString(); } bool IpodDeviceLinux::autodetectMountPath() { unicorn::UserSettings us; int count = us.beginReadArray( "associatedDevices" ); if ( !count ) return false; m_detectedDevices.clear(); DeviceInfo deviceInfo; QString deviceId; for ( int i = 0; i < count; i++ ) { us.setArrayIndex( i ); deviceId = us.value( "deviceId" ).toString(); deviceInfo.prettyName = us.value( "deviceName" ).toString(); deviceInfo.mountPath = us.value( "mountPath" ).toString(); if ( QFile::exists( deviceInfo.mountPath ) ) { m_detectedDevices[ deviceId ] = deviceInfo; } } us.endArray(); //No device detected or many, so user has to choose. if ( m_detectedDevices.count() == 0 || m_detectedDevices.count() > 1 ) { return false; } setMountPath( m_detectedDevices.values()[ 0 ].mountPath, true ); return true; } void IpodDeviceLinux::setMountPath( const QString &path, bool autodetected ) { m_mountPath = path; m_autodetected = autodetected; } ================================================ FILE: app/client/MediaDevices/IpodDevice_linux.h ================================================ /* Copyright 2005-2010 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef IPOD_DEVICE_LINUX_H #define IPOD_DEVICE_LINUX_H #include "MediaDevice.h" #include typedef struct _Itdb_iTunesDB Itdb_iTunesDB; typedef struct _Itdb_Track Itdb_Track; typedef struct _Itdb_Playlist Itdb_Playlist; class IpodTracksFetcher: public QThread { public: IpodTracksFetcher( Itdb_iTunesDB* itdb, QSqlDatabase scrobblesdb, const QString& tableName, const QString& ipodModel ); const QList& tracksToScrobble() const{ return m_tracksToScrobble; } void run(); private: void fetchTracks(); void commit( Itdb_Track* iTrack ); void setTrackInfo( Track& lstTrack, Itdb_Track* iTrack ); uint previousPlayCount( Itdb_Track* iTrack ) const; QDateTime previousPlayTime( Itdb_Track* track ) const; private: Itdb_iTunesDB* m_itdb; QSqlDatabase m_scrobblesdb; QString m_tableName; QString m_ipodModel; QList m_tracksToScrobble; }; class IpodDeviceLinux: public MediaDevice { Q_OBJECT public: enum Error { NoError, AutodetectionError, AccessError, UnknownError }; IpodDeviceLinux(); ~IpodDeviceLinux(); /** * Deletes the table with the device scrobbled tracks information. * @return true on success. */ static bool deleteDeviceHistory( QString username, QString deviceId ); /** * Delete the database containing all the devices scrobbled tracks tables. * @return true on success. */ static bool deleteDevicesHistory(); /** * @return the database to sync tracks to be scrobbled. */ QSqlDatabase database() const; /** * Try to detect if there is any of the user associated devices already mounted and use it. * If more than one device is detected then nothing would be done. * @return true if there was just one of the user's devices mounted, otherwise returns false. */ bool autodetectMountPath(); /** * Sets the mount path where the device is mounted. * @param path The mount path of the mounted device. */ void setMountPath( const QString& path, bool autodetected = false ); /** * @return The mount path of the device. */ QString mountPath() const { return m_mountPath; } virtual QString deviceId() const { return m_deviceId; } virtual QString deviceName() const; /** * @return an unique table name to store the device scrobbled tracks. */ QString tableName() const; /** * @return a list of tracks to be scrobbled. */ const QList& tracksToScrobble() const; Error lastError() const{ return m_error; } public slots: /** * Fetches the tracks from the iPod. */ void fetchTracksToScrobble(); private: void open(); private slots: void onFinished(); private: struct DeviceInfo { QString mountPath; QString prettyName; }; private: Itdb_iTunesDB* m_itdb; Itdb_Playlist* m_mpl; QString m_deviceId; QMap m_detectedDevices; QString m_mountPath; QString m_ipodModel; QList m_tracksToScrobble; IpodTracksFetcher* m_tf; bool m_autodetected; Error m_error; }; #endif // IPOD_DEVICE_H ================================================ FILE: app/client/MediaDevices/MediaDevice.cpp ================================================ /* Copyright 2005-2010 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "MediaDevice.h" #include "lib/unicorn/UnicornSettings.h" #include #include #include #include MediaDevice::MediaDevice() { } bool MediaDevice::associateDevice( QString username ) { if ( deviceId().isEmpty() || deviceName().isEmpty() ) return false; #ifdef Q_WS_X11 if ( mountPath().isEmpty() || !QFile::exists( mountPath() ) ) return false; #endif unicorn::UserSettings us( username ); int count = us.beginReadArray( "associatedDevices" ); int index = count; QString devId; for ( int i = 0; i < count; i++ ) { us.setArrayIndex( i ); devId = us.value( "deviceId", "" ).toString(); if ( devId == deviceId() ) { index = i; break; } } us.endArray(); us.beginWriteArray( "associatedDevices" ); us.setArrayIndex( index ); us.setValue( "deviceId", deviceId() ); us.setValue( "deviceName", deviceName() ); #ifdef Q_WS_X11 us.setValue( "mountPath", QDir::toNativeSeparators( mountPath() ) ); #endif us.endArray(); return true; } bool MediaDevice::isDeviceKnown() const { unicorn::UserSettings us; int count = us.beginReadArray( "associatedDevices" ); QString devId; bool isKnown = false; for ( int i = 0; i < count; i++ ) { us.setArrayIndex( i ); devId = us.value( "deviceId", "" ).toString(); if ( devId == deviceId() ) { isKnown = true; } } us.endArray(); return isKnown; } lastfm::User MediaDevice::associatedUser( const QString deviceId ) { lastfm::User associatedUser( "" ); // Check if the device has been associated with a user // and then if it is with the current user QList roster = unicorn::Settings().userRoster(); foreach( lastfm::User user, roster ) { unicorn::UserSettings us( user.name() ); int count = us.beginReadArray( "associatedDevices" ); for ( int i = 0; i < count; i++ ) { us.setArrayIndex( i ); QString tempDeviceId = us.value( "deviceId" ).toString(); qDebug() << tempDeviceId; if ( tempDeviceId == deviceId ) { associatedUser = user; break; } } us.endArray(); if ( !associatedUser.name().isEmpty() ) break; } return associatedUser; } ================================================ FILE: app/client/MediaDevices/MediaDevice.h ================================================ /* Copyright 2005-2010 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef MEDIA_DEVICE_H #define MEDIA_DEVICE_H #include #include #include #include #define DB_NAME "MediaDevicesScrobbles" class MediaDevice: public QObject { Q_OBJECT public: MediaDevice(); /** * Associates the device to an user account. * @param username the user name to associate the device to. If no user name is provided the * device gets associated to the current user logged in. * @return true if it succeeds, false otherwise. */ bool associateDevice( QString username = "" ); /** * @return the last error ocurred or empty string if there wasn't any. */ QString lastError() const { return m_error; } /** * @return an unique ID for the device. */ virtual QString deviceId() const = 0; /** * @return the device name. */ virtual QString deviceName() const = 0; #ifdef Q_WS_X11 /** * @return The mount path of the device. */ virtual QString mountPath() const = 0; #endif /** * @return true if the device is already associated with the user account, false otherwise. */ bool isDeviceKnown() const; static lastfm::User associatedUser( const QString deviceId ); signals: void deviceScrobblingStarted(); void calculatingScrobbles( int trackCount ); void scrobblingCompleted( int trackCount ); void errorOccurred(); protected: QString m_error; }; #endif // MEDIA_DEVICE_H ================================================ FILE: app/client/Mpris2/DBusAbstractAdaptor.cpp ================================================ /* * Copyright 2012 Alex Merry * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "DBusAbstractAdaptor.h" #include #include #include DBusAbstractAdaptor::DBusAbstractAdaptor( QObject *parent ) : QDBusAbstractAdaptor( parent ) , m_connection( QDBusConnection::sessionBus() ) { } QDBusConnection DBusAbstractAdaptor::connection() const { return m_connection; } void DBusAbstractAdaptor::setConnection( const QDBusConnection &conn ) { m_connection = conn; } QString DBusAbstractAdaptor::dBusPath() const { return m_path; } void DBusAbstractAdaptor::setDBusPath( const QString &path ) { m_path = path; } void DBusAbstractAdaptor::signalPropertyChange( const QString &property, const QVariant &value ) { if ( m_updatedProperties.isEmpty() && m_invalidatedProperties.isEmpty() ) { QMetaObject::invokeMethod( this, "_m_emitPropertiesChanged", Qt::QueuedConnection ); qDebug() << "MPRIS2: Queueing up a PropertiesChanged signal:" << property << value; } m_updatedProperties[property] = value; } void DBusAbstractAdaptor::signalPropertyChange( const QString &property ) { if ( !m_invalidatedProperties.contains( property ) ) { if ( m_updatedProperties.isEmpty() && m_invalidatedProperties.isEmpty() ) { QMetaObject::invokeMethod( this, "_m_emitPropertiesChanged", Qt::QueuedConnection ); qDebug() << "MPRIS2: Queueing up a PropertiesChanged signal:" << property; } m_invalidatedProperties << property; } } void DBusAbstractAdaptor::_m_emitPropertiesChanged() { Q_ASSERT( !m_path.isEmpty() ); if( m_updatedProperties.isEmpty() && m_invalidatedProperties.isEmpty() ) { qDebug() << "MPRIS2: Nothing to do"; return; } int ifaceIndex = metaObject()->indexOfClassInfo( "D-Bus Interface" ); if ( ifaceIndex < 0 ) { qDebug() << "MPRIS2: No D-Bus interface given (missing Q_CLASSINFO)"; } else { QDBusMessage signal = QDBusMessage::createSignal( m_path, "org.freedesktop.DBus.Properties", "PropertiesChanged" ); signal << metaObject()->classInfo( ifaceIndex ).value(); signal << m_updatedProperties; signal << m_invalidatedProperties; m_connection.send( signal ); } m_updatedProperties.clear(); m_invalidatedProperties.clear(); } ================================================ FILE: app/client/Mpris2/DBusAbstractAdaptor.h ================================================ /* * Copyright 2012 Alex Merry * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef DBUSABSTRACTADAPTOR_H #define DBUSABSTRACTADAPTOR_H #include #include #include #include class PropertiesChangedAdaptor : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.freedesktop.DBus.Properties") public: explicit PropertiesChangedAdaptor( QObject *parent ); void emitPropertiesChanged( const QString &interface, const QVariantMap &updatedProperties, const QStringList &invalidatedProperties ); Q_SIGNALS: void propertiesChanged( const QString &interface, const QVariantMap &updatedProperties, const QStringList &invalidatedProperties ); }; /** * Hack for property notification support */ class DBusAbstractAdaptor : public QDBusAbstractAdaptor { Q_OBJECT public: explicit DBusAbstractAdaptor( QObject *parent ); // These are hackish methods that are necessary because // of the way QtDBus is implemented; it is impossible to // find out what bus or path this adaptor is at, and adding // another adaptor for the properties interface prevents // Qt's internal properties implementation from working QDBusConnection connection() const; void setConnection( const QDBusConnection &conn ); QString dBusPath() const; void setDBusPath( const QString &path ); protected: void signalPropertyChange( const QString &property, const QVariant &value ); void signalPropertyChange( const QString &property ); private Q_SLOTS: void _m_emitPropertiesChanged(); private: QStringList m_invalidatedProperties; QVariantMap m_updatedProperties; QString m_path; QDBusConnection m_connection; }; #endif // DBUSABSTRACTADAPTOR_H ================================================ FILE: app/client/Mpris2/MediaPlayer2.cpp ================================================ /* Copyright (C) 2013 John Stamp This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "MediaPlayer2.h" #include "Application.h" MediaPlayer2::MediaPlayer2( QObject* parent ) : DBusAbstractAdaptor( parent ) { } bool MediaPlayer2::CanQuit() const { return true; } bool MediaPlayer2::CanRaise() const { return true; } void MediaPlayer2::Raise() { aApp->showWindow(); } void MediaPlayer2::Quit() { aApp->quit(); } bool MediaPlayer2::HasTrackList() const { return false; } QString MediaPlayer2::Identity() const { return aApp->applicationName(); } QString MediaPlayer2::DesktopEntry() const { return "lastfm-scrobbler"; } QStringList MediaPlayer2::SupportedUriSchemes() const { return QStringList() << "lastfm"; } QStringList MediaPlayer2::SupportedMimeTypes() const { return QStringList(); } ================================================ FILE: app/client/Mpris2/MediaPlayer2.h ================================================ /* Copyright (C) 2013 John Stamp This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef MEDIAPLAYER2_H #define MEDIAPLAYER2_H #include "DBusAbstractAdaptor.h" class MediaPlayer2 : public DBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2"); Q_PROPERTY( bool CanQuit READ CanQuit ) Q_PROPERTY( bool CanRaise READ CanRaise ) Q_PROPERTY( bool HasTrackList READ HasTrackList ) Q_PROPERTY( QString Identity READ Identity ) Q_PROPERTY( QString DesktopEntry READ DesktopEntry ) Q_PROPERTY( QStringList SupportedUriSchemes READ SupportedUriSchemes ) Q_PROPERTY( QStringList SupportedMimeTypes READ SupportedMimeTypes ) public: MediaPlayer2( QObject* parent ); bool CanQuit() const; bool CanRaise() const; bool HasTrackList() const; QString Identity() const; QString DesktopEntry() const; QStringList SupportedUriSchemes() const; QStringList SupportedMimeTypes() const; public slots: void Raise(); void Quit(); }; #endif ================================================ FILE: app/client/Mpris2/MediaPlayer2Player.cpp ================================================ /* Copyright (C) 2013 John Stamp This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "MediaPlayer2Player.h" #include "Application.h" #include #include #include #include "../Services/RadioService.h" #include "lib/unicorn/UnicornSettings.h" MediaPlayer2Player::MediaPlayer2Player( QObject* parent ) : DBusAbstractAdaptor( parent ) { m_playbackState = Stopped; connect( &RadioService::instance(), SIGNAL(paused()), SLOT(onPlaybackStateChanged()) ); connect( &RadioService::instance(), SIGNAL(buffering(int)), SLOT(onPlaybackStateChanged()) ); connect( &RadioService::instance(), SIGNAL(stopped()), SLOT(onPlaybackStateChanged()) ); connect( &RadioService::instance(), SIGNAL(resumed()), SLOT(onPlaybackStateChanged()) ); connect( &RadioService::instance(), SIGNAL(trackSpooled( const Track& )), SLOT(onTrackChanged( const Track& )) ); connect( &aApp->currentSession(), SIGNAL(sessionChanged(unicorn::Session)), SLOT(onSessionInfo()) ); if ( RadioService::instance().audioOutput() ) { connect( RadioService::instance().audioOutput(), SIGNAL(volumeChanged( qreal )), SLOT(onVolumeChanged( qreal )) ); } } // Properties QString MediaPlayer2Player::PlaybackStatus() const { switch (m_playbackState) { case Playing: return "Playing"; case Paused: return "Paused"; default: return "Stopped"; } } double MediaPlayer2Player::Rate() const { return 1.0; } void MediaPlayer2Player::SetRate( double ) { } QVariantMap MediaPlayer2Player::Metadata() const { QVariantMap metaData; if ( m_track.isNull() ) return metaData; metaData["mpris:trackid"] = QVariant::fromValue( QDBusObjectPath( "/fm/last/scrobbler/" + QString::number( m_track.timestamp().toTime_t() ) ) ); metaData["mpris:length"] = static_cast(m_track.duration() * 1000000); if ( !m_track.imageUrl( AbstractType::LargeImage, true ).toString().isEmpty() ) metaData["mpris:artUrl"] = m_track.imageUrl( AbstractType::LargeImage, true ).toString(); if ( !m_track.album().title().isEmpty() ) metaData["xesam:album"] = m_track.album().title(); if ( ! m_track.albumArtist().name().isEmpty() ) metaData["xesam:albumArtist"] = QStringList() << m_track.albumArtist().name(); metaData["xesam:artist"] = QStringList() << m_track.artist().name(); // xesam:asText // xesam:audioBPM // xesam:autoRating // xesam:comment // xesam:composer // xesam:contentCreated // xesam:discNumber // xesam:firstUsed // xesam:genre // xesam:lastUsed // xesam:lyricist metaData["xesam:title"] = m_track.title(); if ( m_track.trackNumber() > 0 ) metaData["xesam:trackNumber"] = m_track.trackNumber(); metaData["xesam:url"] = m_track.url().toString(); // xesam:useCount // xesam:userRating return metaData; } double MediaPlayer2Player::Volume() const { if ( RadioService::instance().audioOutput() ) return RadioService::instance().audioOutput()->volume(); else return 0; } void MediaPlayer2Player::SetVolume( double vol ) { if ( vol < 0.0 ) vol = 0.0; else if ( vol > 1.0 ) vol = 1.0; if ( RadioService::instance().audioOutput() ) RadioService::instance().audioOutput()->setVolume( vol ); } qlonglong MediaPlayer2Player::Position() const { if ( RadioService::instance().mediaObject() ) return RadioService::instance().mediaObject()->currentTime() * 1000; else return 0; } double MediaPlayer2Player::MinimumRate() const { return 1.0; } double MediaPlayer2Player::MaximumRate() const { return 1.0; } bool MediaPlayer2Player::CanGoNext() const { if ( m_playbackState == Playing || m_playbackState == Paused ) return true; else return false; } bool MediaPlayer2Player::CanGoPrevious() const { return false; } bool MediaPlayer2Player::CanPlay() const { if ( !aApp->currentSession().youRadio() ) return false; else return true; } bool MediaPlayer2Player::CanPause() const { if ( m_playbackState == Playing || m_playbackState == Paused ) return true; else return false; } bool MediaPlayer2Player::CanSeek() const { return false; } bool MediaPlayer2Player::CanControl() const { return true; } // Methods void MediaPlayer2Player::Next() const { RadioService::instance().skip(); } void MediaPlayer2Player::Previous() const { } void MediaPlayer2Player::Pause() const { RadioService::instance().pause(); } void MediaPlayer2Player::PlayPause() const { if ( RadioService::instance().state() == Playing ) RadioService::instance().pause(); else if ( !RadioService::instance().state() != Buffering ) Play(); } void MediaPlayer2Player::Stop() const { RadioService::instance().stop(); } void MediaPlayer2Player::Play() const { if ( RadioService::instance().state() == Stopped ) RadioService::instance().play( RadioStation( "" ) ); else if ( !RadioService::instance().state() != Buffering ) RadioService::instance().resume(); } void MediaPlayer2Player::Seek( qlonglong ) const { } void MediaPlayer2Player::SetPosition( const QDBusObjectPath&, qlonglong ) const { } void MediaPlayer2Player::OpenUri( QString uri ) const { RadioService::instance().play( uri ); } // Private slots void MediaPlayer2Player::onTrackChanged( const Track& track ) { if ( m_playbackState != Stopped ) { m_playbackState = Stopped; signalPropertyChange( "PlaybackStatus", PlaybackStatus() ); signalPropertyChange( "CanPause", CanPause() ); } m_track = track; signalPropertyChange( "Metadata", Metadata() ); } void MediaPlayer2Player::onVolumeChanged( qreal vol ) { signalPropertyChange( "Volume", Volume() ); } void MediaPlayer2Player::onPlaybackStateChanged() { State teststate = RadioService::instance().state(); switch ( teststate ) { case Playing: case Paused: break; case Buffering: teststate = Paused; break; default: teststate = Stopped; } if ( teststate != m_playbackState ) { m_playbackState = teststate; if ( m_playbackState == Stopped ) { m_track = Track(); signalPropertyChange( "Metadata", Metadata() ); } signalPropertyChange( "PlaybackStatus", PlaybackStatus() ); signalPropertyChange( "CanPause", CanPause() ); signalPropertyChange( "CanGoNext", CanGoNext() ); } } void MediaPlayer2Player::onSessionInfo() { signalPropertyChange( "CanPlay", CanPlay() ); signalPropertyChange( "CanPause", CanPause() ); } ================================================ FILE: app/client/Mpris2/MediaPlayer2Player.h ================================================ /* Copyright (C) 2013 John Stamp This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef MEDIAPLAYER2PLAYER_H #define MEDIAPLAYER2PLAYER_H #include "DBusAbstractAdaptor.h" #include "lib/listener/State.h" #include #include class MediaPlayer2Player : public DBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2.Player"); Q_PROPERTY( QString PlaybackStatus READ PlaybackStatus ) Q_PROPERTY( double Rate READ Rate WRITE SetRate ) Q_PROPERTY( QVariantMap Metadata READ Metadata ) Q_PROPERTY( double Volume READ Volume WRITE SetVolume ) Q_PROPERTY( qlonglong Position READ Position ) Q_PROPERTY( double MinimumRate READ MinimumRate ) Q_PROPERTY( double MaximumRate READ MaximumRate ) Q_PROPERTY( bool CanGoNext READ CanGoNext ) Q_PROPERTY( bool CanGoPrevious READ CanGoPrevious ) Q_PROPERTY( bool CanPlay READ CanPlay ) Q_PROPERTY( bool CanPause READ CanPause ) Q_PROPERTY( bool CanSeek READ CanSeek ) Q_PROPERTY( bool CanControl READ CanControl ) public: MediaPlayer2Player( QObject* parent = 0 ); QString PlaybackStatus() const; double Rate() const; void SetRate(double value); QVariantMap Metadata() const; double Volume() const; void SetVolume(double value); qlonglong Position() const; double MinimumRate() const; double MaximumRate() const; bool CanGoNext() const; bool CanGoPrevious() const; bool CanPlay() const; bool CanPause() const; bool CanSeek() const; bool CanControl() const; public slots: void Next() const; void Previous() const; void Pause() const; void PlayPause() const; void Stop() const; void Play() const; void Seek( qlonglong Offset ) const; void SetPosition( const QDBusObjectPath& TrackId, qlonglong Position ) const; void OpenUri( QString Uri ) const; signals: void Seeked( qlonglong Position ) const; private slots: void onTrackChanged( const Track& track ); void onVolumeChanged( qreal vol ); void onPlaybackStateChanged(); void onSessionInfo(); private: Track m_track; State m_playbackState; }; #endif ================================================ FILE: app/client/Mpris2/Mpris2.cpp ================================================ /*********************************************************************** * Copyright 2012 Eike Hein * * This program is free software; you can redistribute it and/or * modify it under the 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 "Mpris2.h" #include "MediaPlayer2.h" #include "MediaPlayer2Player.h" #include #include Mpris2::Mpris2( QObject *parent ) : QObject( parent ) { QString mpris2Name( "org.mpris.MediaPlayer2.lastfm-scrobbler" ); bool success = QDBusConnection::sessionBus().registerService( mpris2Name ); // If the above failed, it's likely because we're not the first instance // and the name is already taken. In that event the MPRIS2 spec wants the // following: if (!success) { mpris2Name = mpris2Name + ".instance" + QString::number( getpid() ); success = QDBusConnection::sessionBus().registerService( mpris2Name ); } if ( success ) { DBusAbstractAdaptor *adaptor = new MediaPlayer2( this ); adaptor->setDBusPath( "/org/mpris/MediaPlayer2" ); adaptor = new MediaPlayer2Player( this ); adaptor->setDBusPath( "/org/mpris/MediaPlayer2" ); QDBusConnection::sessionBus().registerObject( "/org/mpris/MediaPlayer2", this, QDBusConnection::ExportAdaptors ); } } Mpris2::~Mpris2() { } ================================================ FILE: app/client/Mpris2/Mpris2.h ================================================ /*********************************************************************** * Copyright 2012 Eike Hein * * This program is free software; you can redistribute it and/or * modify it under the 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 MPRIS2_H #define MPRIS2_H #include class Mpris2 : public QObject { Q_OBJECT public: explicit Mpris2( QObject *parent = 0 ); ~Mpris2(); }; #endif // MPRIS2_H ================================================ FILE: app/client/PrefPane/English.lproj/MainWindow.xib ================================================ 1050 9L31a 677 949.54 353.00 YES YES com.apple.InterfaceBuilderKit com.apple.InterfaceBuilder.CocoaPlugin YES YES YES YES FmLastPrefPane FirstResponder NSApplication 15 2 {{335, 244}, {668, 506}} 1946157056 Window NSWindow {3.40282e+38, 3.40282e+38} 256 YES 45 {{20, 20}, {628, 466}} FmLastPrefPaneQtView {668, 506} YES {{0, 0}, {1440, 878}} {3.40282e+38, 3.40282e+38} NSFontManager AppDelegate FmLastPrefPane YES delegate 454 _window 460 _window 462 qtView 463 YES 0 YES -2 RmlsZSdzIE93bmVyA -1 First Responder -3 Application 371 YES 372 YES 420 456 453 461 YES YES -1.IBPluginDependency -2.IBPluginDependency -3.IBPluginDependency 371.IBEditorWindowLastContentRect 371.IBWindowTemplateEditedContentRect 371.NSWindowTemplate.visibleAtLaunch 371.editorWindowContentRectSynchronizationRect 371.windowTemplate.maxSize 372.IBPluginDependency 420.IBPluginDependency 453.IBPluginDependency 456.IBPluginDependency 456.IBViewIntegration.shadowBlurRadius 456.IBViewIntegration.shadowColor 456.IBViewIntegration.shadowOffsetHeight 456.IBViewIntegration.shadowOffsetWidth 461.IBPluginDependency YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilderKit com.apple.InterfaceBuilderKit {{392, 455}, {668, 506}} {{392, 455}, {668, 506}} {{33, 99}, {480, 360}} {3.40282e+38, 3.40282e+38} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin 3 MAA com.apple.InterfaceBuilder.CocoaPlugin YES YES YES YES YES YES 463 YES FmLastPrefPane NSPreferencePane qtView FmLastPrefPaneQtView IBProjectSource FmLastPrefPane.h FmLastPrefPaneQtView NSView IBProjectSource FmLastPrefPaneQtView.h YES NSPreferencePane NSObject YES YES _firstKeyView _initialKeyView _lastKeyView _window YES NSView NSView NSView NSWindow IBDocumentRelativeSource ../../../../../../../../../System/Library/Frameworks/PreferencePanes.framework/Versions/A/Headers/NSPreferencePane.h 0 ../PrefPane.xcodeproj 3 ================================================ FILE: app/client/PrefPane/FmLastPrefPane.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #import #import #import "FmLastPrefPaneQtView.h" @interface FmLastPrefPane : NSPreferencePane { IBOutlet FmLastPrefPaneQtView* qtView; } - (void)mainViewDidLoad; - (void)didSelect; @end ================================================ FILE: app/client/PrefPane/FmLastPrefPanePrefWidget.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PREF_WIDGET_H_ #define PREF_WIDGET_H_ #include #include class FmLastPrefPanePrefWidget { public: FmLastPrefPanePrefWidget(); NSView* view(); QMacNativeWidget* qWidget(){ return widget; } void updateGeometry(); private: QMacNativeWidget* widget; class SettingsDialog* dialog; }; #endif //PREF_WIDGET_H_ ================================================ FILE: app/client/PrefPane/FmLastPrefPanePrefWidget.mm ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "FmLastPrefPanePrefWidget.h" #include #include #include #include #include #include #include #include "../Dialogs/SettingsDialog.h" #include #include "../../../lib/unicorn/UnicornApplication.h" #include class MySettingsDialog : public SettingsDialog { public: MySettingsDialog( QWidget* p = 0 ) :SettingsDialog( p ){ setWindowFlags( Qt::Widget ); ui.pageList->setCurrentRow( 2 ); setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed);} // virtual QSize sizeHint() const { // NSView* nsv = reinterpret_cast(winId()); // return QSize( [[[nsv superview] superview] frame].size.width, [[[nsv superview] superview] frame].size.height ); // } protected: virtual bool eventFilter(QObject *obj, QEvent *ev) { if( ev->type() == QEvent::Resize ) { // updateGeometry(); NSView* nsv = reinterpret_cast(winId()); layout()->update(); if( [nsv window] ) [nsv setFrameSize: [[[nsv superview] superview] frame].size ]; } return false; } }; FmLastPrefPanePrefWidget::FmLastPrefPanePrefWidget() :dialog( 0 ), widget( 0 ) { char* argv; int argc = 0; unicorn::Application* app = new unicorn::Application( argc, &argv ); QCoreApplication::arguments(); widget = new QMacNativeWidget(); QBoxLayout* layout = new QVBoxLayout( widget ); layout->addWidget( dialog = new MySettingsDialog( widget ) ); foreach( QWidget* w, dialog->findChildren()) { w->installEventFilter( dialog ); w->show(); } NSWindow *systemPrefsWindow = [[NSApplication sharedApplication] mainWindow]; [reinterpret_cast(dialog->winId()) setFrame: [systemPrefsWindow contentRectForFrameRect:[systemPrefsWindow frame]]]; [reinterpret_cast(widget->winId()) setAutoresizingMask: NSViewWidthSizable ]; widget->layout()->setContentsMargins( 0, 0, 0, 0 ); widget->layout()->setSpacing( 0 ); dialog->setAutoFillBackground( true ); widget->show(); } NSView* FmLastPrefPanePrefWidget::view() { qDebug() << "Getting view.."; const WId id = widget->winId(); const NSView* ret = reinterpret_cast(id); // widget->layout()->update(); return ret; } void FmLastPrefPanePrefWidget::updateGeometry() { if( this && dialog ) dialog->updateGeometry(); } ================================================ FILE: app/client/PrefPane/FmLastPrefPaneQtView.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #import @interface FmLastPrefPaneQtView : NSView { class FmLastPrefPanePrefWidget* pw; } - (id) initWithFrame:(NSRect)frameRect; - (void) drawRect:(NSRect)rect; - (FmLastPrefPanePrefWidget*) prefWidget; @end ================================================ FILE: app/client/PrefPane/FmLastPrefPaneQtView.mm ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #import "FmLastPrefPaneQtView.h" #include "FmLastPrefPanePrefWidget.h" @implementation FmLastPrefPaneQtView - (id) initWithFrame:(NSRect)frameRect { [super initWithFrame: frameRect]; pw = new FmLastPrefPanePrefWidget; NSView* pwView = pw->view(); [self addSubview: pwView positioned:NSWindowAbove relativeTo:nil]; // [pwView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; // [pwView setAutoresizesSubviews:YES]; [self drawRect: [self frame]]; return self; } - (void) drawRect:(NSRect)rect { NSView* pwView = pw->view(); [pwView setFrameOrigin: NSMakePoint( 0, [self frame].size.height - [pwView frame].size.height)]; [pwView setFrameSize: [self frame].size]; [super drawRect: rect]; } - (FmLastPrefPanePrefWidget*) prefWidget { return pw; } @end ================================================ FILE: app/client/PrefPane/Info.plist ================================================ CFBundleDevelopmentRegion English CFBundleIdentifier fm.last.prefpane CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType BNDL CFBundleSignature ???? CFBundleVersion 1.0 NSPrefPaneIconLabel Last.fm NSPrefPaneIconFile icon.png NSMainNibFile MainWindow NSPrincipalClass FmLastPrefPane ================================================ FILE: app/client/PrefPane/PrefPane.pro ================================================ TEMPLATE = app debug { pp.commands = xcodebuild -project PrefPane.xcodeproj -configuration Debug } release { pp.commands = xcodebuild -project PrefPane.xcodeproj -configuration Release } pp.target=PrefPane.prefpane QMAKE_CLEAN = -r build # Disable the linker: QMAKE_LINK=@\\$$LITERAL_HASH CONFIG-=app_bundle CONFIG+=lib_bundle OBJECTS_DIR=. QMAKE_BUNDLE_EXTENSION=.prefpane debug:TARGET=Scrobbler_debug.prefpane release:TARGET=Scrobbler.prefpane QMAKE_EXTRA_TARGETS = pp PRE_TARGETDEPS = $$pp.target ================================================ FILE: app/client/PrefPane/PrefPane.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 45; objects = { /* Begin PBXBuildFile section */ 8D5B49B0048680CD000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C167DFE841241C02AAC07 /* InfoPlist.strings */; }; 8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */; }; FB2089DD11ECD211004CA2E5 /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = FB2089DC11ECD211004CA2E5 /* icon.png */; }; FB208A0C11EF15A8004CA2E5 /* FmLastPrefPane.mm in Sources */ = {isa = PBXBuildFile; fileRef = FB208A0A11EF15A8004CA2E5 /* FmLastPrefPane.mm */; }; FBA446ED11EC9EDF005DA6BB /* FmLastPrefPaneQtView.mm in Sources */ = {isa = PBXBuildFile; fileRef = FBA446EA11EC9EDF005DA6BB /* FmLastPrefPaneQtView.mm */; }; FBA446EE11EC9EDF005DA6BB /* FmLastPrefPanePrefWidget.mm in Sources */ = {isa = PBXBuildFile; fileRef = FBA446EC11EC9EDF005DA6BB /* FmLastPrefPanePrefWidget.mm */; }; FBA446F211EC9F46005DA6BB /* PreferencePanes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBA446F111EC9F46005DA6BB /* PreferencePanes.framework */; }; FBA446FB11ECBFC8005DA6BB /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = FBA446F911ECBFC8005DA6BB /* MainWindow.xib */; }; FBA4470211ECBFE9005DA6BB /* QtCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBA4470011ECBFE9005DA6BB /* QtCore.framework */; }; FBA4470311ECBFE9005DA6BB /* QtGui.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBA4470111ECBFE9005DA6BB /* QtGui.framework */; }; FBD4837111FA088F00B21E5A /* moc_SettingsDialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBD4837011FA088F00B21E5A /* moc_SettingsDialog.cpp */; }; FBD4838111FA09DC00B21E5A /* SettingsDialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBD4838011FA09DC00B21E5A /* SettingsDialog.cpp */; }; FBD4838611FA0A0D00B21E5A /* AccountSettingsWidget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBD4838311FA0A0D00B21E5A /* AccountSettingsWidget.cpp */; }; FBD4838711FA0A0D00B21E5A /* IpodSettingsWidget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBD4838411FA0A0D00B21E5A /* IpodSettingsWidget.cpp */; }; FBD4838811FA0A0D00B21E5A /* ScrobbleSettingsWidget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBD4838511FA0A0D00B21E5A /* ScrobbleSettingsWidget.cpp */; }; FBD483AE11FA0CC300B21E5A /* moc_AccountSettingsWidget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBD483AB11FA0CC300B21E5A /* moc_AccountSettingsWidget.cpp */; }; FBD483AF11FA0CC300B21E5A /* moc_IpodSettingsWidget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBD483AC11FA0CC300B21E5A /* moc_IpodSettingsWidget.cpp */; }; FBD483B011FA0CC300B21E5A /* moc_ScrobbleSettingsWidget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBD483AD11FA0CC300B21E5A /* moc_ScrobbleSettingsWidget.cpp */; }; FBD483B411FA0CFE00B21E5A /* moc_SettingsWidget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBD483B311FA0CFE00B21E5A /* moc_SettingsWidget.cpp */; }; FBD483B711FA0D4700B21E5A /* SettingsWidget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBD483B611FA0D4700B21E5A /* SettingsWidget.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 089C1672FE841209C02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 089C167EFE841241C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 089C167FFE841241C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 8D5B49B6048680CD000E48DA /* Scrobbler_debug.prefpane */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Scrobbler_debug.prefpane; sourceTree = BUILT_PRODUCTS_DIR; }; 8D5B49B7048680CD000E48DA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D2F7E65807B2D6F200F64583 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; FB2089DC11ECD211004CA2E5 /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; FB208A0A11EF15A8004CA2E5 /* FmLastPrefPane.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FmLastPrefPane.mm; sourceTree = ""; }; FB208A0B11EF15A8004CA2E5 /* FmLastPrefPane.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FmLastPrefPane.h; sourceTree = ""; }; FBA446E911EC9EDF005DA6BB /* FmLastPrefPaneQtView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FmLastPrefPaneQtView.h; sourceTree = ""; }; FBA446EA11EC9EDF005DA6BB /* FmLastPrefPaneQtView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FmLastPrefPaneQtView.mm; sourceTree = ""; }; FBA446EB11EC9EDF005DA6BB /* FmLastPrefPanePrefWidget.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FmLastPrefPanePrefWidget.h; sourceTree = ""; }; FBA446EC11EC9EDF005DA6BB /* FmLastPrefPanePrefWidget.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FmLastPrefPanePrefWidget.mm; sourceTree = ""; }; FBA446F111EC9F46005DA6BB /* PreferencePanes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PreferencePanes.framework; path = /System/Library/Frameworks/PreferencePanes.framework; sourceTree = ""; }; FBA446FA11ECBFC8005DA6BB /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainWindow.xib; sourceTree = ""; }; FBA4470011ECBFE9005DA6BB /* QtCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QtCore.framework; path = /opt/qt/qt.git/lib/QtCore.framework; sourceTree = ""; }; FBA4470111ECBFE9005DA6BB /* QtGui.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QtGui.framework; path = /opt/qt/qt.git/lib/QtGui.framework; sourceTree = ""; }; FBCB815F11ECCA780019E4C7 /* PrefPane_prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrefPane_prefix.pch; sourceTree = ""; }; FBD4837011FA088F00B21E5A /* moc_SettingsDialog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_SettingsDialog.cpp; path = ../_build/moc_SettingsDialog.cpp; sourceTree = SOURCE_ROOT; }; FBD4838011FA09DC00B21E5A /* SettingsDialog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SettingsDialog.cpp; path = ../Dialogs/SettingsDialog.cpp; sourceTree = SOURCE_ROOT; }; FBD4838311FA0A0D00B21E5A /* AccountSettingsWidget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AccountSettingsWidget.cpp; path = ../Widgets/AccountSettingsWidget.cpp; sourceTree = SOURCE_ROOT; }; FBD4838411FA0A0D00B21E5A /* IpodSettingsWidget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = IpodSettingsWidget.cpp; path = ../Widgets/IpodSettingsWidget.cpp; sourceTree = SOURCE_ROOT; }; FBD4838511FA0A0D00B21E5A /* ScrobbleSettingsWidget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScrobbleSettingsWidget.cpp; path = ../Widgets/ScrobbleSettingsWidget.cpp; sourceTree = SOURCE_ROOT; }; FBD483AB11FA0CC300B21E5A /* moc_AccountSettingsWidget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_AccountSettingsWidget.cpp; path = ../_build/moc_AccountSettingsWidget.cpp; sourceTree = SOURCE_ROOT; }; FBD483AC11FA0CC300B21E5A /* moc_IpodSettingsWidget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_IpodSettingsWidget.cpp; path = ../_build/moc_IpodSettingsWidget.cpp; sourceTree = SOURCE_ROOT; }; FBD483AD11FA0CC300B21E5A /* moc_ScrobbleSettingsWidget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_ScrobbleSettingsWidget.cpp; path = ../_build/moc_ScrobbleSettingsWidget.cpp; sourceTree = SOURCE_ROOT; }; FBD483B311FA0CFE00B21E5A /* moc_SettingsWidget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_SettingsWidget.cpp; path = ../_build/moc_SettingsWidget.cpp; sourceTree = SOURCE_ROOT; }; FBD483B611FA0D4700B21E5A /* SettingsWidget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SettingsWidget.cpp; path = ../Widgets/SettingsWidget.cpp; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8D5B49B3048680CD000E48DA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */, FBA446F211EC9F46005DA6BB /* PreferencePanes.framework in Frameworks */, FBA4470211ECBFE9005DA6BB /* QtCore.framework in Frameworks */, FBA4470311ECBFE9005DA6BB /* QtGui.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 089C166AFE841209C02AAC07 /* PrefPane */ = { isa = PBXGroup; children = ( FBD483B611FA0D4700B21E5A /* SettingsWidget.cpp */, FBD4837011FA088F00B21E5A /* moc_SettingsDialog.cpp */, FBD483B311FA0CFE00B21E5A /* moc_SettingsWidget.cpp */, FBD483AB11FA0CC300B21E5A /* moc_AccountSettingsWidget.cpp */, FBD483AC11FA0CC300B21E5A /* moc_IpodSettingsWidget.cpp */, FBD483AD11FA0CC300B21E5A /* moc_ScrobbleSettingsWidget.cpp */, 08FB77AFFE84173DC02AAC07 /* Classes */, 32C88E010371C26100C91783 /* Other Sources */, 089C167CFE841241C02AAC07 /* Resources */, 089C1671FE841209C02AAC07 /* Frameworks and Libraries */, 19C28FB8FE9D52D311CA2CBB /* Products */, ); name = PrefPane; sourceTree = ""; }; 089C1671FE841209C02AAC07 /* Frameworks and Libraries */ = { isa = PBXGroup; children = ( 1058C7ACFEA557BF11CA2CBB /* Linked Frameworks */, 1058C7AEFEA557BF11CA2CBB /* Other Frameworks */, ); name = "Frameworks and Libraries"; sourceTree = ""; }; 089C167CFE841241C02AAC07 /* Resources */ = { isa = PBXGroup; children = ( FB2089DC11ECD211004CA2E5 /* icon.png */, FBA446F911ECBFC8005DA6BB /* MainWindow.xib */, 8D5B49B7048680CD000E48DA /* Info.plist */, 089C167DFE841241C02AAC07 /* InfoPlist.strings */, ); name = Resources; sourceTree = ""; }; 08FB77AFFE84173DC02AAC07 /* Classes */ = { isa = PBXGroup; children = ( FBD4838311FA0A0D00B21E5A /* AccountSettingsWidget.cpp */, FBD4838411FA0A0D00B21E5A /* IpodSettingsWidget.cpp */, FBD4838511FA0A0D00B21E5A /* ScrobbleSettingsWidget.cpp */, FBD4838011FA09DC00B21E5A /* SettingsDialog.cpp */, FBA446E911EC9EDF005DA6BB /* FmLastPrefPaneQtView.h */, FBA446EA11EC9EDF005DA6BB /* FmLastPrefPaneQtView.mm */, FBA446EB11EC9EDF005DA6BB /* FmLastPrefPanePrefWidget.h */, FBA446EC11EC9EDF005DA6BB /* FmLastPrefPanePrefWidget.mm */, FB208A0A11EF15A8004CA2E5 /* FmLastPrefPane.mm */, FB208A0B11EF15A8004CA2E5 /* FmLastPrefPane.h */, ); name = Classes; sourceTree = ""; }; 1058C7ACFEA557BF11CA2CBB /* Linked Frameworks */ = { isa = PBXGroup; children = ( FBA4470011ECBFE9005DA6BB /* QtCore.framework */, FBA4470111ECBFE9005DA6BB /* QtGui.framework */, FBA446F111EC9F46005DA6BB /* PreferencePanes.framework */, 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */, ); name = "Linked Frameworks"; sourceTree = ""; }; 1058C7AEFEA557BF11CA2CBB /* Other Frameworks */ = { isa = PBXGroup; children = ( 089C167FFE841241C02AAC07 /* AppKit.framework */, D2F7E65807B2D6F200F64583 /* CoreData.framework */, 089C1672FE841209C02AAC07 /* Foundation.framework */, ); name = "Other Frameworks"; sourceTree = ""; }; 19C28FB8FE9D52D311CA2CBB /* Products */ = { isa = PBXGroup; children = ( 8D5B49B6048680CD000E48DA /* Scrobbler_debug.prefpane */, ); name = Products; sourceTree = ""; }; 32C88E010371C26100C91783 /* Other Sources */ = { isa = PBXGroup; children = ( FBCB815F11ECCA780019E4C7 /* PrefPane_prefix.pch */, ); name = "Other Sources"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 8D5B49AC048680CD000E48DA /* PrefPane */ = { isa = PBXNativeTarget; buildConfigurationList = 1DEB913A08733D840010E9CD /* Build configuration list for PBXNativeTarget "PrefPane" */; buildPhases = ( 8D5B49AF048680CD000E48DA /* Resources */, 8D5B49B1048680CD000E48DA /* Sources */, 8D5B49B3048680CD000E48DA /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = PrefPane; productInstallPath = "$(HOME)/Library/Bundles"; productName = PrefPane; productReference = 8D5B49B6048680CD000E48DA /* Scrobbler_debug.prefpane */; productType = "com.apple.product-type.bundle"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 089C1669FE841209C02AAC07 /* Project object */ = { isa = PBXProject; buildConfigurationList = 1DEB913E08733D840010E9CD /* Build configuration list for PBXProject "PrefPane" */; compatibilityVersion = "Xcode 3.1"; hasScannedForEncodings = 1; mainGroup = 089C166AFE841209C02AAC07 /* PrefPane */; projectDirPath = ""; projectRoot = ""; targets = ( 8D5B49AC048680CD000E48DA /* PrefPane */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 8D5B49AF048680CD000E48DA /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 8D5B49B0048680CD000E48DA /* InfoPlist.strings in Resources */, FBA446FB11ECBFC8005DA6BB /* MainWindow.xib in Resources */, FB2089DD11ECD211004CA2E5 /* icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 8D5B49B1048680CD000E48DA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( FBA446ED11EC9EDF005DA6BB /* FmLastPrefPaneQtView.mm in Sources */, FBA446EE11EC9EDF005DA6BB /* FmLastPrefPanePrefWidget.mm in Sources */, FB208A0C11EF15A8004CA2E5 /* FmLastPrefPane.mm in Sources */, FBD4837111FA088F00B21E5A /* moc_SettingsDialog.cpp in Sources */, FBD4838111FA09DC00B21E5A /* SettingsDialog.cpp in Sources */, FBD4838611FA0A0D00B21E5A /* AccountSettingsWidget.cpp in Sources */, FBD4838711FA0A0D00B21E5A /* IpodSettingsWidget.cpp in Sources */, FBD4838811FA0A0D00B21E5A /* ScrobbleSettingsWidget.cpp in Sources */, FBD483AE11FA0CC300B21E5A /* moc_AccountSettingsWidget.cpp in Sources */, FBD483AF11FA0CC300B21E5A /* moc_IpodSettingsWidget.cpp in Sources */, FBD483B011FA0CC300B21E5A /* moc_ScrobbleSettingsWidget.cpp in Sources */, FBD483B411FA0CFE00B21E5A /* moc_SettingsWidget.cpp in Sources */, FBD483B711FA0D4700B21E5A /* SettingsWidget.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 089C167DFE841241C02AAC07 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 089C167EFE841241C02AAC07 /* English */, ); name = InfoPlist.strings; sourceTree = ""; }; FBA446F911ECBFC8005DA6BB /* MainWindow.xib */ = { isa = PBXVariantGroup; children = ( FBA446FA11ECBFC8005DA6BB /* English */, ); name = MainWindow.xib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 1DEB913B08733D840010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", /opt/qt/qt.git/lib, ); GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = PrefPane_Prefix.pch; HEADER_SEARCH_PATHS = ( "/opt/qt/qt-current/include/**", /opt/local/include, ../../.., ); INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Library/Bundles"; LIBRARY_SEARCH_PATHS = /opt/local/lib; OTHER_LDFLAGS = ( "-llastfm", "-lunicorn", ); PRODUCT_NAME = Scrobbler_debug; WRAPPER_EXTENSION = prefpane; }; name = Debug; }; 1DEB913C08733D840010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", /opt/qt/qt.git/lib, ); GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = PrefPane_Prefix.pch; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Library/Bundles"; PRODUCT_NAME = Scrobbler; WRAPPER_EXTENSION = prefpane; }; name = Release; }; 1DEB913F08733D840010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; BUNDLE_LOADER = "/Applications/System Preferences.app/Contents/MacOS/System Preferences"; CONFIGURATION_BUILD_DIR = ../../../_bin; EXECUTABLE_EXTENSION = ""; GCC_C_LANGUAGE_STANDARD = c99; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_PREPROCESS = YES; MACOSX_DEPLOYMENT_TARGET = 10.4; ONLY_ACTIVE_ARCH = YES; PREBINDING = NO; PRELINK_LIBS = ""; PRODUCT_NAME = Audioscrobbler; SDKROOT = macosx10.4; WRAPPER_EXTENSION = prefpane; }; name = Debug; }; 1DEB914008733D840010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; CONFIGURATION_BUILD_DIR = ../../../_bin; GCC_C_LANGUAGE_STANDARD = c99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( ../../../, "/opt/qt/qt-current/include/QtGui", ); PREBINDING = NO; SDKROOT = macosx10.4; USER_HEADER_SEARCH_PATHS = ""; WRAPPER_EXTENSION = prefpane; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1DEB913A08733D840010E9CD /* Build configuration list for PBXNativeTarget "PrefPane" */ = { isa = XCConfigurationList; buildConfigurations = ( 1DEB913B08733D840010E9CD /* Debug */, 1DEB913C08733D840010E9CD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 1DEB913E08733D840010E9CD /* Build configuration list for PBXProject "PrefPane" */ = { isa = XCConfigurationList; buildConfigurations = ( 1DEB913F08733D840010E9CD /* Debug */, 1DEB914008733D840010E9CD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 089C1669FE841209C02AAC07 /* Project object */; } ================================================ FILE: app/client/PrefPane/PrefPane_prefix.pch ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ ================================================ FILE: app/client/ScrobSocket.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "ScrobSocket.h" #include #include #include #include #ifdef WIN32 #include "common/c++/win/scrobSubPipeName.cpp" #endif ScrobSocket::ScrobSocket( const QString& clientId, QObject* parent ) : QLocalSocket( parent ) , m_bInConnect( false ) , m_clientId( clientId ) { connect( this, SIGNAL(readyRead()), SLOT(onReadyRead()) ); connect( this, SIGNAL(error( QLocalSocket::LocalSocketError )), SLOT(onError( QLocalSocket::LocalSocketError )) ); connect( this, SIGNAL(connected()), SLOT(onConnected()) ); connect( this, SIGNAL(disconnected()), SLOT(onDisconnected()) ); transmit( "INIT c=" + clientId + "\n" ); } ScrobSocket::~ScrobSocket() { if (!m_track.isNull()) stop(); } void ScrobSocket::transmit( const QString& data ) { m_msgQueue.enqueue( data ); qDebug() << "Connection state == " << state(); if( state() == QLocalSocket::UnconnectedState ) { doConnect(); } } void ScrobSocket::onConnected() { if( !m_msgQueue.empty() ) { qDebug() << m_msgQueue.head().trimmed(); write( m_msgQueue.takeFirst().toUtf8()); flush(); } } void ScrobSocket::doConnect() { if (!m_bInConnect) { #ifdef WIN32 std::string s; DWORD r = scrobSubPipeName( &s ); if (r != 0) throw std::runtime_error( formatWin32Error( r ) ); QString const name = QString::fromStdString( s ); #else QString const name = "lastfm_scrobsub"; #endif // avoid stack-overflow connect/disconnect loop with m_bInConnect m_bInConnect = true; connectToServer( name ); m_bInConnect = false; } } void ScrobSocket::onDisconnected() { if( !m_msgQueue.empty()) doConnect(); } void ScrobSocket::onError( QLocalSocket::LocalSocketError error ) { switch (error) { case SocketTimeoutError: // TODO look, really we should store at least one start message forever // then if last time we didn't connect and this time it's a pause we // send the start first m_msgQueue.clear(); break; case PeerClosedError: // expected break; case ConnectionRefusedError: // happens if client isn't running break; default: // may as well qDebug() << lastfm::qMetaEnumString( error, "SocketError" ); } } static inline QString encodeAmp( QString data ) { return data.replace( '&', "&&" ); } void ScrobSocket::start( const Track& t ) { m_track = t; transmit( "START c=" + m_clientId + "&" "a=" + encodeAmp( t.artist() ) + "&" "t=" + encodeAmp( t.title() ) + "&" "b=" + encodeAmp( t.album().title() == "[unknown]" ? QString("") : t.album().title() ) + "&" // todo: and when album.isNull? "l=" + QString::number( t.duration() ) + "&" "p=" + encodeAmp( t.url().path() ) + '\n' ); } void ScrobSocket::pause() { transmit( "PAUSE c=" + m_clientId + "\n" ); } void ScrobSocket::resume() { transmit( "RESUME c=" + m_clientId + "\n" ); } void ScrobSocket::stop() { transmit( "STOP c=" + m_clientId + "\n" ); } void ScrobSocket::onReadyRead() { QByteArray bytes = readAll(); if (bytes != "OK\n") qWarning() << bytes.trimmed(); disconnectFromServer(); } ================================================ FILE: app/client/ScrobSocket.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SCROB_SOCKET_H #define SCROB_SOCKET_H #include #include #include #include /** @author Christian Muehlhaeuser * @contributor Erik Jaelevik * @rewrite Max Howell */ class ScrobSocket : public QLocalSocket { Q_OBJECT public: ScrobSocket( const QString& clientId, QObject* parent = 0); ~ScrobSocket(); public slots: void start( const Track& ); void pause(); void resume(); void stop(); private slots: void transmit( const QString& data ); void onError( QLocalSocket::LocalSocketError ); void onReadyRead(); void onConnected(); void onDisconnected(); private: void doConnect(); Track m_track; QQueue m_msgQueue; bool m_bInConnect; QString m_clientId; }; #endif ================================================ FILE: app/client/Services/AnalyticsService/AnalyticsService.cpp ================================================ /* Copyright 2005-2012 Last.fm Ltd. - Primarily authored by Frantz Joseph This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifdef LASTFM_ANALYTICS #include #endif #include #include #include #include #include #include "lib/unicorn/UnicornSettings.h" #include "../Application.h" #include "PersistentCookieJar.h" #include "AnalyticsService.h" AnalyticsService::AnalyticsService() :m_customVarsSet( false ), m_pageLoaded( false ) { #ifdef LASTFM_ANALYTICS m_webView = new QWebView(); m_cookieJar = new PersistentCookieJar( this ); m_webView->page()->networkAccessManager()->setCookieJar( m_cookieJar ); connect( m_webView, SIGNAL(loadFinished(bool)), m_cookieJar, SLOT(save()) ); connect( m_webView, SIGNAL(loadFinished(bool)), SLOT(onLoadFinished()) ); connect( aApp, SIGNAL(gotUserInfo(lastfm::User)), SLOT(onGotUserInfo(lastfm::User)) ); m_webView->load( QString( "https://cdn.last.fm/client/ga.html" ) ); #endif } void AnalyticsService::sendEvent( const QString& category, const QString& action, const QString& label, const QString& value ) { #ifdef LASTFM_ANALYTICS m_queue.enqueue( QString( "https://cdn.last.fm/client/ga.html#event?category=%1&action=%2&label=%3&value=%4" ).arg( category, action, label, value ) ); loadPages(); #endif } void AnalyticsService::sendPageView( const QString& url ) { #ifdef LASTFM_ANALYTICS m_queue.enqueue( QString( "https://cdn.last.fm/client/ga.html#pageview?url=%1" ).arg( url ) ); loadPages(); #endif } void AnalyticsService::loadPages() { #ifdef LASTFM_ANALYTICS if ( m_pageLoaded && m_customVarsSet ) { while ( !m_customVars.isEmpty() ) m_webView->load( m_customVars.dequeue() ); while ( !m_queue.isEmpty() ) m_webView->load( m_queue.dequeue() ); } #endif } void AnalyticsService::onLoadFinished() { #ifdef LASTFM_ANALYTICS m_pageLoaded = true; loadPages(); #endif } QString userTypeToString( lastfm::User::Type type ) { if ( type == lastfm::User::TypeUser ) return "user"; else if ( type == lastfm::User::TypeSubscriber ) return "subscriber"; else if ( type == lastfm::User::TypeModerator ) return "moderator"; else if ( type == lastfm::User::TypeStaff ) return "staff"; else if ( type == lastfm::User::TypeAlumni ) return "alumni"; return "unknown"; } void AnalyticsService::onGotUserInfo( const lastfm::User& user ) { // set all the session level custom vars m_customVars.clear(); m_customVars.enqueue( QString( "https://cdn.last.fm/client/ga.html#custom?version=%1&usertype=%2" ).arg( QCoreApplication::applicationVersion(), userTypeToString( user.type() ) ) ); m_customVarsSet = true; loadPages(); } ================================================ FILE: app/client/Services/AnalyticsService/AnalyticsService.h ================================================ /* Copyright 2005-2012 Last.fm Ltd. - Primarily authored by Frantz Joseph This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #pragma once #include #include namespace lastfm { class User; } // -- Categories and their associated actions #define NOW_PLAYING_CATEGORY "NowPlaying" #define SCROBBLES_CATEGORY "Scrobbles" #define PROFILE_CATEGORY "Profile" #define FRIENDS_CATEGORY "Friends" #define RADIO_CATEGORY "Radio" #define SETTINGS_CATEGORY "Settings" #define LOVE_TRACK "LoveTrack" #define BAN_TRACK "BanTrack" #define LINK_CLICKED "LinkClicked" #define PLAY_CLICKED "PlayClicked" #define PLAY_NEXT_CLICKED "PlayNextClicked" #define PLAY_MULTI_CLICKED "PlayMultiClicked" #define PLAY_MULTI_NEXT_CLICKED "PlayMultiNextClicked" #define QUICKSTART_PLAY_CLICKED "QuickstartPlayClicked" #define QUICKSTART_PLAY_NEXT_CLICKED "QuickstartPlayNextClicked" #define SHARE_CLICKED "ShareClicked" #define TAG_CLICKED "TagClicked" #define SIDE_BAR_CLICKED "SideBarClicked" #define SKIP_CLICKED "SkipClicked" #define BUY_CLICKED "BuyClicked" #define SETTINGS_CLICKED "SettingsClicked" #define BASIC_SETTINGS "Basic" #define SCROBBLING_SETTINGS "Scrobbling" class AnalyticsService : public QObject { Q_OBJECT public: AnalyticsService(); static AnalyticsService& instance(){ static AnalyticsService a; return a; } public: void sendEvent( const QString& category, const QString& action, const QString& label, const QString& value = "" ); void sendPageView( const QString& url ); private: void loadPages(); private slots: void onGotUserInfo( const lastfm::User& user ); void onLoadFinished(); private: class QWebView* m_webView; class PersistentCookieJar* m_cookieJar; QQueue m_queue; QQueue m_customVars; bool m_customVarsSet; bool m_pageLoaded; }; ================================================ FILE: app/client/Services/AnalyticsService/PersistentCookieJar.cpp ================================================ /* Copyright 2005-2012 Last.fm Ltd. - Primarily authored by Frantz Joseph This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "lib/unicorn/UnicornSettings.h" #include "PersistentCookieJar.h" PersistentCookieJar::PersistentCookieJar(QObject *parent) :QNetworkCookieJar(parent) { load(); } PersistentCookieJar::~PersistentCookieJar() { save(); } bool PersistentCookieJar::setCookiesFromUrl(const QList &cookieList, const QUrl &url) { // save every time some cookies are set bool oneOrMoreSet = QNetworkCookieJar::setCookiesFromUrl( cookieList, url ); save(); return oneOrMoreSet; } void PersistentCookieJar::save() { QList list = allCookies(); QByteArray data; foreach (QNetworkCookie cookie, list) { // don't save session cookies if (!cookie.isSessionCookie()) { data.append(cookie.toRawForm()); data.append("\n"); } } unicorn::AppSettings settings; settings.setValue("Cookies", data); } void PersistentCookieJar::load() { unicorn::AppSettings settings; QByteArray data = settings.value("Cookies").toByteArray(); setAllCookies( QNetworkCookie::parseCookies( data ) ); } ================================================ FILE: app/client/Services/AnalyticsService/PersistentCookieJar.h ================================================ /* Copyright 2005-2012 Last.fm Ltd. - Primarily authored by Frantz Joseph This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #pragma once #include class PersistentCookieJar : public QNetworkCookieJar { Q_OBJECT public: PersistentCookieJar(QObject *parent); ~PersistentCookieJar(); void load(); private: bool setCookiesFromUrl(const QList &cookieList, const QUrl &url); public slots: void save(); }; ================================================ FILE: app/client/Services/AnalyticsService.h ================================================ #include "AnalyticsService/AnalyticsService.h" ================================================ FILE: app/client/Services/RadioService/RadioService.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include "lib/unicorn/UnicornSettings.h" #include "../../Application.h" #include "RadioService.h" RadioService::RadioService() : m_audioOutput( 0 ), m_mediaObject( 0 ), m_state( Stopped ), m_bErrorRecover( false ), m_maxUsageCount( 180 ) { initRadio(); QDesktopServices::setUrlHandler( "lastfm", this, "onLastFmUrl" ); onSessionChanged( aApp->currentSession() ); connect( aApp, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onSessionChanged(unicorn::Session)) ); } void RadioService::onSessionChanged( const unicorn::Session& session ) { if ( session.user().name() != m_currentUser ) { m_currentUser = session.user().name(); // if they change user, make sure we stop the radio if ( m_mediaObject && m_mediaObject->state() != Phonon::StoppedState ) stop(); } } void RadioService::onLastFmUrl( const QUrl& url ) { RadioStation rs( url.toString() ); play( rs ); } // fixme: // todo: // note: // if the station is the same as current station (ie: the user hit stop then start) // then we *don't* retune. norman is quite emphatic about this. :) void RadioService::play( const RadioStation& station ) { if ( !aApp->currentSession().youRadio() ) { // they are not a subscriber so don't let them start the radio emit error( lastfm::ws::SubscribersOnly, tr( "You need to be a subscriber to listen to radio" ) ); return; } if( m_state == Paused && (station.url() == "" || station.url() == m_station.url() ) ) { m_mediaObject->play(); return; } if( m_state != Stopped && ( station.url() == "" || station.url() == m_station.url() ) ) { // do nothing return; } if (m_state != Stopped) { //FIXME filthy! get us to a clean slate State oldstate = m_state; m_state = Stopped; //prevents stateChanged() doing anything stop(); clear(); m_state = oldstate; } delete m_tuner; if (0 == m_audioOutput) { if (!initRadio()) { changeState( Stopped ); return; } } m_station = station; if ( m_station.url() == "" ) { unicorn::UserSettings us; QString stationUrl = us.value( "lastStationUrl", "" ).toString(); QString stationTitle = us.value( "lastStationTitle", tr( "A Radio Station" ) ).toString(); m_station.setUrl( stationUrl ); m_station.setTitle( stationTitle ); } // Make sure the radio station has the radio options from the settings bool ok; m_station.setRep( unicorn::AppSettings().value( "rep", 0.5 ).toDouble( &ok ) ); m_station.setMainstr( unicorn::AppSettings().value( "mainstr", 0.5 ).toDouble( &ok ) ); m_station.setDisco( unicorn::AppSettings().value( "disco", false ).toBool() ); m_tuner = new lastfm::RadioTuner( m_station ); connect( m_tuner, SIGNAL(title( QString )), SLOT(setStationName( QString )) ); connect( m_tuner, SIGNAL(supportsDisco( bool )), SLOT(setSupportsDisco( bool )) ); connect( m_tuner, SIGNAL(trackAvailable()), SLOT(enqueue()) ); connect( m_tuner, SIGNAL(error( lastfm::ws::Error, QString )), SLOT(onTunerError( lastfm::ws::Error, QString )) ); changeState( TuningIn ); } // play this radio station after the current track has finished void RadioService::playNext( const RadioStation& station ) { if (m_state == Playing) { m_station = station; if ( m_station.url() == "" ) { unicorn::UserSettings us; QString stationUrl = us.value( "lastStationUrl", "" ).toString(); QString stationTitle = us.value( "lastStationTitle", tr( "A Radio Station" ) ).toString(); m_station.setUrl( stationUrl ); m_station.setTitle( stationTitle ); } // Make sure the radio station has the radio options from the settings bool ok; m_station.setRep( unicorn::AppSettings().value( "rep", 0.5 ).toDouble( &ok ) ); m_station.setMainstr( unicorn::AppSettings().value( "mainstr", 0.5 ).toDouble( &ok ) ); m_station.setDisco( unicorn::AppSettings().value( "disco", false ).toBool() ); m_tuner->retune( m_station ); } } void RadioService::enqueue() { qDebug() << "enqueue"; if (m_state == Stopped) { // this should be impossible. If we are stopped, then the GUI looks // stopped too, and receiving tracks to play will result in a playing // radio and a stopped GUI. NICE. Q_ASSERT( 0 ); return; } phononEnqueue(); } void RadioService::skip() { if (!m_mediaObject) return; // attempt to refill the phonon queue if it's empty if (m_mediaObject->queue().isEmpty()) phononEnqueue(); QList q = m_mediaObject->queue(); if ( q.size() ) { m_mediaObject->setCurrentSource( q[0] ); //if the error returns a 403 permission denied error, the mediaObject is uninitialised if( m_mediaObject ) m_mediaObject->play(); else { initRadio(); play( RadioStation()); } } else if (m_state != Stopped) { qDebug() << "queue empty"; // we are still waiting for a playlist to come back from the tuner m_mediaObject->blockSignals( true ); //don't tell outside world that we stopped m_mediaObject->stop(); m_mediaObject->setCurrentSource( QUrl() ); m_mediaObject->blockSignals( false ); changeState( TuningIn ); } } void RadioService::onTunerError( lastfm::ws::Error e, const QString& message ) { // otherwise leave things be, we'll stop when we run out of content if (m_state == TuningIn) stop(); emit error( e, message ); } void RadioService::stop() { delete m_tuner; m_mediaObject->blockSignals( true ); //prevent the error state due to setting current source to null m_mediaObject->stop(); m_mediaObject->clearQueue(); m_mediaObject->setCurrentSource( QUrl() ); m_mediaObject->blockSignals( false ); clear(); changeState( Stopped ); } void RadioService::pause() { Q_ASSERT( m_mediaObject ); if ( m_mediaObject ) { m_mediaObject->pause(); changeState( Paused ); } } void RadioService::resume() { Q_ASSERT( m_mediaObject ); if ( m_mediaObject ) { m_mediaObject->play(); changeState( Playing ); } } void RadioService::clear() { m_track = Track(); m_station = RadioStation(); delete m_tuner; } void RadioService::volumeUp() { } void RadioService::volumeDown() { } void RadioService::mute() { m_audioOutput->setMuted( !m_audioOutput->isMuted() ); } void RadioService::onPhononStateChanged( Phonon::State newstate, Phonon::State oldstate ) { qDebug() << (int)oldstate << " -> " << (int)newstate; if (m_mediaObject == 0) { qDebug() << "m_mediaObject is null!"; return; } switch (newstate) { case Phonon::ErrorState: qWarning() << m_mediaObject->errorType() << m_mediaObject->errorString(); emit error( lastfm::ws::UnknownError, QVariant( m_mediaObject->errorString() )); deInitRadio(); changeState( Stopped ); break; case Phonon::PausedState: // if the play queue runs out we get this for some reason // this means we are fetching new tracks still, we should show a // tuning in state; if (m_mediaObject->queue().size() == 0) { qDebug() << "queue empty, going to TuningIn"; changeState( Paused ); } break; case Phonon::StoppedState: if (m_bErrorRecover) { m_bErrorRecover = false; skip(); } break; case Phonon::BufferingState: changeState( Buffering ); restoreVolume(); break; case Phonon::PlayingState: changeState( Playing ); break; case Phonon::LoadingState: break; } } void RadioService::restoreVolume() { // restore the last volume if ( unicorn::AppSettings().contains("Volume") ) { bool ok; double volume = unicorn::AppSettings().value( "Volume", 1 ).toReal(&ok); if (ok) m_audioOutput->setVolume(volume); else m_audioOutput->setVolume( 1 ); } else m_audioOutput->setVolume( 1 ); m_audioOutput->setMuted(unicorn::AppSettings().value("Muted", false).toBool()); } void RadioService::phononEnqueue() { qDebug() << "phononEnqueue"; qDebug() << "queue size: " << m_mediaObject->queue().size(); if (m_mediaObject->queue().size() || !m_tuner) return; // keep only one track in the phononQueue // Loop until we get a null url or a valid url. forever { // consume next track from the track source. a null track // response means wait until the trackAvailable signal Track t = m_tuner->takeNextTrack(); if (t.isNull()) { m_track = t; changeState( TuningIn ); break; } // Invalid urls won't trigger the correct phonon // state changes, so we must filter them. if (!t.url().isValid()) continue; m_track = t; Phonon::MediaSource ms( t.url() ); qDebug() << "enqueuing " << t; try { m_mediaObject->enqueue( ms ); m_mediaObject->play(); } catch (...) { continue; } break; } } // onPhononCurrentSourceChanged happens always (even if the source is // unplayable), so we use it to update our now playing track. void RadioService::onPhononCurrentSourceChanged( const Phonon::MediaSource& ) { if (m_mediaObject == 0) { qDebug() << "m_mediaObject is null!"; return; } if ( !m_track.isNull() ) { MutableTrack( m_track ).stamp(); if (m_mediaObject->state() != Phonon::PlayingState) changeState( Buffering ); emit trackSpooled( m_track ); } } void RadioService::changeState( State const newstate ) { State const oldstate = m_state; if (oldstate == newstate) return; qDebug().nospace() << newstate << " (was " << oldstate << ')'; m_state = newstate; // always assign state properties before you tell other // objects about it switch (newstate) { case TuningIn: qDebug() << "Tuning to:" << m_station; emit tuningIn( m_station ); break; default: case Buffering: break; case Playing: emit resumed(); break; case Stopped: emit stopped(); break; case Paused: emit paused(); break; } } void RadioService::setStationName( const QString& s ) { m_station.setTitle( s ); unicorn::UserSettings us; us.setValue( "lastStationUrl", m_station.url() ); us.setValue( "lastStationTitle", m_station.title() ); } void RadioService::setSupportsDisco( bool supportsDiscovery ) { emit supportsDisco( supportsDiscovery ); } void RadioService::setMaxUsageCount(int count) { m_maxUsageCount = count; } void RadioService::onBuffering( int percent_filled ) { Q_UNUSED(percent_filled); emit buffering( percent_filled ); } void RadioService::onMutedChanged(bool muted) { unicorn::AppSettings().setValue("Muted", muted); } void RadioService::onOutputDeviceChanged(const Phonon::AudioOutputDevice& newDevice) { qDebug() << "name: " << newDevice.name() << " description: " << newDevice.description(); } void RadioService::onVolumeChanged(qreal vol) { unicorn::AppSettings().setValue( "Volume", vol ); } void RadioService::onFinished() { // A track has finished and there is nothing else in the queue // try to go to the next track skip(); } // returns true on successful initialisation bool RadioService::initRadio() { qDebug() << "initRadio"; Phonon::AudioOutput* audioOutput = new Phonon::AudioOutput( Phonon::MusicCategory ); qDebug() << audioOutput->name(); qDebug() << audioOutput->outputDevice().description(); qDebug() << audioOutput->outputDevice().name(); qDebug() << (audioOutput->isMuted() ? "muted,": "not-muted,") << (audioOutput->isValid() ? "valid,": "not-valid,") << audioOutput->volumeDecibel() << "db " << audioOutput->volume(); foreach (QByteArray a, audioOutput->outputDevice().propertyNames()) { qDebug() << a << ":" << audioOutput->outputDevice().property(a); } QString audioOutputDeviceName = "";//TODO moose::Settings().audioOutputDeviceName(); if (audioOutputDeviceName.size()) { foreach (Phonon::AudioOutputDevice d, Phonon::BackendCapabilities::availableAudioOutputDevices()) if (d.name() == audioOutputDeviceName) { audioOutput->setOutputDevice( d ); break; } } Phonon::MediaObject* mediaObject = new Phonon::MediaObject; m_path = Phonon::createPath( mediaObject, audioOutput ); if (!m_path.isValid()) { qDebug() << "Phonon::createPath failed"; // can't delete the mediaObject after a failed Phonon::createPath without a crash in phonon... (Qt 4.4.3) // so, we leak it: // mediaObject->deleteLater(); audioOutput->deleteLater(); return false; } mediaObject->setTickInterval( 500 ); connect( mediaObject, SIGNAL(stateChanged( Phonon::State, Phonon::State )), SLOT(onPhononStateChanged( Phonon::State, Phonon::State )) ); connect( mediaObject, SIGNAL(bufferStatus(int)), SLOT(onBuffering(int))); connect( mediaObject, SIGNAL(currentSourceChanged( Phonon::MediaSource )), SLOT(onPhononCurrentSourceChanged( Phonon::MediaSource )) ); connect( mediaObject, SIGNAL(aboutToFinish()), SLOT(phononEnqueue()) ); // this fires when the whole queue is about to finish connect( mediaObject, SIGNAL(finished()), SLOT(onFinished())); connect( mediaObject, SIGNAL(tick(qint64)), SIGNAL(tick(qint64))); connect( audioOutput, SIGNAL(mutedChanged(bool)), SLOT(onMutedChanged(bool))); connect( audioOutput, SIGNAL(outputDeviceChanged(Phonon::AudioOutputDevice)), SLOT(onOutputDeviceChanged(Phonon::AudioOutputDevice))); connect( audioOutput, SIGNAL(volumeChanged(qreal)), SLOT(onVolumeChanged(qreal))); m_audioOutput = audioOutput; m_mediaObject = mediaObject; restoreVolume(); return true; } void RadioService::deInitRadio() { qDebug() << "deInitRadio"; // try to deleteLater and phonon crashes. poo. // leak em... :( m_audioOutput->deleteLater(); m_audioOutput = 0; m_mediaObject->deleteLater(); m_mediaObject = 0; } ================================================ FILE: app/client/Services/ScrobbleService/ScrobbleService.cpp ================================================ /* Copyright 2005-2010 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "ScrobbleService.h" #include #include "../../Application.h" #if defined(Q_OS_WIN) || defined(Q_OS_MAC) #include "lib/listener/legacy/LegacyPlayerListener.h" #else #include "lib/listener/mpris2/Mpris2Listener.h" #endif #include "lib/listener/PlayerConnection.h" #include "lib/listener/PlayerListener.h" #include "lib/listener/PlayerMediator.h" #include "../MediaDevices/DeviceScrobbler.h" #include "StopWatch.h" #ifdef Q_WS_MAC #include "lib/listener/mac/SpotifyListener.h" #include "lib/listener/mac/ITunesListener.h" #endif #ifdef Q_OS_WIN #include "lib/listener/win/SpotifyListener.h" #endif ScrobbleService::ScrobbleService() { qRegisterMetaType("Track"); /// mediator m_mediator = new PlayerMediator(this); connect( m_mediator, SIGNAL(activeConnectionChanged( PlayerConnection* )), SLOT(setConnection( PlayerConnection* )) ); /// listeners try{ #ifdef Q_OS_MAC ITunesListener* itunes = new ITunesListener(m_mediator); connect(itunes, SIGNAL(newConnection(PlayerConnection*)), m_mediator, SLOT(follow(PlayerConnection*))); SpotifyListenerMac* spotify = new SpotifyListenerMac(m_mediator); connect(spotify, SIGNAL(newConnection(PlayerConnection*)), m_mediator, SLOT(follow(PlayerConnection*))); #endif #ifdef Q_OS_WIN SpotifyListenerWin* spotify = new SpotifyListenerWin(m_mediator); connect(spotify, SIGNAL(newConnection(PlayerConnection*)), m_mediator, SLOT(follow(PlayerConnection*))); #endif QObject* o = new PlayerListener(m_mediator); connect(o, SIGNAL(newConnection(PlayerConnection*)), m_mediator, SLOT(follow(PlayerConnection*))); #if defined(Q_OS_WIN) || defined(Q_OS_MAC) o = new LegacyPlayerListener(m_mediator); connect(o, SIGNAL(newConnection(PlayerConnection*)), m_mediator, SLOT(follow(PlayerConnection*))); #else Mpris2Listener* mpris2 = new Mpris2Listener(m_mediator); connect(mpris2, SIGNAL(newConnection(PlayerConnection*)), m_mediator, SLOT(follow(PlayerConnection*))); mpris2->createConnection(); #endif } catch(std::runtime_error& e){ qWarning() << e.what(); //TODO user visible warning } connect( aApp, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onSessionChanged(unicorn::Session)) ); resetScrobbler(); } ScrobbleService& ScrobbleService::instance() { static ScrobbleService s; return s; } bool ScrobbleService::isDirExcluded( const lastfm::Track& track ) { QString pathToTest = track.url().toLocalFile(); #ifdef Q_OS_WIN pathToTest = pathToTest.toLower(); #endif if ( pathToTest.isEmpty() ) return false; unicorn::UserSettings us; QStringList exculsionDirs = us.value( "ExclusionDirs" ).toStringList(); foreach ( QString bannedPath, exculsionDirs ) { bannedPath = QDir( bannedPath ).absolutePath(); #ifdef Q_OS_WIN bannedPath = bannedPath.toLower(); #endif qDebug() << pathToTest << bannedPath; // Try and match start of given path with banned dir if ( pathToTest.startsWith( bannedPath ) ) { // Found, this path is from a banned dir return true; } } // The path wasn't found in exclusions list return false; } bool ScrobbleService::scrobblableTrack( const lastfm::Track& track ) const { unicorn::UserSettings userSettings; return userSettings.scrobblingOn() && ( track.extra( "playerId" ) != "spt" && track.extra( "playerId" ) != "mpris2" ) && !track.artist().isNull() && ( userSettings.podcasts() || !track.isPodcast() ) && !track.isVideo() && !isDirExcluded( track ); } bool ScrobbleService::scrobblingOn() const { return scrobblableTrack( m_currentTrack ); } void ScrobbleService::scrobbleSettingsChanged() { if ( m_watch ) { ScrobblePoint timeout( ( m_currentTrack.duration() * unicorn::UserSettings().scrobblePoint() ) / 100.0 ); timeout.setEnforceScrobbleTimeMax( unicorn::UserSettings().enforceScrobbleTimeMax() ); m_watch->setScrobblePoint( timeout ); } bool scrobblingOn = scrobblableTrack( m_currentTrack ); // Check if the current track now meets the requirements // and scrobble it straight away if so if ( m_as && m_watch && m_watch->elapsed() >= (m_watch->scrobblePoint() * 1000) && m_currentTrack.scrobbleStatus() == Track::Null && scrobblingOn ) m_as->cache( m_currentTrack ); emit scrobblingOnChanged( scrobblingOn ); } void ScrobbleService::submitCache() { if ( m_as ) m_as->submit(); } void ScrobbleService::onSessionChanged( const unicorn::Session& ) { resetScrobbler(); } void ScrobbleService::resetScrobbler() { if ( !aApp->currentSession().user().name().isEmpty() && aApp->currentSession().user().name() != m_currentUsername) { // only create the scrobble cache, etc if we have a user // we won't have a user during the first run wizard m_currentUsername = aApp->currentSession().user().name(); /// audioscrobbler delete m_as; m_as = new Audioscrobbler( "ass" ); connect( m_as, SIGNAL(scrobblesCached(QList)), SIGNAL(scrobblesCached(QList))); connect( m_as, SIGNAL(scrobblesSubmitted(QList)), SIGNAL(scrobblesSubmitted(QList))); /// DeviceScrobbler delete m_deviceScrobbler; m_deviceScrobbler = new DeviceScrobbler( this ); connect( m_deviceScrobbler, SIGNAL(foundScrobbles(QList)), SLOT(onFoundScrobbles(QList))); connect( m_deviceScrobbler, SIGNAL(foundScrobbles(QList)), SIGNAL(foundIPodScrobbles(QList))); // Do this a bit later to as it's nicer for the user and // it gives the main window time to be diplayed on boot QTimer::singleShot( 3000, m_deviceScrobbler, SLOT(checkCachedIPodScrobbles()) ); } } void ScrobbleService::onFoundScrobbles( QList tracks ) { m_as->cacheBatch( tracks ); m_as->submit(); } void ScrobbleService::setConnection(PlayerConnection*c) { if( m_connection ) { // disconnect from all the objects that we connect to below disconnect( m_connection, 0, this, 0); if(m_watch) m_connection->setElapsed(m_watch->elapsed()); } // connect(c, SIGNAL(trackStarted(lastfm::Track,lastfm::Track)), this, SLOT(onTrackStarted(lastfm::Track,lastfm::Track)), Qt::QueuedConnection); connect(c, SIGNAL(paused()), this, SLOT(onPaused()), Qt::QueuedConnection); connect(c, SIGNAL(resumed()), this, SLOT(onResumed()), Qt::QueuedConnection); connect(c, SIGNAL(stopped()), this, SLOT(onStopped()), Qt::QueuedConnection); connect(c, SIGNAL(bootstrapReady(QString)), SIGNAL( bootstrapReady(QString))); m_connection = c; if(c->state() == Playing || c->state() == Paused) c->forceTrackStarted(Track()); if( c->state() == Paused ) c->forcePaused(); } void ScrobbleService::onTrackStarted( const lastfm::Track& t, const lastfm::Track& ot ) { Q_ASSERT(m_connection); //TODO move to playerconnection if(t.isNull()) { qWarning() << "Can't start null track!"; return; } Track oldtrack = ot.isNull() ? m_currentTrack : ot; if ( unicorn::UserSettings().scrobblePoint() == 100.0 && !oldtrack.isNull() ) { // was the last track at 100%? Should we scrobble it? if ( m_watch ) { /** At this point, we can't tell for ceratin if a track was played until the end. * The next lowest percentage the user can set is 95% which means a 30 second track * will scrobble at 28.5 seconds. As long as we're above this, I think we're fine. * Here we'rechecking if the track got within 1 second of the end point At 30 seconds. * At 30 seconds this equates to 96.66%, which is tolerable. Assuming an average 3 * minute track length, the actual percentage we will at is 99.44% which is close * enough to 100% for me. As this is timing based, things can go slightly off, it's * still possible that scrobbles will be missed, but these are the perils of setting * your scrobble point to 100%. * TODO: really the plugins should be able to tell us if the track got to 100% and * we just use that, but I'm not sure if that's possible and that would be a rather * big change. This should be enough for now. */ if ( !m_watch->paused() // the watch is running (if we scrobble after the user paused and skipped, it would give away we're not actually scrobbling at 100%) && oldtrack.duration() == m_watch->duration() // the duration of the track and the stop watch duration is the same (double checking it's the same track) && m_watch->elapsed() > (m_watch->duration() * 1000) - 1000 ) // it's within a second so assume it got to 100% (see above) onScrobble(); // we should scrobble the last track } } m_state = Playing; m_currentTrack = t; ScrobblePoint timeout( ( m_currentTrack.duration() * unicorn::UserSettings().scrobblePoint() ) / 100.0 ); timeout.setEnforceScrobbleTimeMax( unicorn::UserSettings().enforceScrobbleTimeMax() ); delete m_watch; m_watch = new StopWatch(m_currentTrack.duration(), timeout); m_watch->start(); connect( m_watch, SIGNAL(scrobble()), SLOT(onScrobble())); connect( m_watch, SIGNAL(paused(bool)), SIGNAL(paused(bool))); connect( m_watch, SIGNAL(frameChanged( int )), SIGNAL(frameChanged( int ))); connect( m_watch, SIGNAL(timeout()), SIGNAL(timeout())); qDebug() << "********** AS = " << m_as; if( m_as ) { m_as->submit(); if ( scrobblableTrack( t ) ) { qDebug() << "************** Now Playing.."; m_as->nowPlaying( t ); } } emit trackStarted( t, oldtrack ); } void ScrobbleService::onPaused() { // We can sometimes get a stopped before a play when the // media player is playing before the scrobbler is started if ( m_state == Unknown ) return; m_state = Paused; //m_currentTrack.removeNowPlaying(); Q_ASSERT(m_connection); Q_ASSERT(m_watch); if(m_watch) m_watch->pause(); emit paused(); } void ScrobbleService::onStopped() { // We can sometimes get a stopped before a play when the // media player is playing before the scrobbler is started if ( m_state == Unknown ) return; m_state = Stopped; Q_ASSERT(m_watch); Q_ASSERT(m_connection); delete m_watch; if( m_as ) m_as->submit(); emit stopped(); } void ScrobbleService::onResumed() { // We can sometimes get a stopped before a play when the // media player is playing before the scrobbler is started if ( m_state == Unknown ) return; m_state = Playing; Q_ASSERT(m_watch); Q_ASSERT(m_connection); //m_currentTrack.updateNowPlaying( m_currentTrack.duration() - (m_watch->elapsed()/1000) ); if (m_watch) m_watch->resume(); emit resumed(); } void ScrobbleService::onScrobble() { Q_ASSERT(m_connection); if( m_as && scrobblableTrack( m_currentTrack ) && m_currentTrack.scrobbleStatus() == Track::Null ) m_as->cache( m_currentTrack ); } void ScrobbleService::handleIPodDetectedMessage( const QStringList& message ) { m_deviceScrobbler->iPodDetected( message ); } void ScrobbleService::handleTwiddlyMessage( const QStringList& message ) { m_deviceScrobbler->handleMessage( message ); } ================================================ FILE: app/client/Services/ScrobbleService/ScrobbleService.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SCROBBLE_SERVICE_H #define SCROBBLE_SERVICE_H #include #include #include "lib/listener/State.h" #include #include namespace unicorn { class Session; } class PlayerMediator; class PlayerConnection; class StopWatch; class DeviceScrobbler; class ScrobbleService : public QObject { Q_OBJECT public: ScrobbleService(); bool scrobblableTrack( const lastfm::Track& track ) const; static bool isDirExcluded( const lastfm::Track& track ); Track currentTrack() const { return m_currentTrack; } QPointer deviceScrobbler() { return m_deviceScrobbler; } QPointer currentConnection() { return m_connection; } QPointer stopWatch() { return m_watch; } void handleTwiddlyMessage( const QStringList& message ); void handleIPodDetectedMessage( const QStringList& message ); static ScrobbleService& instance(); public slots: void onSessionChanged( const unicorn::Session& session ); void onScrobble(); void scrobbleSettingsChanged(); signals: void trackStarted( const lastfm::Track& newTrack, const lastfm::Track& oldTrack ); void resumed(); void paused(); void stopped(); void scrobblingOnChanged( bool scrobblingOn ); void scrobblesCached( const QList& tracks ); void scrobblesSubmitted( const QList& tracks ); void foundIPodScrobbles( const QList& tracks ); void bootstrapReady( const QString& playerId ); void paused( bool ); void frameChanged( int ); void timeout(); public slots: void submitCache(); protected slots: void setConnection( PlayerConnection* ); void onTrackStarted( const lastfm::Track&, const lastfm::Track& ); void onPaused(); void onResumed(); void onStopped(); void onFoundScrobbles( QList tracks ); private: void resetScrobbler(); bool scrobblingOn() const; protected: State m_state; QPointer m_watch; QPointer m_mediator; QPointer m_connection; QPointer m_as; QPointer m_deviceScrobbler; Track m_currentTrack; QString m_currentUsername; }; #endif //SCROBBLE_SERVICE_H_ ================================================ FILE: app/client/Services/ScrobbleService/StopWatch.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "StopWatch.h" #include StopWatch::StopWatch( uint duration, ScrobblePoint timeout ) : m_duration( duration ), m_point( timeout ), m_scrobbled(false) { m_timeline = new QTimeLine( duration * 1000, this ); m_timeline->setFrameRange( 0, duration * 2 ); m_timeline->setEasingCurve( QEasingCurve::Linear ); m_timeline->setUpdateInterval( 50 ); m_timeline->setCurrentTime( 0 ); connect( m_timeline, SIGNAL(finished()), SIGNAL(timeout()) ); connect( m_timeline, SIGNAL(frameChanged(int)), SLOT(onFrameChanged(int))); } ScrobblePoint StopWatch::scrobblePoint() const { return m_point; } void StopWatch::setScrobblePoint( const ScrobblePoint& timeout_in_seconds ) { m_point = timeout_in_seconds; } uint StopWatch::duration() const { return m_duration; } bool StopWatch::scrobbled() const { return m_scrobbled; } void StopWatch::onFrameChanged( int /*frame*/ ) { emit frameChanged( m_timeline->currentTime() ); if ( !m_scrobbled && elapsed() >= (m_point * 1000) ) { emit scrobble(); m_scrobbled = true; } } bool StopWatch::paused() { return (m_timeline->state() == QTimeLine::Paused); } void StopWatch::start() { m_timeline->start(); emit paused( false ); } void StopWatch::pause() { m_timeline->setPaused( true ); emit paused( true ); } void StopWatch::resume() { // Only resume if we are already running if ( m_timeline->state() == QTimeLine::Paused ) m_timeline->resume(); emit paused( false ); } uint StopWatch::elapsed() const { return m_timeline->currentTime(); } ================================================ FILE: app/client/Services/ScrobbleService/StopWatch.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef STOP_WATCH_H #define STOP_WATCH_H #include #include #include namespace audioscrobbler { class Application; } /** Emits timeout() after seconds specified to start. * Continues to measure time after that point until object death. */ class StopWatch : public QObject { Q_OBJECT Q_DISABLE_COPY( StopWatch ) friend class audioscrobbler::Application; //for access to timeout() signal friend class TestStopWatch; //for testing, duh! public: /** The StopWatch starts off paused, call resume() to start. * The watch will not timeout() if elapsed is greater that the * scrobble point */ StopWatch( uint duration_in_seconds, ScrobblePoint timeout_in_seconds ); void start(); void pause(); void resume(); bool paused(); /** in milliseconds */ uint elapsed() const; ScrobblePoint scrobblePoint() const; void setScrobblePoint( const ScrobblePoint& timeout_in_seconds ); uint duration() const; signals: void paused( bool ); void frameChanged( int millisecs ); void scrobble(); void timeout(); private slots: void onFrameChanged( int frame ); private: bool scrobbled() const; private: class QTimeLine* m_timeline; uint m_duration; ScrobblePoint m_point; bool m_scrobbled; }; #endif ================================================ FILE: app/client/Services/ScrobbleService.h ================================================ #include "ScrobbleService/ScrobbleService.h" #include "ScrobbleService/StopWatch.h" ================================================ FILE: app/client/Settings/AccountSettingsWidget.cpp ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole, Michael Coffey, and William Viana This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "ui_AccountSettingsWidget.h" #include "AccountSettingsWidget.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/UnicornApplication.h" #include "lib/unicorn/UnicornSession.h" #include "lib/unicorn/UnicornSettings.h" #include #include #include #include #include #include #include using lastfm::User; AccountSettingsWidget::AccountSettingsWidget( QWidget* parent ) : SettingsWidget( parent ), ui( new Ui::AccountSettingsWidget ) { ui->setupUi( this ); connect( ui->users, SIGNAL(userChanged() ), SLOT(onSettingsChanged() ) ); connect( ui->users, SIGNAL(rosterUpdated() ), qApp, SIGNAL(rosterUpdated() ) ); } void AccountSettingsWidget::saveSettings() { qDebug() << "has unsaved changes?" << hasUnsavedChanges(); if ( hasUnsavedChanges() ) { UserRadioButton* urb = qobject_cast( ui->users->checkedButton() ); if ( urb && urb->user() != User().name() ) { unicorn::Settings s; s.setValue( "Username", urb->user() ); unicorn::UserSettings us( urb->user() ); QString sessionKey = us.sessionKey(); qobject_cast( qApp )->changeSession( urb->user(), sessionKey, true ); } onSettingsSaved(); } } ================================================ FILE: app/client/Settings/AccountSettingsWidget.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole, Michael Coffey, and William Viana This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef ACCOUNT_SETTINGS_WIDGET_H_ #define ACCOUNT_SETTINGS_WIDGET_H_ #include "SettingsWidget.h" namespace Ui { class AccountSettingsWidget; } class AccountSettingsWidget: public SettingsWidget { Q_OBJECT public: AccountSettingsWidget( QWidget* parent = 0 ); public slots: virtual void saveSettings(); private: void setupUi(); void populateLanguages(); private: Ui::AccountSettingsWidget* ui; }; #endif //ACCOUNT_SETTINGS_WIDGET_H_ ================================================ FILE: app/client/Settings/AccountSettingsWidget.ui ================================================ AccountSettingsWidget 0 0 400 300 Form UserManagerWidget QWidget
    lib/unicorn/widgets/UserManagerWidget.h
    1
    ================================================ FILE: app/client/Settings/AdvancedSettingsWidget.cpp ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole, Michael Coffey, and William Viana This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/UnicornApplication.h" #include "lib/unicorn/UnicornSession.h" #include "lib/unicorn/widgets/UserManagerWidget.h" #include "../Widgets/ShortcutEdit.h" #include "../AudioscrobblerSettings.h" #include "../Application.h" #include "ui_AdvancedSettingsWidget.h" #include "AdvancedSettingsWidget.h" using lastfm::User; AdvancedSettingsWidget::AdvancedSettingsWidget( QWidget* parent ) : SettingsWidget( parent ), ui( new Ui::AdvancedSettingsWidget ) { ui->setupUi( this ); AudioscrobblerSettings settings; #ifdef Q_WS_X11 ui->shortcuts->hide(); #else ui->sce->setTextValue( settings.raiseShortcutDescription() ); ui->sce->setModifiers( settings.raiseShortcutModifiers() ); ui->sce->setKey( settings.raiseShortcutKey() ); connect( ui->sce, SIGNAL(editTextChanged(QString)), this, SLOT(onSettingsChanged())); #endif ui->cache->hide(); ui->ssl->setVisible( QSslSocket::supportsSsl() ); ui->ssl->setChecked( settings.value( "enableSsl", false ).toBool() ); connect( ui->ssl, SIGNAL(clicked()), SLOT(onSettingsChanged())); connect( ui->proxySettings, SIGNAL(changed()), SLOT(onSettingsChanged())); } void AdvancedSettingsWidget::saveSettings() { qDebug() << "has unsaved changes?" << hasUnsavedChanges(); if ( hasUnsavedChanges() ) { AudioscrobblerSettings settings; settings.setRaiseShortcutKey( ui->sce->key() ); settings.setRaiseShortcutModifiers( ui->sce->modifiers() ); settings.setRaiseShortcutDescription( ui->sce->textValue() ); aApp->setRaiseHotKey( ui->sce->modifiers(), ui->sce->key() ); ui->proxySettings->save(); settings.setValue( "enableSsl", ui->ssl->isChecked() ); lastfm::ws::setScheme( ui->ssl->isChecked() ? lastfm::ws::Https : lastfm::ws::Http ); } } ================================================ FILE: app/client/Settings/AdvancedSettingsWidget.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole, Michael Coffey, and William Viana This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef ADVANCED_SETTINGS_WIDGET_H_ #define ADVANCED_SETTINGS_WIDGET_H_ #include "SettingsWidget.h" namespace Ui { class AdvancedSettingsWidget; } class AdvancedSettingsWidget: public SettingsWidget { Q_OBJECT public: AdvancedSettingsWidget( QWidget* parent = 0 ); public slots: virtual void saveSettings(); private: Ui::AdvancedSettingsWidget* ui; }; #endif //KEYBOARD_SETTINGS_WIDGET_H_ ================================================ FILE: app/client/Settings/AdvancedSettingsWidget.ui ================================================ AdvancedSettingsWidget 0 0 461 398 Form Keyboard Shortcuts: false Raise/Hide Last.fm true false Cache Size: true Proxy: Enable SSL unicorn::ProxyWidget QWidget
    lib/unicorn/widgets/ProxyWidget.h
    1
    ShortcutEdit QComboBox
    ../Widgets/ShortcutEdit.h
    ================================================ FILE: app/client/Settings/CheckFileSystemModel.cpp ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "CheckFileSystemModel.h" CheckFileSystemModel::CheckFileSystemModel( QObject *parent ) :QFileSystemModel( parent ) { } Qt::ItemFlags CheckFileSystemModel::flags( const QModelIndex& index) const { return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable; } QVariant CheckFileSystemModel::data( const QModelIndex& index, int role) const { if (role == Qt::CheckStateRole) { qint64 id = index.internalId(); return m_checkTable.contains(id) ? m_checkTable.value(id) : Qt::Unchecked; } else { return QFileSystemModel::data(index, role); } } bool CheckFileSystemModel::setData( const QModelIndex& index, const QVariant& value, int role ) { if (role == Qt::CheckStateRole) { m_checkTable.insert(index.internalId(), (Qt::CheckState)value.toInt()); emit dataChanged(index, index); emit dataChangedByUser(index); return true; } else { return QFileSystemModel::setData(index, value, role); } } void CheckFileSystemModel::setCheck( const QModelIndex& index, const QVariant& value) { m_checkTable.insert(index.internalId(), (Qt::CheckState)value.toInt()); emit dataChanged(index, index); } Qt::CheckState CheckFileSystemModel::getCheck( const QModelIndex& index ) { return (Qt::CheckState)data(index, Qt::CheckStateRole).toInt(); } ================================================ FILE: app/client/Settings/CheckFileSystemModel.h ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef CHECKFILESYSTEMMODEL_H #define CHECKFILESYSTEMMODEL_H #include class CheckFileSystemModel : public QFileSystemModel { Q_OBJECT public: explicit CheckFileSystemModel( QObject *parent = 0 ); virtual Qt::ItemFlags flags( const QModelIndex& index ) const; virtual QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const; virtual bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole); void setCheck( const QModelIndex& index, const QVariant& value); Qt::CheckState getCheck( const QModelIndex& index ); signals: void dataChangedByUser( const QModelIndex & index); private: QHash m_checkTable; }; #endif // CHECKFILESYSTEMMODEL_H ================================================ FILE: app/client/Settings/CheckFileSystemView.cpp ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "CheckFileSystemView.h" #include CheckFileSystemView::CheckFileSystemView( QWidget* parent ) :QTreeView( parent ) { m_fsModel = new CheckFileSystemModel( this ); m_fsModel->setRootPath( "" ); m_fsModel->setFilter( QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks ); setModel( m_fsModel ); setRootIndex( m_fsModel->index( "" ) ); setColumnHidden(1, true); setColumnHidden(2, true); setColumnHidden(3, true); //header()->hide(); connect( m_fsModel, SIGNAL(dataChangedByUser(const QModelIndex&)), SLOT(updateNode(const QModelIndex&))); connect( m_fsModel, SIGNAL(dataChangedByUser(const QModelIndex&)), SIGNAL(dataChanged())); connect( this, SIGNAL(collapsed(const QModelIndex&)), SLOT(onCollapse(const QModelIndex&))); connect( this, SIGNAL(expanded(const QModelIndex&)), SLOT(onExpand(const QModelIndex&))); } void CheckFileSystemView::checkPath( const QString& path, Qt::CheckState state ) { QModelIndex index = m_fsModel->index(path); m_fsModel->setCheck(index, state); updateNode(index); } void CheckFileSystemView::setExclusions( const QStringList& list ) { foreach(QString path, list) { checkPath(path, Qt::Checked); } } QStringList CheckFileSystemView::getExclusions() { QStringList exclusions; QModelIndex root = rootIndex(); getExclusionsForNode(root, exclusions); return exclusions; } void CheckFileSystemView::getExclusionsForNode( const QModelIndex& index, QStringList& exclusions) { // Look at first node // Is it unchecked? // - move on to next node // Is it checked? // - add to list // - move to next node // Is it partially checked? // - recurse int numChildren = m_fsModel->rowCount(index); for (int i = 0; i < numChildren; ++i) { QModelIndex kid = m_fsModel->index(i, 0, index); Qt::CheckState check = m_fsModel->getCheck(kid); if (check == Qt::Unchecked) { continue; } else if (check == Qt::Checked) { exclusions.append(m_fsModel->filePath(kid)); } else if (check == Qt::PartiallyChecked) { getExclusionsForNode(kid, exclusions); } else { Q_ASSERT(false); } } } void CheckFileSystemView::onCollapse( const QModelIndex& /*idx*/ ) { } void CheckFileSystemView::onExpand( const QModelIndex& idx ) { // If the node is partially checked, that means we have been below it // setting some stuff, so only fill down if we are unchecked. if (m_fsModel->getCheck(idx) != Qt::PartiallyChecked) { fillDown(idx); } } void CheckFileSystemView::updateNode( const QModelIndex& idx ) { // Start by recursing down to the bottom and then work upwards fillDown(idx); updateParent(idx); } void CheckFileSystemView::fillDown( const QModelIndex& parent ) { // Recursion stops when we reach a directory which has never been expanded // or one that has no children. if (!isExpanded(parent) || !m_fsModel->hasChildren(parent)) { return; } Qt::CheckState state = m_fsModel->getCheck(parent); int numChildren = m_fsModel->rowCount(parent); for (int i = 0; i < numChildren; ++i) { QModelIndex kid = m_fsModel->index(i, 0, parent); m_fsModel->setCheck(kid, state); fillDown(kid); } } void CheckFileSystemView::updateParent( const QModelIndex& index ) { QModelIndex parent = index.parent(); if (!parent.isValid()) { // We have reached the root return; } // Initialise overall state to state of first child QModelIndex kid = m_fsModel->index(0, 0, parent); Qt::CheckState overall = m_fsModel->getCheck(kid); int numChildren = m_fsModel->rowCount(parent); for (int i = 1; i <= numChildren; ++i) { kid = m_fsModel->index(i, 0, parent); Qt::CheckState state = m_fsModel->getCheck(kid); if (state != overall) { // If we ever come across a state different than the first child, // we are partially checked overall = Qt::PartiallyChecked; break; } } m_fsModel->setCheck(parent, overall); updateParent(parent); } ================================================ FILE: app/client/Settings/CheckFileSystemView.h ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef CHECKFILESYSTEMVIEW_H #define CHECKFILESYSTEMVIEW_H #include "CheckFileSystemModel.h" #include class CheckFileSystemView : public QTreeView { Q_OBJECT public: explicit CheckFileSystemView( QWidget* parent = 0 ); void checkPath( const QString& path, Qt::CheckState state ); void setExclusions( const QStringList& list ); QStringList getExclusions(); signals: void dataChanged(); private: void fillDown( const QModelIndex& index); void updateParent( const QModelIndex& index ); void getExclusionsForNode( const QModelIndex& index, QStringList&exclusions); private slots: void onCollapse( const QModelIndex& idx ); void onExpand( const QModelIndex& idx); void updateNode( const QModelIndex& idx); private: CheckFileSystemModel* m_fsModel; QSet m_expandedSet; }; #endif // CHECKFILESYSTEMVIEW_H ================================================ FILE: app/client/Settings/GeneralSettingsWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include "../Application.h" #include "ui_GeneralSettingsWidget.h" #include "GeneralSettingsWidget.h" GeneralSettingsWidget::GeneralSettingsWidget( QWidget* parent ) :SettingsWidget( parent ), ui( new Ui::GeneralSettingsWidget ) { ui->setupUi( this ); populateLanguages(); ui->notifications->setChecked( unicorn::Settings().notifications() ); ui->sendCrashReports->setChecked( unicorn::Settings().sendCrashReports() ); ui->beta->setChecked( unicorn::Settings().betaUpdates() ); #if !defined( Q_OS_WIN ) && !defined( Q_OS_MAC ) ui->beta->hide(); // only have a beta update setting in mac and windows #endif #ifdef Q_OS_MAC ui->showAs->setChecked( unicorn::Settings().showAS() ); ui->showDock->setChecked( unicorn::Settings().showDock() ); #else ui->showDock->hide(); ui->showAs->setChecked( unicorn::Settings().showAS() ); #endif #ifndef Q_WS_X11 ui->launch->setChecked( unicorn::OldeAppSettings().launchWithMediaPlayers() ); ui->updates->setChecked( unicorn::Settings().checkForUpdates() ); #else ui->launch->hide(); ui->updates->hide(); #endif connect( ui->languages, SIGNAL( currentIndexChanged( int ) ), SLOT( onSettingsChanged() ) ); connect( ui->notifications, SIGNAL(stateChanged(int)), SLOT( onSettingsChanged() ) ); connect( ui->sendCrashReports, SIGNAL(stateChanged(int)), SLOT( onSettingsChanged() ) ); connect( ui->beta, SIGNAL(stateChanged(int)), SLOT( onSettingsChanged() ) ); connect( ui->showAs, SIGNAL(stateChanged(int)), SLOT( onSettingsChanged() ) ); connect( ui->showDock, SIGNAL(stateChanged(int)), SLOT( onSettingsChanged() ) ); connect( ui->updates, SIGNAL(stateChanged(int)), SLOT( onSettingsChanged() ) ); } void GeneralSettingsWidget::populateLanguages() { ui->languages->addItem( tr( "System Language" ), "" ); ui->languages->addItem( "English", QLocale( QLocale::English, QLocale::UnitedKingdom ).name() ); ui->languages->addItem( QString::fromUtf8( "français" ), QLocale( QLocale::French ).name() ); ui->languages->addItem( "Italiano", QLocale( QLocale::Italian ).name() ); ui->languages->addItem( "Deutsch", QLocale( QLocale::German ).name() ); ui->languages->addItem( QString::fromUtf8( "Español" ), QLocale( QLocale::Spanish ).name() ); ui->languages->addItem( QString::fromUtf8( "Português" ), QLocale( QLocale::Portuguese, QLocale::Brazil ).name() ); ui->languages->addItem( "Polski", QLocale( QLocale::Polish ).name() ); ui->languages->addItem( "Svenska", QLocale( QLocale::Swedish ).name()); ui->languages->addItem( QString::fromUtf8( "Türkçe" ), QLocale( QLocale::Turkish ).name() ); ui->languages->addItem( QString::fromUtf8( "Pусский" ), QLocale( QLocale::Russian ).name() ); ui->languages->addItem( QString::fromUtf8( "简体中文‡" ), QLocale( QLocale::Chinese, QLocale::China ).name() ); ui->languages->addItem( QString::fromUtf8( "日本語" ), QLocale( QLocale::Japanese ).name()); QString currLanguage = unicorn::AppSettings().value( "language", "" ).toString(); int index = ui->languages->findData( currLanguage ); if ( index != -1 ) { ui->languages->setCurrentIndex( index ); } } void GeneralSettingsWidget::saveSettings() { qDebug() << "has unsaved changes?" << hasUnsavedChanges(); if ( hasUnsavedChanges() ) { bool restartNeeded = false; int currIndex = ui->languages->currentIndex(); QString currLanguage = ui->languages->itemData( currIndex ).toString(); if ( unicorn::AppSettings().value( "language", "" ) != currLanguage ) { if ( currLanguage == "" ) QLocale::setDefault( QLocale::system() ); else QLocale::setDefault( QLocale( currLanguage ) ); unicorn::AppSettings().setValue( "language", currLanguage ); #ifdef Q_OS_MAC aApp->translate(); #endif restartNeeded = true; } // setting is for the 'Client' aplication for compatibility with old media player plugins unicorn::OldeAppSettings().setLaunchWithMediaPlayers( ui->launch->isChecked() ); unicorn::Settings().setNotifications( ui->notifications->isChecked() ); unicorn::Settings().setSendCrashReports( ui->sendCrashReports->isChecked() ); unicorn::Settings().setCheckForUpdates( ui->updates->isChecked() ); unicorn::Settings().setBetaUpdates( ui->beta->isChecked() ); aApp->setBetaUpdates( ui->beta->isChecked() ); #ifdef Q_OS_MAC /// dock hiding bool showDockOld = unicorn::Settings().showDock(); unicorn::Settings().setShowDock( ui->showDock->isChecked() ); if ( showDockOld != ui->showDock->isChecked() ) { // the setting has changed aApp->showDockIcon( ui->showDock->isChecked() ); // Hiding the dock icon while the app is running is not supported on Snow Leopard if ( QSysInfo::MacintoshVersion <= QSysInfo::MV_10_6 && !ui->showDock->isChecked() ) restartNeeded = true; } #endif unicorn::Settings().setShowAS( ui->showAs->isChecked() ); aApp->showAs( ui->showAs->isChecked() ); onSettingsSaved(); if ( restartNeeded ) { int button = QMessageBoxBuilder( this ) .setIcon( QMessageBox::Question ) .setTitle( tr( "Restart now?" ) ) .setText( tr( "An application restart is required for the change to take effect. Would you like to restart now?" ) ) .setButtons( QMessageBox::Yes | QMessageBox::No ) .exec(); if ( button == QMessageBox::Yes ) aApp->restart(); } } } ================================================ FILE: app/client/Settings/GeneralSettingsWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef GENERALSETTINGSWIDGET_H #define GENERALSETTINGSWIDGET_H #include "SettingsWidget.h" namespace Ui { class GeneralSettingsWidget; } class GeneralSettingsWidget : public SettingsWidget { Q_OBJECT public: GeneralSettingsWidget( QWidget* parent = 0 ); private: void saveSettings(); void populateLanguages(); private: Ui::GeneralSettingsWidget* ui; }; #endif // GENERALSETTINGSWIDGET_H ================================================ FILE: app/client/Settings/GeneralSettingsWidget.ui ================================================ GeneralSettingsWidget 0 0 491 311 Form false Check for updates automatically true true Launch application with media players true false Send crash reports to Last.fm Show application icon in menu bar true Update to beta versions - Warning: only for the brave! Show dock icon true true Show desktop notifications true true Language: ================================================ FILE: app/client/Settings/IpodSettingsWidget.cpp ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole, Michael Coffey, and William Viana This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "ui_IpodSettingsWidget.h" #include "IpodSettingsWidget.h" #ifdef Q_WS_X11 #include "../MediaDevices/IpodDevice_linux.h" #endif #ifdef Q_OS_WIN #include "lib/unicorn/plugins/ITunesPluginInfo.h" #endif #include "lib/unicorn/UnicornApplication.h" #include "lib/unicorn/dialogs/CloseAppsDialog.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include "../MediaDevices/IpodDevice.h" #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/widgets/Label.h" #include #include #include #include #include #include #include #include #include #include #include #include #include IpodSettingsWidget::IpodSettingsWidget( QWidget* parent ) : SettingsWidget( parent ), ui( new Ui::IpodSettingsWidget ) { ui->setupUi( this ); ui->alwaysAsk->setChecked( unicorn::AppSettings().alwaysAsk() ); connect( ui->alwaysAsk, SIGNAL(clicked(bool)), SLOT(onSettingsChanged())); #ifdef Q_WS_X11 ui->deviceScrobblingEnabled->hide(); #else ui->deviceScrobblingEnabled->setChecked( unicorn::OldeAppSettings().deviceScrobblingEnabled() ); connect( ui->deviceScrobblingEnabled, SIGNAL(clicked(bool)), SLOT(onSettingsChanged())); #endif ui->note->setText( unicorn::Label::boldLinkStyle( tr( "

    Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.

    " "

    iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.

    " ).arg( unicorn::Label::anchor( "itmss://itunes.apple.com/gb/app/scrobbler-for-ios/id585235199", "Scrobbler for iOS" ) ), Qt::black ) ); ui->note->adjustSize(); ui->noteBox->adjustSize(); } void IpodSettingsWidget::saveSettings() { if ( hasUnsavedChanges() ) { // save settings qDebug() << "Saving settings..."; unicorn::AppSettings().setAlwaysAsk( ui->alwaysAsk->isChecked() ); // we need to restart iTunes for this setting to take affect bool currentlyEnabled = unicorn::OldeAppSettings().deviceScrobblingEnabled(); #ifndef Q_WS_X11 if ( currentlyEnabled != ui->deviceScrobblingEnabled->isChecked() ) { #ifdef Q_OS_WIN QList plugins; unicorn::ITunesPluginInfo* iTunesPluginInfo = new unicorn::ITunesPluginInfo; plugins << iTunesPluginInfo; unicorn::CloseAppsDialog* closeApps = new unicorn::CloseAppsDialog( plugins, this ); closeApps->setOwnsPlugins( true ); #else unicorn::CloseAppsDialog* closeApps = new unicorn::CloseAppsDialog( this ); #endif if ( closeApps->result() != QDialog::Accepted ) closeApps->exec(); else closeApps->deleteLater(); if ( closeApps->result() == QDialog::Accepted ) { unicorn::OldeAppSettings().setDeviceScrobblingEnabled( ui->deviceScrobblingEnabled->isChecked() ); } else { ui->deviceScrobblingEnabled->setChecked( currentlyEnabled ); // The user didn't close their media players QMessageBoxBuilder( this ).setTitle( tr( "Setting not changed" ) ) .setIcon( QMessageBox::Warning ) .setText( tr( "You did not close iTunes for this setting to change" ) ) .setButtons( QMessageBox::Ok ) .exec(); } } #endif onSettingsSaved(); } } ================================================ FILE: app/client/Settings/IpodSettingsWidget.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole, Michael Coffey, and William Viana This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef IPOD_SETTINGS_H #define IPOD_SETTINGS_H #include "SettingsWidget.h" namespace Ui { class IpodSettingsWidget; } class IpodSettingsWidget: public SettingsWidget { Q_OBJECT public: IpodSettingsWidget( QWidget* parent = 0 ); public slots: virtual void saveSettings(); private: Ui::IpodSettingsWidget* ui; }; #endif ================================================ FILE: app/client/Settings/IpodSettingsWidget.ui ================================================ IpodSettingsWidget 0 0 418 365 Form Enable Device Scrobbling Confirm Device Scrobbles Please note Qt::RichText true Qt::Vertical 20 0 unicorn::Label QLabel
    lib/unicorn/widgets/Label.h
    ================================================ FILE: app/client/Settings/PreferencesDialog.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include "PreferencesDialog.h" #include "ui_PreferencesDialog.h" PreferencesDialog::PreferencesDialog( QMenuBar* menuBar, QWidget* parent ) :unicorn::MainWindow( menuBar, parent ), ui( new Ui::PreferencesDialog ) { // Disable the minimize and maximize buttons. Qt::WindowFlags flags = this->windowFlags(); flags &= ~Qt::WindowMinMaxButtonsHint; setWindowFlags(flags); ui->setupUi( this ); setAttribute( Qt::WA_DeleteOnClose, true ); static_cast( ui->toolBar->widgetForAction( ui->actionGeneral ) )->setAutoExclusive( true ); static_cast( ui->toolBar->widgetForAction( ui->actionScrobbling ) )->setAutoExclusive( true ); static_cast( ui->toolBar->widgetForAction( ui->actionDevices ) )->setAutoExclusive( true ); static_cast( ui->toolBar->widgetForAction( ui->actionAccounts ) )->setAutoExclusive( true ); static_cast( ui->toolBar->widgetForAction( ui->actionAdvanced ) )->setAutoExclusive( true ); connect( ui->toolBar->widgetForAction( ui->actionGeneral ), SIGNAL(toggled(bool)), ui->actionGeneral, SLOT(setChecked(bool)) ); connect( ui->toolBar->widgetForAction( ui->actionScrobbling ), SIGNAL(toggled(bool)), ui->actionScrobbling, SLOT(setChecked(bool)) ); connect( ui->toolBar->widgetForAction( ui->actionDevices ), SIGNAL(toggled(bool)), ui->actionDevices, SLOT(setChecked(bool)) ); connect( ui->toolBar->widgetForAction( ui->actionAccounts ), SIGNAL(toggled(bool)), ui->actionAccounts, SLOT(setChecked(bool)) ); connect( ui->toolBar->widgetForAction( ui->actionAdvanced ), SIGNAL(toggled(bool)), ui->actionAdvanced, SLOT(setChecked(bool)) ); connect( this, SIGNAL( saveNeeded() ), ui->general, SLOT( saveSettings() ) ); connect( this, SIGNAL( saveNeeded() ), ui->scrobbling, SLOT( saveSettings() ) ); connect( this, SIGNAL( saveNeeded() ), ui->ipod, SLOT( saveSettings() ) ); connect( this, SIGNAL( saveNeeded() ), ui->accounts, SLOT( saveSettings() ) ); connect( this, SIGNAL( saveNeeded() ), ui->advanced, SLOT( saveSettings() ) ); connect( ui->general, SIGNAL( settingsChanged() ), SLOT( onSettingsChanged() ) ); connect( ui->scrobbling, SIGNAL( settingsChanged() ), SLOT( onSettingsChanged() ) ); connect( ui->ipod, SIGNAL( settingsChanged() ), SLOT( onSettingsChanged() ) ); connect( ui->accounts, SIGNAL( settingsChanged() ), SLOT( onSettingsChanged() ) ); connect( ui->advanced, SIGNAL( settingsChanged() ), SLOT( onSettingsChanged() ) ); connect( ui->actionGeneral, SIGNAL(triggered()), SLOT(onTabButtonClicked())); connect( ui->actionScrobbling, SIGNAL(triggered()), SLOT(onTabButtonClicked())); connect( ui->actionDevices, SIGNAL(triggered()), SLOT(onTabButtonClicked())); connect( ui->actionAccounts, SIGNAL(triggered()), SLOT(onTabButtonClicked())); connect( ui->actionAdvanced, SIGNAL(triggered()), SLOT(onTabButtonClicked())); connect( ui->stackedWidget, SIGNAL(currentChanged(int)), this, SLOT(onStackCurrentChanged(int)), Qt::QueuedConnection ); #ifdef Q_OS_MAC ui->buttonBox->hide(); #endif connect( ui->buttonBox, SIGNAL( accepted() ), SLOT( onAccepted() ) ); connect( ui->buttonBox, SIGNAL( rejected() ), SLOT( onRejected() ) ); QAbstractButton* applyButton = ui->buttonBox->button( QDialogButtonBox::Apply ); applyButton->setEnabled( false ); connect( applyButton, SIGNAL( clicked() ), SLOT( onApplyButtonClicked() ) ); setFixedWidth( 550 ); ui->stackedWidget->setCurrentWidget( ui->accounts ); ui->actionGeneral->trigger(); } PreferencesDialog::~PreferencesDialog() { delete ui; } void PreferencesDialog::onTabButtonClicked() { QAction* clickedButton = qobject_cast(sender()); setWindowTitle( clickedButton->text() ); if ( clickedButton == ui->actionGeneral ) ui->stackedWidget->setCurrentWidget( ui->general ); else if ( clickedButton == ui->actionAccounts ) ui->stackedWidget->setCurrentWidget( ui->accounts ); else if ( clickedButton == ui->actionDevices ) ui->stackedWidget->setCurrentWidget( ui->ipod ); else if ( clickedButton == ui->actionAdvanced ) ui->stackedWidget->setCurrentWidget( ui->advanced ); else if ( clickedButton == ui->actionScrobbling ) ui->stackedWidget->setCurrentWidget( ui->scrobbling ); } void PreferencesDialog::onStackCurrentChanged( int /*index*/ ) { adjustSize(); } void PreferencesDialog::onAccepted() { emit saveNeeded(); close(); } void PreferencesDialog::onRejected() { close(); } void PreferencesDialog::onSettingsChanged() { ui->buttonBox->button( QDialogButtonBox::Apply )->setEnabled( true ); } void PreferencesDialog::onApplyButtonClicked() { emit saveNeeded(); ui->buttonBox->button( QDialogButtonBox::Apply )->setEnabled( false ); } ================================================ FILE: app/client/Settings/PreferencesDialog.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PREFERENCESDIALOG_H #define PREFERENCESDIALOG_H #include #include "lib/unicorn/dialogs/UnicornDialog.h" namespace Ui { class PreferencesDialog; } class PreferencesDialog : public unicorn::MainWindow { Q_OBJECT public: explicit PreferencesDialog( QMenuBar* menuBar, QWidget* parent = 0 ); ~PreferencesDialog(); signals: void saveNeeded(); private slots: void onTabButtonClicked(); void onAccepted(); void onRejected(); void onSettingsChanged(); void onApplyButtonClicked(); void onStackCurrentChanged( int index ); private: Ui::PreferencesDialog *ui; }; #endif // PREFERENCESDIALOG_H ================================================ FILE: app/client/Settings/PreferencesDialog.ui ================================================ PreferencesDialog Qt::NonModal 0 0 583 466 0 0 Qt::StrongFocus MainWindow true 0 0 QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok 0 0 toolBar false Qt::TopToolBarArea Qt::ToolButtonTextUnderIcon false TopToolBarArea false true true :/preferences_general.png:/preferences_general.png General true :/preferences_accounts.png:/preferences_accounts.png Accounts true :/preferences_scrobbling.png:/preferences_scrobbling.png Scrobbling true :/preferences_devices.png:/preferences_devices.png Devices true :/preferences_advanced.png:/preferences_advanced.png Advanced unicorn::StackedWidget QStackedWidget
    lib/unicorn/widgets/StackedWidget.h
    1
    AccountSettingsWidget QWidget
    ../Settings/AccountSettingsWidget.h
    1
    ScrobbleSettingsWidget QWidget
    ../Settings/ScrobbleSettingsWidget.h
    1
    IpodSettingsWidget QWidget
    ../Settings/IpodSettingsWidget.h
    1
    GeneralSettingsWidget QWidget
    ../Settings/GeneralSettingsWidget.h
    1
    AdvancedSettingsWidget QWidget
    ../Settings/AdvancedSettingsWidget.h
    1
    ================================================ FILE: app/client/Settings/ScrobbleSettingsWidget.cpp ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole, Michael Coffey, and William Viana This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include #include "lib/unicorn/UnicornSettings.h" #include "../Application.h" #include "../Services/ScrobbleService/ScrobbleService.h" #include "../Services/AnalyticsService.h" #include "ui_ScrobbleSettingsWidget.h" #include "ScrobbleSettingsWidget.h" ScrobbleSettingsWidget::ScrobbleSettingsWidget( QWidget* parent ) : SettingsWidget( parent ), ui( new Ui::ScrobbleSettingsWidget ) { ui->setupUi( this ); unicorn::UserSettings userSettings; double scrobblePointValue = userSettings.scrobblePoint(); ui->scrobblePoint->setValue( scrobblePointValue ); ui->percentText->setText( QString::number(scrobblePointValue) ); ui->percentText->setFixedWidth( ui->percentText->fontMetrics().width( "100" ) ); m_initialScrobblePercentage = scrobblePointValue; ui->allowFingerprint->setChecked( userSettings.fingerprinting() ); ui->enfocreScrobbleTimeMax->setChecked( userSettings.enforceScrobbleTimeMax() ); ui->scrobblingOn->setChecked( userSettings.scrobblingOn() ); ui->podcasts->setChecked( userSettings.podcasts() ); QStringList exclusionDirs = userSettings.exclusionDirs(); exclusionDirs.removeAll( "" ); ui->exclusionDirs->setExclusions( exclusionDirs ); connect( ui->scrobblePoint, SIGNAL(sliderMoved(int)), SLOT(onSliderMoved(int)) ); connect( ui->scrobblePoint, SIGNAL(valueChanged(int)), SLOT(onSliderMoved(int)) ); connect( ui->scrobblePoint, SIGNAL(valueChanged(int)), SLOT(onSettingsChanged()) ); connect( ui->allowFingerprint, SIGNAL(stateChanged(int)), SLOT(onSettingsChanged()) ); connect( aApp, SIGNAL(scrobbleToggled(bool)), ui->scrobblingOn, SLOT(setChecked(bool))); connect( ui->scrobblingOn, SIGNAL(clicked(bool)), SLOT(onSettingsChanged()) ); connect( ui->podcasts, SIGNAL(stateChanged(int)), SLOT(onSettingsChanged()) ); connect( ui->enfocreScrobbleTimeMax, SIGNAL(stateChanged(int)), SLOT(onSettingsChanged()) ); connect( ui->exclusionDirs, SIGNAL(dataChanged()), SLOT(onSettingsChanged()) ); } ScrobbleSettingsWidget::~ScrobbleSettingsWidget() { if ( unicorn::UserSettings().scrobblePoint() != m_initialScrobblePercentage ) AnalyticsService::instance().sendEvent(SETTINGS_CATEGORY, SCROBBLING_SETTINGS, "ScrobblePercentageChanged", QString::number( ui->scrobblePoint->value() ) ); } void ScrobbleSettingsWidget::onSliderMoved( int value ) { ui->percentText->setText( QString::number( value ) ); } void ScrobbleSettingsWidget::saveSettings() { if ( hasUnsavedChanges() ) { qDebug() << "Saving settings..."; aApp->onScrobbleToggled( ui->scrobblingOn->isChecked() ); unicorn::UserSettings userSettings; userSettings.setScrobblePoint( ui->scrobblePoint->value() ); userSettings.setFingerprinting( ui->allowFingerprint->isChecked() ); userSettings.setPodcasts( ui->podcasts->isChecked() ); userSettings.setEnforceScrobbleTimeMax( ui->enfocreScrobbleTimeMax->isChecked() ); QStringList exclusionDirs = ui->exclusionDirs->getExclusions(); exclusionDirs.removeAll( "" ); qDebug() << exclusionDirs; userSettings.setExclusionDirs( exclusionDirs ); userSettings.sync(); ScrobbleService::instance().scrobbleSettingsChanged(); onSettingsSaved(); } } ================================================ FILE: app/client/Settings/ScrobbleSettingsWidget.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole, Michael Coffey, and William Viana This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SCROBBLE_SETTINGS_WIDGET_H #define SCROBBLE_SETTINGS_WIDGET_H #include "SettingsWidget.h" namespace Ui { class ScrobbleSettingsWidget; } class ScrobbleSettingsWidget: public SettingsWidget { Q_OBJECT public: ScrobbleSettingsWidget( QWidget* parent = 0 ); ~ScrobbleSettingsWidget(); public slots: virtual void saveSettings(); private slots: void onSliderMoved( int value ); private: Ui::ScrobbleSettingsWidget* ui; double m_initialScrobblePercentage; }; #endif ================================================ FILE: app/client/Settings/ScrobbleSettingsWidget.ui ================================================ ScrobbleSettingsWidget 0 0 400 361 Form 10 Enable scrobbling true Scrobble at 50 100 1 false Qt::Horizontal QSlider::TicksBelow 10 50 percent of the track Qt::Horizontal 40 20 ...or at 4 minutes (whichever comes first) true true Scrobble podcasts true true Allow Last.fm to fingerprint my tracks true Selected directories will not be scrobbled 10 12 true CheckFileSystemView QTreeView
    ../Settings/CheckFileSystemView.h
    ================================================ FILE: app/client/Settings/SettingsWidget.cpp ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole, Michael Coffey, and William Viana This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "SettingsWidget.h" SettingsWidget::SettingsWidget( QWidget* parent ) : QWidget( parent ) , m_settingsChanged( false ) { } void SettingsWidget::onSettingsChanged() { m_settingsChanged = true; #ifdef Q_OS_MAC saveSettings(); #else emit settingsChanged(); #endif } void SettingsWidget::onSettingsSaved() { m_settingsChanged = false; } bool SettingsWidget::hasUnsavedChanges() { return m_settingsChanged; } ================================================ FILE: app/client/Settings/SettingsWidget.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole, Michael Coffey, and William Viana This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SETTINGS_WIDGET_H #define SETTINGS_WIDGET_H #include class SettingsWidget : public QWidget { Q_OBJECT public: SettingsWidget( QWidget* parent = 0 ); bool hasUnsavedChanges(); public slots: virtual void saveSettings() = 0; signals: void settingsChanged(); void settingsSaved(); protected slots: void onSettingsChanged(); void onSettingsSaved(); private: bool m_settingsChanged; }; #endif ================================================ FILE: app/client/Widgets/BioWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include "lib/unicorn/widgets/BannerWidget.h" #include "lib/unicorn/widgets/HttpImageWidget.h" #include "lib/unicorn/DesktopServices.h" #include "../Services/AnalyticsService.h" #include "../Application.h" #include "WidgetTextObject.h" #include "BioWidget.h" BioWidget::BioWidget( QWidget* p ) :QTextBrowser( p ), m_currentHoverWidget(0) { setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); connect(document()->documentLayout(), SIGNAL( documentSizeChanged(QSizeF)), SLOT( onBioChanged(QSizeF))); connect(this, SIGNAL(anchorClicked(QUrl)), SLOT(onAnchorClicked(QUrl))); m_widgetTextObject = new WidgetTextObject; viewport()->installEventFilter( this ); document()->documentLayout()->registerHandler( WidgetImageFormat, m_widgetTextObject ); ui.image = new HttpImageWidget(this); ui.image->setFixedWidth( 160 ); ui.image->setAlignment( Qt::AlignTop ); ui.onTour = new BannerWidget( tr("On Tour" ) ); ui.onTour->setBannerVisible( false ); ui.onTour->setWidget( ui.image ); ui.onTour->setFixedWidth( 170 ); ui.onTour->setObjectName( "onTour" ); connect( ui.image, SIGNAL(loaded()), SLOT(onImageLoaded())); connect( ui.image, SIGNAL(loaded()), SLOT(update())); connect( this, SIGNAL(highlighted(QString)), SLOT(onHighlighted(QString)) ); } BioWidget::~BioWidget() { delete m_widgetTextObject; } void BioWidget::setBioText( const QString& bioText ) { m_bioText = bioText; } void BioWidget::onHighlighted( const QString& url ) { QUrl displayUrl( url ); QToolTip::showText( cursor().pos(), displayUrl.toString(), this, QRect() ); } void BioWidget::onImageLoaded() { insertWidget( ui.onTour ); append( m_bioText ); updateGeometry(); emit finished(); } void BioWidget::insertWidget( QWidget* w ) { w->installEventFilter( this ); m_widgetImageFormat.setObjectType( WidgetImageFormat ); m_widgetImageFormat.setProperty( WidgetData, QVariant::fromValue( w ) ); m_widgetImageFormat.setName( w->objectName() ); QTextCursor cursor = textCursor(); cursor.insertImage( m_widgetImageFormat, QTextFrameFormat::FloatLeft ); setTextCursor( cursor ); } bool BioWidget::eventFilter( QObject* o, QEvent* e ) { QWidget* w = qobject_cast( o ); if ( viewport() == w ) { if ( QEvent::MouseMove != e->type() ) return false; QMouseEvent* event = static_cast(e); //respect child widget cursor QWidget* w = m_widgetTextObject->widgetAtPoint(event->pos() ); if ( w != m_currentHoverWidget ) { m_currentHoverWidget = w; if( 0 == w ) viewport()->unsetCursor(); else { QWidget* c = w->childAt(event->pos()); c = c ? c : w; viewport()->setCursor( c->cursor()); } } return false; } return false; } void BioWidget::mousePressEvent( QMouseEvent* event ) { update(); if ( !sendMouseEvent(event) ) QTextBrowser::mousePressEvent( event ); } void BioWidget::mouseReleaseEvent( QMouseEvent* event ) { if ( !sendMouseEvent(event) ) QTextBrowser::mouseReleaseEvent( event ); } void BioWidget::mouseMoveEvent( QMouseEvent* event ) { if (!sendMouseEvent(event)) QTextBrowser::mouseMoveEvent( event ); } bool BioWidget::sendMouseEvent( QMouseEvent* event ) { QWidget* w = m_widgetTextObject->widgetAtPoint( event->pos()); if ( !w ) return false; QRectF wRect = m_widgetTextObject->widgetRect( w ); QPoint pos = event->pos() - wRect.toRect().topLeft(); QWidget* childWidget = w->childAt( event->pos()); if( !childWidget ) childWidget = w; else pos = childWidget->mapTo( w, pos ); QMouseEvent* widgetMouseEvent = new QMouseEvent( event->type(), pos, event->button(), event->buttons(), event->modifiers()); QCoreApplication::postEvent( childWidget, widgetMouseEvent ); event->accept(); return true; } void BioWidget::showEvent( QShowEvent* /*event*/ ) { // HACK: onBioChanged reports the wrong size for the document // and id we polish it a bit later it gets set correctly. // Polish very soon after being shown so that it gets // the right height quickly if you're looking at it. // Do another polish a bit later as for the case when you // switch to that tab as the first one won't work. QTimer::singleShot( 1, this, SLOT(polish())); QTimer::singleShot( 200, this, SLOT(polish())); } void BioWidget::onAnchorClicked( const QUrl& link ) { unicorn::DesktopServices::openUrl( link ); AnalyticsService::instance().sendEvent( aApp->currentCategory(), LINK_CLICKED, "BioPageLinkClicked"); } void BioWidget::onDocumentLayoutChanged() { setFixedHeight( document()->size().height() ); } void BioWidget::onBioChanged( const QSizeF& /*size*/ ) { updateGeometry(); onDocumentLayoutChanged(); } void BioWidget::polish() { if ( isVisible() ) style()->polish( this ); else QTimer::singleShot( 20, this, SLOT(polish())); } void BioWidget::setPixmap( const QPixmap& pixmap ) { ui.image->setPixmap( pixmap ); } void BioWidget::loadImage( const QUrl& url, HttpImageWidget::ScaleType scale ) { ui.image->loadUrl( url, scale ); } void BioWidget::setImageHref( const QUrl& href ) { ui.image->setHref( href ); } void BioWidget::setOnTourVisible( bool visible, const QUrl& url ) { ui.onTour->setBannerVisible( visible ); if ( url.isValid()) ui.onTour->setHref( url ); update(); } ================================================ FILE: app/client/Widgets/BioWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef BIO_WIDGET_H_ #define BIO_WIDGET_H_ #include #include #include #include "lib/unicorn/widgets/HttpImageWidget.h" /** A specialized QTextBrowser which can insert widgets inline */ #include class BioWidget : public QTextBrowser { Q_OBJECT public: BioWidget( QWidget* parent ); ~BioWidget(); bool eventFilter( QObject* o, QEvent* e ); void setBioText( const QString& bioText ); void setPixmap( const QPixmap& pixmap ); void loadImage( const QUrl&, HttpImageWidget::ScaleType scale = HttpImageWidget::ScaleAuto ); void setImageHref( const QUrl& ); void setOnTourVisible( bool, const QUrl& = QUrl()); signals: void finished(); protected slots: void onBioChanged( const QSizeF& size ); void onAnchorClicked( const QUrl& link ); void onHighlighted( const QString& url ); void onImageLoaded(); void onDocumentLayoutChanged(); void polish(); protected: void insertWidget( QWidget* w ); class WidgetTextObject* m_widgetTextObject; void mousePressEvent( QMouseEvent* event ); void mouseReleaseEvent( QMouseEvent* event ); void mouseMoveEvent( QMouseEvent* event ); void showEvent(QShowEvent *); bool sendMouseEvent( QMouseEvent* event ); enum WidgetProperties { WidgetData = 1 }; enum { WidgetImageFormat = QTextFormat::UserObject + 1 }; QWidget* m_currentHoverWidget; QString m_bioText; QTextImageFormat m_widgetImageFormat; struct { class BannerWidget* onTour; class HttpImageWidget* image; } ui; }; #endif //BIO_WIDGET_H_ ================================================ FILE: app/client/Widgets/ContextLabel.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "ContextLabel.h" ContextLabel::ContextLabel( QWidget* parent ) :unicorn::Label( parent ) { } void ContextLabel::paintEvent( QPaintEvent* event ) { QLabel::paintEvent( event ); // draw the arrow on the context QPainter p; p.begin( this ); static QPixmap arrow( ":/meta_context_arrow.png" ); // these values match the ones in the stylesheet int topMargin = 20; int leftMargin = 20; if ( this->objectName() == "userBlurb" ) { topMargin = 12; leftMargin = 0; } QPoint arrowPoint = QPoint( leftMargin + (( 126 + 10 - arrow.size().width() ) / 2 ), topMargin + 1 - arrow.size().height() ); p.drawPixmap( arrowPoint, arrow ); p.end(); } ================================================ FILE: app/client/Widgets/ContextLabel.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef CONTEXTLABEL_H #define CONTEXTLABEL_H #include "lib/unicorn/widgets/Label.h" class ContextLabel : public unicorn::Label { Q_OBJECT public: explicit ContextLabel( QWidget *parent = 0); private: void paintEvent( QPaintEvent* event ); }; #endif // CONTEXTLABEL_H ================================================ FILE: app/client/Widgets/FriendListWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/unicorn/UnicornSession.h" #include "lib/unicorn/DesktopServices.h" #include "../Application.h" #include "FriendWidget.h" #include "FriendListWidget.h" #include "RefreshButton.h" #include "ui_FriendListWidget.h" class FriendWidgetItem : public QListWidgetItem { public: FriendWidgetItem( QListWidget* parent ); bool operator<( const QListWidgetItem& that ) const; }; FriendWidgetItem::FriendWidgetItem( QListWidget* parent ) :QListWidgetItem( parent ) { } bool FriendWidgetItem::operator<( const QListWidgetItem& that ) const { if ( !qobject_cast(listWidget()->itemWidget( const_cast(&that) )) ) return false; if ( !qobject_cast(listWidget()->itemWidget( const_cast(this) )) ) return true; return *qobject_cast(listWidget()->itemWidget( const_cast(this) )) < *qobject_cast(listWidget()->itemWidget( const_cast(&that) )); } FriendListWidget::FriendListWidget(QWidget *parent) : QWidget(parent), ui( new Ui::FriendListWidget ) { ui->setupUi( this ); #ifdef Q_OS_MAC connect( ui->friends->verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(scroll()) ); #endif ui->noFriends->setText( tr( "

    You haven't made any friends on Last.fm yet.

    " "

    Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.

    " ) ); connect( ui->findFriends, SIGNAL(clicked()), SLOT(onFindFriends()) ); #if QT_VERSION >= 0x040700 // The placeholder property was introduced in Qt 4.7 ui->filter->setPlaceholderText( tr( "Search for a friend by username or real name" ) ); #endif ui->filter->setAttribute( Qt::WA_MacShowFocusRect, false ); ui->friends->setObjectName( "friends" ); ui->friends->setAttribute( Qt::WA_MacShowFocusRect, false ); connect( ui->filter, SIGNAL(textChanged(QString)), SLOT(onTextChanged(QString))); connect( aApp, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onSessionChanged(unicorn::Session)) ); m_movie = new QMovie( ":/loading_meta.gif", "GIF", this ); m_movie->setCacheMode( QMovie::CacheAll ); ui->spinner->setMovie( m_movie ); ui->stackedWidget->setCurrentWidget( ui->spinnerPage ); m_movie->start(); onSessionChanged( aApp->currentSession() ); } #ifdef Q_OS_MAC void FriendListWidget::scroll() { // KLUDGE: The friend list widgets don't move unless we do this ui->friends->sortItems( Qt::AscendingOrder ); } #endif void FriendListWidget::onSessionChanged( const unicorn::Session& session ) { if ( session.user().name() != m_currentUser ) { m_currentUser = session.user().name(); if ( m_reply ) m_reply->abort(); ui->friends->clear(); // add the refresh button FriendWidgetItem* item = new FriendWidgetItem( ui->friends ); RefreshButton* refresh = new RefreshButton( this ); refresh->setText( tr( "Refresh Friends" ) ); ui->friends->setItemWidget( item, refresh ); item->setSizeHint( refresh->sizeHint() ); connect( refresh, SIGNAL(clicked()) , SLOT(refresh())); ui->stackedWidget->setCurrentWidget( ui->spinnerPage ); m_movie->start(); m_reply = session.user().getFriends( true, 50, 1 ); connect( m_reply, SIGNAL(finished()), SLOT(onGotFriends())); } } void FriendListWidget::onFindFriends() { unicorn::DesktopServices::openUrl( lastfm::UrlBuilder( "findfriends" ).url() ); } void FriendListWidget::onTextChanged( const QString& text ) { QString trimmedText = text.trimmed(); setUpdatesEnabled( false ); if ( text.isEmpty() ) { // special case an empty string so it's a bit zippier for ( int i = 1 ; i < ui->friends->count() ; ++i ) ui->friends->item( i )->setHidden( false ); } else { QRegExp re( QString( "^%1" ).arg( trimmedText ), Qt::CaseInsensitive ); for ( int i = 1 ; i < ui->friends->count() ; ++i ) { FriendWidget* user = static_cast( ui->friends->itemWidget( ui->friends->item( i ) ) ); ui->friends->item( i )->setHidden( !( user->name().startsWith( trimmedText, Qt::CaseInsensitive ) || user->realname().startsWith( trimmedText, Qt::CaseInsensitive ) || user->realname().split( ' ' ).filter( re ).count() > 0 ) ); } } setUpdatesEnabled( true ); } void FriendListWidget::onCurrentChanged( int index ) { if ( index == 3 ) refresh(); } void FriendListWidget::refresh() { if ( !m_reply || ( m_reply && m_reply->isFinished() ) ) { RefreshButton* refresh = qobject_cast(ui->friends->itemWidget( ui->friends->item( 0 ) ) ); refresh->setEnabled( false ); refresh->setText( tr( "Refreshing..." ) ); m_reply = User().getFriendsListeningNow( 50, 1 ); connect( m_reply, SIGNAL(finished()), SLOT(onGotFriendsListeningNow())); } } void FriendListWidget::onGotFriends() { // add this set of users to the list lastfm::XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { foreach( const lastfm::XmlQuery& user, lfm["friends"].children( "user" ) ) { FriendWidgetItem* item = new FriendWidgetItem( ui->friends ); FriendWidget* friendWidget = new FriendWidget( user, this ); ui->friends->setItemWidget( item, friendWidget ); item->setSizeHint( friendWidget->sizeHint() ); } int page = lfm["friends"].attribute( "page" ).toInt(); int perPage = lfm["friends"].attribute( "perPage" ).toInt(); int totalPages = lfm["friends"].attribute( "totalPages" ).toInt(); //int total = lfm["friends"].attribute( "total" ).toInt(); // Check if we need to fetch another page of users if ( page != totalPages ) { m_reply = lastfm::User().getFriends( true, perPage, page + 1 ); connect( m_reply, SIGNAL(finished()), SLOT(onGotFriends()) ); } else { // we have fetched all the pages! onTextChanged( ui->filter->text() ); m_reply = User().getFriendsListeningNow( 50, 1 ); connect( m_reply, SIGNAL(finished()), SLOT(onGotFriendsListeningNow())); } } else { showList(); } } void FriendListWidget::onGotFriendsListeningNow() { // update the users in the list lastfm::XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { // reset all the friends to have the same order of max unsigned int for ( int i = 1 ; i < ui->friends->count() ; ++i ) static_cast( ui->friends->itemWidget( ui->friends->item( i ) ) )->setOrder( 0 - 1 ); QList users = lfm["friendslisteningnow"].children( "user" ); for ( int i = 0 ; i < users.count() ; ++i ) { XmlQuery& user = users[i]; for ( int j = 1 ; j < ui->friends->count() ; ++j ) { FriendWidget* friendWidget = static_cast( ui->friends->itemWidget( ui->friends->item( j ) ) ); if ( friendWidget->name().compare( user["name"].text(),Qt::CaseInsensitive ) == 0 ) friendWidget->update( user, i ); } } int page = lfm["friends"].attribute( "page" ).toInt(); int perPage = lfm["friends"].attribute( "perPage" ).toInt(); int totalPages = lfm["friends"].attribute( "totalPages" ).toInt(); //int total = lfm["friends"].attribute( "total" ).toInt(); // Check if we need to fetch another page of users if ( page != totalPages ) { m_reply = lastfm::User().getFriends( true, perPage, page + 1 ); connect( m_reply, SIGNAL(finished()), SLOT(onGotFriends()) ); } else { // we have fetched all the pages! showList(); } } else { // there was an error downloading a page showList(); } } void FriendListWidget::showList() { onTextChanged( ui->filter->text() ); ui->friends->sortItems( Qt::AscendingOrder ); if ( ui->friends->count() == 1 ) ui->stackedWidget->setCurrentWidget( ui->noFriendsPage ); else ui->stackedWidget->setCurrentWidget( ui->friendsPage ); m_movie->stop(); RefreshButton* refresh = qobject_cast(ui->friends->itemWidget( ui->friends->item( 0 ) ) ); refresh->setEnabled( true ); refresh->setText( tr( "Refresh Friends" ) ); } ================================================ FILE: app/client/Widgets/FriendListWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef FRIENDLISTWIDGET_H #define FRIENDLISTWIDGET_H #include #include class QNetworkReply; namespace unicorn { class Session; } namespace lastfm { class XmlQuery; } namespace lastfm { class User; } namespace Ui { class FriendListWidget; } class FriendListWidget : public QWidget { Q_OBJECT public: explicit FriendListWidget( QWidget *parent = 0 ); public slots: void onCurrentChanged(int); private slots: void onSessionChanged( const unicorn::Session& session ); void onGotFriends(); void onGotFriendsListeningNow(); void onTextChanged( const QString& text ); void onFindFriends(); void refresh(); #ifdef Q_OS_MAC void scroll(); #endif private: void showList(); private: QString m_currentUser; Ui::FriendListWidget* ui; QPointer m_movie; QPointer m_reply; }; #endif // FRIENDLISTWIDGET_H ================================================ FILE: app/client/Widgets/FriendListWidget.ui ================================================ FriendListWidget 0 0 446 595 Form 0 0 1 0 0 0 0 Qt::ScrollBarAlwaysOff QAbstractItemView::NoSelection QAbstractItemView::ScrollPerPixel false false 0 Qt::RichText true Find your friends on Last.fm Qt::Vertical 20 0 0 0 Spinner Qt::AlignCenter unicorn::Label QLabel
    lib/unicorn/widgets/Label.h
    PushButton QPushButton
    ../Widgets/PushButton.h
    ================================================ FILE: app/client/Widgets/FriendWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include "lib/unicorn/widgets/AvatarWidget.h" #include "lib/unicorn/widgets/Label.h" #include "../Application.h" #include "FriendWidget.h" #include "ui_FriendWidget.h" FriendWidget::FriendWidget( const lastfm::XmlQuery& user, QWidget* parent) :QFrame( parent ), ui( new Ui::FriendWidget ), m_user( user ), m_order( 0 - 1 ), m_listeningNow( false ) { ui->setupUi( this ); layout()->setAlignment( ui->avatar, Qt::AlignTop ); m_movie = new QMovie( ":/icon_eq.gif", "GIF", this ); m_movie->setCacheMode( QMovie::CacheAll ); ui->equaliser->setMovie( m_movie ); ui->equaliser->hide(); update( user, -1 ); QRegExp re( "/serve/(\\d*)s?/" ); ui->avatar->loadUrl( user["image size=medium"].text().replace( re, "/serve/\\1s/" ), HttpImageWidget::ScaleNone ); ui->avatar->setHref( user["url"].text() ); ui->avatar->setUser( m_user ); } void FriendWidget::update( const lastfm::XmlQuery& user, unsigned int order ) { m_order = order; m_track.setTitle( user["recenttrack"]["name"].text() ); m_track.setAlbum( user["recenttrack"]["album"]["name"].text() ); m_track.setArtist( user["recenttrack"]["artist"]["name"].text() ); m_track.setExtra( "playerName", user["scrobblesource"]["name"].text() ); m_track.setExtra( "playerURL", user["scrobblesource"]["url"].text() ); QString recentTrackDate = user["recenttrack"].attribute( "uts" ); bool hasListened = m_track != lastfm::Track(); ui->trackFrame->setVisible( hasListened ); m_listeningNow = recentTrackDate.isEmpty() && hasListened; if ( !recentTrackDate.isEmpty() ) m_track.setTimeStamp( QDateTime::fromTime_t( recentTrackDate.toUInt() ) ); setDetails(); } void FriendWidget::setOrder( int order ) { m_order = order; } void FriendWidget::setListeningNow( bool listeningNow ) { m_listeningNow = listeningNow; } bool FriendWidget::operator<( const FriendWidget& that ) const { // sort by most recently listened and then by name if ( this->m_listeningNow && !that.m_listeningNow ) return true; if ( !this->m_listeningNow && that.m_listeningNow ) return false; if ( this->m_listeningNow && that.m_listeningNow ) return this->name().toLower() < that.name().toLower(); if ( !this->m_track.timestamp().isNull() && that.m_track.timestamp().isNull() ) return true; if ( this->m_track.timestamp().isNull() && !that.m_track.timestamp().isNull() ) return false; if ( this->m_track.timestamp().isNull() && that.m_track.timestamp().isNull() ) return this->name().toLower() < that.name().toLower(); // both timestamps are valid! if ( this->m_track.timestamp() == that.m_track.timestamp() ) { if ( this->m_order == that.m_order ) return this->name().toLower() < that.name().toLower(); return this->m_order < that.m_order; } // this is the other way around because a higher time means it's lower in the list return this->m_track.timestamp() > that.m_track.timestamp(); } QString FriendWidget::genderString( const lastfm::Gender& gender ) { QString result; if ( gender.male() ) result = tr( "Male" ); else if ( gender.female() ) result = tr( "Female" ); else result = tr( "Neuter" ); return result; } QString FriendWidget::userString( const lastfm::User& user ) { QString text; text = QString("%1").arg( user.realName().isEmpty() ? user.name() : user.realName() ); if ( user.age() ) text.append( QString(", %1").arg( user.age() ) ); if ( user.gender().known() ) text.append( QString(", %1").arg( genderString( user.gender() ) ) ); if ( !user.country().isEmpty() ) text.append( QString(", %1").arg( user.country() ) ); return text; } void FriendWidget::setDetails() { ui->userDetails->setText( userString( m_user ) ); ui->username->setText( Label::boldLinkStyle( Label::anchor( m_user.www().toString(), name() ), Qt::black ) ); ui->lastTrack->setText( m_track.toString() ); if ( m_listeningNow ) { // show the m_movie->start(); ui->equaliser->show(); ui->trackFrame->setObjectName( "nowListening" ); style()->polish( ui->trackFrame ); if ( !m_track.extra( "playerName" ).isEmpty() ) ui->timestamp->setText( tr( "Scrobbling now from %1" ).arg( m_track.extra( "playerName" ) ) ); else ui->timestamp->setText( tr( "Scrobbling now" ) ); if ( m_timestampTimer ) m_timestampTimer->stop(); } else { m_movie->stop(); ui->equaliser->hide(); ui->trackFrame->setObjectName( "groupBox" ); style()->polish( ui->trackFrame ); updateTimestamp(); } } void FriendWidget::updateTimestamp() { if ( !m_timestampTimer ) { m_timestampTimer = new QTimer( this ); m_timestampTimer->setSingleShot( true ); connect( m_timestampTimer, SIGNAL(timeout()), SLOT(updateTimestamp())); } unicorn::Label::prettyTime( *ui->timestamp, m_track.timestamp(), m_timestampTimer ); } QString FriendWidget::name() const { return m_user.name(); } QString FriendWidget::realname() const { return m_user.realName(); } ================================================ FILE: app/client/Widgets/FriendWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef FRIENDWIDGET_H #define FRIENDWIDGET_H #include #include #include #include #include namespace Ui { class FriendWidget; } namespace unicorn { class Label; } using unicorn::Label; class FriendWidget : public QFrame { Q_OBJECT public: explicit FriendWidget( const lastfm::XmlQuery& user, QWidget *parent = 0 ); void update( const lastfm::XmlQuery& user, unsigned int order ); void setOrder( int order ); QString name() const; QString realname() const; void setListeningNow( bool listeningNow ); bool operator<( const FriendWidget& that ) const; static QString genderString( const lastfm::Gender& gender ); static QString userString( const lastfm::User& user ); private: void setDetails(); private slots: void updateTimestamp(); private: Ui::FriendWidget* ui; lastfm::User m_user; lastfm::MutableTrack m_track; unsigned int m_order; bool m_listeningNow; QPointer m_movie; QPointer m_timestampTimer; }; #endif // FRIENDWIDGET_H ================================================ FILE: app/client/Widgets/FriendWidget.ui ================================================ FriendWidget 0 0 423 130 Form 0 0 3 0 0 Username Qt::RichText true User details Qt::PlainText true Qt::Vertical 20 6 4 0 Artist - Title Qt::PlainText true 0 e Timestamp Qt::PlainText true unicorn::Label QLabel
    lib/unicorn/widgets/Label.h
    AvatarWidget QLabel
    lib/unicorn/widgets/AvatarWidget.h
    ================================================ FILE: app/client/Widgets/MetadataWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "MetadataWidget.h" #include "ui_MetadataWidget.h" #include "../Application.h" #include "../Services/ScrobbleService.h" #include "../Services/AnalyticsService.h" #include "ScrobbleControls.h" #include "BioWidget.h" #include "TagWidget.h" #include "lib/unicorn/widgets/HttpImageWidget.h" #include "lib/unicorn/widgets/DataBox.h" #include "lib/unicorn/widgets/DataListWidget.h" #include "lib/unicorn/widgets/BannerWidget.h" #include "lib/unicorn/widgets/LfmListViewWidget.h" #include "lib/unicorn/widgets/Label.h" #include "lib/unicorn/layouts/FlowLayout.h" #include "lib/unicorn/DesktopServices.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using unicorn::Label; MetadataWidget::MetadataWidget( const Track& track, QWidget* p ) :QFrame( p ), ui( new Ui::MetadataWidget ), m_track( track ), m_globalTrackScrobbles( 0 ), m_userTrackScrobbles( 0 ), m_userArtistScrobbles( 0 ), m_fetchedTrackInfo( false ) { ui->setupUi( this ); ui->artistBioEdit->hide(); m_movie = new QMovie( ":/loading_meta.gif", "GIF", this ); m_movie->setCacheMode( QMovie::CacheAll ); ui->spinnerLabel->setMovie (m_movie ); ui->loadingStack->setCurrentWidget( ui->spinner ); ui->scrollArea->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->back->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->trackTagsFrame->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->artistPlays->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->artistPlaysLabel->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->artistUserPlays->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->artistUserPlaysLabel->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->artistListeners->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->artistListenersLabel->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->trackYourTags->setLinkColor( QRgb( 0x008AC7 ) ); ui->trackPopTags->setLinkColor( QRgb( 0x008AC7 ) ); ui->artistYourTags->setLinkColor( QRgb( 0x008AC7 ) ); ui->artistPopTags->setLinkColor( QRgb( 0x008AC7 ) ); ui->artist1->setPixmap( QPixmap( ":/meta_artist_no_photo.png" ) ); ui->artist2->setPixmap( QPixmap( ":/meta_artist_no_photo.png" ) ); ui->artist3->setPixmap( QPixmap( ":/meta_artist_no_photo.png" ) ); ui->artist4->setPixmap( QPixmap( ":/meta_artist_no_photo.png" ) ); ui->scrobbleControls->setTrack( track ); setTrackDetails( track ); ui->albumImage->setPixmap( QPixmap( ":/meta_album_no_art.png" ) ); ui->artistBio->setPixmap( QPixmap( ":/meta_artist_no_photo.png" ) ); connect( &ScrobbleService::instance(), SIGNAL(scrobblesCached(QList)), SLOT(onScrobblesCached(QList))); connect( ui->back, SIGNAL(clicked()), SIGNAL(backClicked())); } MetadataWidget::~MetadataWidget() { delete ui; } void MetadataWidget::fetchTrackInfo( bool force ) { if ( ( force || isVisible() ) && !m_fetchedTrackInfo ) { m_fetchedTrackInfo = true; m_numCalls = m_track.album().isNull() ? 6: 7; QString username = User().name(); qWarning() << username; // fetch Track info m_track.getInfo( this, "onTrackGotInfo", username ); if( !m_track.album().isNull() ) connect( m_track.album().getInfo( username ), SIGNAL(finished()), SLOT(onAlbumGotInfo())); connect( m_track.artist().getInfo( username ), SIGNAL(finished()), SLOT(onArtistGotInfo())); connect( m_track.getTags(), SIGNAL(finished()), SLOT(onTrackGotYourTags())); connect( m_track.artist().getTags(), SIGNAL(finished()), SLOT(onArtistGotYourTags())); connect( m_track.artist().getEvents(), SIGNAL(finished()), SLOT(onArtistGotEvents())); QString country = aApp->currentSession().user().country(); connect( m_track.getBuyLinks( country ), SIGNAL(finished()), SLOT(onTrackGotBuyLinks()) ); } } void MetadataWidget::showEvent( QShowEvent* /*e*/ ) { if ( !m_fetchedTrackInfo ) m_movie->start(); fetchTrackInfo( false ); } void MetadataWidget::checkFinished() { Q_ASSERT( m_numCalls > 0 ); if ( --m_numCalls == 0 ) { ui->loadingStack->setCurrentWidget( ui->content ); m_movie->stop(); emit finished(); } } ScrobbleControls* MetadataWidget::scrobbleControls() const { return ui->scrobbleControls; } void MetadataWidget::setTrackDetails( const Track& track ) { if ( ui->scrollArea->verticalScrollBar()->isVisible() ) ui->scrollArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn ); ui->trackTitle->setText( Label::anchor( track.www().toString(), track.title() ) ); ui->trackArtist->setText( tr("by %1").arg( Label::anchor( track.artist().www().toString(), track.artist()))); ui->artistArtist->setText( Label::anchor( track.artist().www().toString(),track.artist())); ui->similarArtists->setText( Label::anchor( "http://www.last.fm/music/" + track.artist() + "/+similar", tr( "Similar Artists" ) ) ); if ( !m_albumGuess.isNull() ) ui->trackAlbum->setText( tr("from %1").arg( Label::anchor( m_albumGuess.www().toString(), m_albumGuess))); else { if ( m_track.album().isNull() ) ui->trackAlbum->hide(); else ui->trackAlbum->setText( tr("from %1").arg( Label::anchor( track.album().www().toString(), track.album()))); } connect( track.signalProxy(), SIGNAL(loveToggled(bool)), ui->scrobbleControls, SLOT(setLoveChecked(bool))); } void MetadataWidget::onArtistGotInfo() { QNetworkReply* reply = qobject_cast(sender()); XmlQuery lfm; if ( lfm.parse( reply ) ) { m_globalArtistScrobbles = lfm["artist"]["stats"]["playcount"].text().toInt(); m_artistListeners = lfm["artist"]["stats"]["listeners"].text().toInt(); m_userArtistScrobbles = lfm["artist"]["stats"]["userplaycount"].text().toInt(); ui->artistPlays->setText( tr( "%L1" ).arg( m_globalArtistScrobbles ) ); ui->artistUserPlays->setText( tr( "%L1" ).arg( m_userArtistScrobbles ) ); ui->artistListeners->setText( tr( "%L1" ).arg( m_artistListeners ) ); ui->artistPlaysLabel->setText( tr( "Play(s)", "", m_globalArtistScrobbles ) ); ui->artistUserPlaysLabel->setText( tr( "Play(s) in your library", "", m_userArtistScrobbles ) ); ui->artistListenersLabel->setText( tr( "Listener(s)", "", m_artistListeners ) ); // Update the context now that we have the user track listens ui->context->setText( contextString( m_track ) ); { // Similar artists! QList artists = lfm["artist"]["similar"].children("artist").mid( 0, 4 ); if ( artists.count() != 0 ) { ui->similarArtistFrame->show(); ui->similarArtists->show(); QList widgets; widgets << ui->artist1 << ui->artist2 << ui->artist3 << ui->artist4; QRegExp re( "/serve/(\\d*)s?/" ); for ( int i = 0 ; i < artists.count() ; ++i ) { widgets[i]->setArtist( artists[i]["name"].text() ); widgets[i]->setToolTip( artists[i]["name"].text() ); widgets[i]->loadUrl( artists[i]["image size=medium"].text().replace( re, "/serve/\\1s/" ), HttpImageWidget::ScaleNone ); widgets[i]->setHref( artists[i]["url"].text() ); } } } QList tags = lfm["artist"]["tags"].children("tag"); if ( tags.count() == 0 ) ui->artistTagsFrame->hide(); else { QString tagString = tr( "Popular tags:" ); for ( int i = 0 ; i < tags.count() ; ++i ) { if ( i == 0 ) tagString.append( QString( " %1" ).arg( Label::anchor( tags.at(i)["url"].text(), tags.at(i)["name"].text() ) ) ); else tagString.append( QString( " %1 %2" ).arg( QString::fromUtf8( "·" ), Label::anchor( tags.at(i)["url"].text(), tags.at(i)["name"].text() ) ) ); } ui->artistPopTags->setText( tagString ); } //TODO if empty suggest they edit it QString bio; { QStringList bioList = lfm["artist"]["bio"]["summary"].text().trimmed().split( "\r" ); foreach( const QString& p, bioList ) { QString pTrimmed = p.trimmed(); if( pTrimmed.isEmpty()) continue; bio += "

    " + pTrimmed + "

    "; } } bio = Label::boldLinkStyle( bio, Qt::black ); ui->artistBio->setBioText( bio ); ui->artistBio->updateGeometry(); QUrl url = lfm["artist"]["image size=extralarge"].text(); ui->artistBio->loadImage( url, HttpImageWidget::ScaleWidth ); ui->artistBio->setImageHref( QUrl(lfm["artist"]["url"].text())); ui->artistBio->setOnTourVisible( false, QUrl(lfm["artist"]["url"].text()+"/+events")); // Sat, 11 Dec 2010 23:49:01 +0000 QDateTime published; published.fromString( lfm["artist"]["bio"]["published"].text(), "ddd, d MMM yyyy HH:mm:ss" ); ui->artistBioEdit->setText( tr( "Edited on %1 | %2 Edit" ).arg( published.toString( "" ), QString::fromUtf8( "✎" ) ) ); connect( ui->artistBio, SIGNAL(finished()), SLOT(checkFinished()) ); ++m_numCalls; } else { // TODO: what happens when we fail? qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } checkFinished(); } void MetadataWidget::onArtistGotYourTags() { QNetworkReply* reply = qobject_cast(sender()); XmlQuery lfm; if ( lfm.parse( reply ) ) { QList tags = lfm["tags"].children("tag").mid(0, 5); if ( tags.count() == 0 ) ui->artistYourTags->hide(); else { QString tagString = tr( "Your tags:" ); for ( int i = 0 ; i < tags.count() ; ++i ) { if ( i ==0 ) tagString.append( tr( " %1" ).arg( Label::anchor( tags.at(i)["url"].text(), tags.at(i)["name"].text() ) ) ); else tagString.append( tr( " %1 %2" ).arg( QString::fromUtf8( "·" ), Label::anchor( tags.at(i)["url"].text(), tags.at(i)["name"].text() ) ) ); } ui->artistYourTags->setText( tagString ); } } else { // TODO: what happens when we fail? qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } checkFinished(); } void MetadataWidget::onArtistGotEvents() { QNetworkReply* reply = qobject_cast(sender()); XmlQuery lfm; if ( lfm.parse( reply ) ) { if (lfm["events"].children("event").count() > 0) { // Display an on tour notification ui->artistBio->setOnTourVisible( true ); } } else { // TODO: what happens when we fail? qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } checkFinished(); } void MetadataWidget::onAlbumGotInfo() { XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { // int scrobbles = lfm["album"]["playcount"].text().toInt(); // int listeners = lfm["album"]["listeners"].text().toInt(); // int userListens = lfm["album"]["userplaycount"].text().toInt(); ui->albumImage->loadUrl( lfm["album"]["image size=large"].text() ); } else { // TODO: what happens when we fail? qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } checkFinished(); } void MetadataWidget::onTrackGotBuyLinks() { XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { bool thingsToBuy = false; QMenu* menu = new QMenu( this ); menu->addAction( tr("Downloads") )->setEnabled( false ); // USD EUR GBP foreach ( const XmlQuery& affiliation, lfm["affiliations"]["downloads"].children( "affiliation" ) ) { bool isSearch = affiliation["isSearch"].text() == "1"; QAction* buyAction = 0; if ( isSearch ) buyAction = menu->addAction( tr("Search on %1").arg( affiliation["supplierName"].text() ) ); else buyAction = menu->addAction( tr("Buy on %1 %2").arg( affiliation["supplierName"].text(), unicorn::Label::price( affiliation["price"]["amount"].text(), affiliation["price"]["currency"].text() ) ) ); buyAction->setData( affiliation["buyLink"].text() ); thingsToBuy = true; } menu->addSeparator(); menu->addAction( tr("Physical") )->setEnabled( false ); foreach ( const XmlQuery& affiliation, lfm["affiliations"]["physicals"].children( "affiliation" ) ) { bool isSearch = affiliation["isSearch"].text() == "1"; QAction* buyAction = 0; if ( isSearch ) buyAction = menu->addAction( tr("Search on %1").arg( affiliation["supplierName"].text() ) ); else buyAction = menu->addAction( tr("Buy on %1 %2").arg( affiliation["supplierName"].text(), unicorn::Label::price( affiliation["price"]["amount"].text(), affiliation["price"]["currency"].text() ) ) ); buyAction->setData( affiliation["buyLink"].text() ); thingsToBuy = true; } ui->scrobbleControls->ui.buy->setVisible( thingsToBuy ); ui->scrobbleControls->ui.buy->setMenu( menu ); connect( menu, SIGNAL(triggered(QAction*)), SLOT(onBuyActionTriggered(QAction*)) ); } else { // TODO: what happens when we fail? qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } checkFinished(); } void MetadataWidget::onBuyActionTriggered( QAction* buyAction ) { unicorn::DesktopServices::openUrl( buyAction->data().toString() ); AnalyticsService::instance().sendEvent(NOW_PLAYING_CATEGORY, LINK_CLICKED, "BuyLinkClicked" ); } void MetadataWidget::onTrackGotInfo( const QByteArray& data ) { XmlQuery lfm; if ( lfm.parse( data ) ) { m_globalTrackScrobbles = lfm["track"]["playcount"].text().toInt(); //int listeners = lfm["track"]["listeners"].text().toInt(); if ( lfm["track"]["userplaycount"].text().length() > 0 ) m_userTrackScrobbles = lfm["track"]["userplaycount"].text().toInt(); // Update the context now that we have the user track listens ui->context->setText( contextString( m_track ) ); ui->albumImage->setHref( lfm["track"]["url"].text()); if ( lfm["track"]["userloved"].text().length() > 0 ) ui->scrobbleControls->setLoveChecked( lfm["track"]["userloved"].text() == "1" ); // get the popular tags QList tags = lfm["track"]["toptags"].children("tag").mid( 0, 5 ); if ( tags.count() == 0 ) ui->trackTagsFrame->hide(); else { QString tagString = tr( "Popular tags:" ); for ( int i = 0 ; i < tags.count() ; ++i ) { if ( i ==0 ) tagString.append( tr( " %1" ).arg( Label::anchor( tags.at(i)["url"].text(), tags.at(i)["name"].text() ) ) ); else tagString.append( tr( " %1 %2" ).arg( QString::fromUtf8( "·" ), Label::anchor( tags.at(i)["url"].text(), tags.at(i)["name"].text() ) ) ); } ui->trackPopTags->setText( tagString ); // If we don't know the album then set the track image if ( m_track.album().isNull() ) { ui->albumImage->loadUrl( lfm["track"]["album"]["image size=medium"].text() ); } } } else { // TODO: what happens when we fail? qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } checkFinished(); } void MetadataWidget::onTrackGotYourTags() { XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { QList tags = lfm["tags"].children("tag").mid(0, 5); if ( tags.count() == 0 ) ui->trackYourTags->hide(); else { QString tagString = tr( "Your tags:" ); for ( int i = 0 ; i < tags.count() ; ++i ) { if ( i ==0 ) tagString.append( tr( " %1" ).arg( Label::anchor( tags.at(i)["url"].text(), tags.at(i)["name"].text() ) ) ); else tagString.append( tr( " %1 %2" ).arg( QString::fromUtf8( "·" ), Label::anchor( tags.at(i)["url"].text(), tags.at(i)["name"].text() ) ) ); } ui->trackYourTags->setText( tagString ); } } else { // TODO: what happens when we fail? qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } checkFinished(); } void MetadataWidget::listItemClicked( const QModelIndex& i ) { const QUrl& url = i.data( LfmListModel::WwwRole ).toUrl(); unicorn::DesktopServices::openUrl( url ); } void MetadataWidget::onScrobblesCached( const QList& tracks ) { foreach ( lastfm::Track track, tracks ) connect( track.signalProxy(), SIGNAL(scrobbleStatusChanged( short )), SLOT(onScrobbleStatusChanged( short ))); } void MetadataWidget::onScrobbleStatusChanged( short scrobbleStatus ) { if (scrobbleStatus == lastfm::Track::Submitted) { // update total scrobbles and your scrobbles! ++m_userTrackScrobbles; ++m_globalTrackScrobbles; ui->artistUserPlays->setText( QString("%L1").arg( ++m_userArtistScrobbles ) ); ui->artistPlays->setText( QString("%L1").arg( ++m_globalArtistScrobbles ) ); if ( m_userTrackScrobbles == 1 ) ui->artistListeners->setText( QString("%L1").arg( ++m_artistListeners ) ); ui->artistPlaysLabel->setText( tr( "Play(s)", "", m_globalArtistScrobbles ) ); ui->artistUserPlaysLabel->setText( tr( "Play(s) in your library", "", m_userArtistScrobbles ) ); ui->artistListenersLabel->setText( tr( "Listener(s)", "", m_artistListeners ) ); //ui->context->setText( contextString( m_track ) ); } } QString userLibraryLink( const QString& user, const lastfm::Artist& artist ) { return QString("http://www.last.fm/user/%1/library/music/%2").arg( user, artist.name() ); } QString userLibraryLink( const QString& user, const lastfm::Track& track ) { return QString("http://www.last.fm/user/%1/library/music/%2/_/%3").arg( user, track.artist().name(), track.title() ); } QString userLibrary( const QString& user, const lastfm::Artist& artist ) { return Label::anchor( userLibraryLink( user, artist ), user ); } QString MetadataWidget::getContextString( const Track& track ) { QString contextString; return contextString; } QString MetadataWidget::contextString( const Track& track ) { QString context = getContextString( track ); if ( context.isEmpty() ) context = scrobbleString( track ); return context; } QString MetadataWidget::scrobbleString( const Track& track ) { QString artistString = Label::anchor( userLibraryLink( User().name(), track.artist().toString() ), track.artist() ); QString trackString = Label::anchor( userLibraryLink( User().name(), track ), track.title() ); QString userArtistScrobblesString = tr( "%L1 time(s)", "", m_userArtistScrobbles ).arg( m_userArtistScrobbles ); QString userTrackScrobblesString = tr( "%L1 time(s)", "", m_userTrackScrobbles ).arg( m_userTrackScrobbles ); QString scrobbleString; if ( m_userTrackScrobbles != 0 ) scrobbleString = tr( "You've listened to %1 %2 and %3 %4." ).arg( artistString, userArtistScrobblesString, trackString, userTrackScrobblesString ); else { if ( m_userArtistScrobbles != 0 ) scrobbleString = tr( "You've listened to %1 %2, but not this track." ).arg( artistString, userArtistScrobblesString ); else scrobbleString = tr( "This is the first time you've listened to %1." ).arg( artistString ); } return scrobbleString; } void MetadataWidget::setBackButtonVisible( bool visible ) { ui->context->setText( contextString( m_track ) ); ui->back->setVisible( visible ); ui->scrobbleControls->ui.love->setVisible( true ); } ================================================ FILE: app/client/Widgets/MetadataWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef METADATAWIDGET_H #define METADATAWIDGET_H #include #include #include #include #include "lib/unicorn/widgets/HttpImageWidget.h" namespace Ui { class MetadataWidget; } class DataListWidget; class HttpImageWidget; class QLabel; class QGroupBox; class BioWidget; class QMovie; class MetadataWidget : public QFrame { Q_OBJECT public: MetadataWidget( const Track& track, QWidget* p = 0 ); ~MetadataWidget(); void fetchTrackInfo( bool force ); class ScrobbleControls* scrobbleControls() const; QWidget* basicInfoWidget(); void setBackButtonVisible( bool ); static QString getContextString( const Track& track ); private slots: void onTrackGotInfo(const QByteArray& data); void onAlbumGotInfo(); void onArtistGotInfo(); void onArtistGotEvents(); void onTrackGotBuyLinks(); void onBuyActionTriggered( QAction* buyAction ); void onTrackGotYourTags(); void onArtistGotYourTags(); void listItemClicked( const class QModelIndex& ); void onScrobblesCached( const QList& tracks ); void onScrobbleStatusChanged( short scrobbleStatus ); void checkFinished(); signals: void lovedStateChanged(bool loved); void backClicked(); void finished(); private: void setTrackDetails( const Track& track ); QString contextString( const Track& track ); QString scrobbleString( const Track& track ); void showEvent( QShowEvent *e ); private: Ui::MetadataWidget *ui; Track m_track; int m_globalTrackScrobbles; int m_userTrackScrobbles; int m_globalArtistScrobbles; int m_userArtistScrobbles; int m_artistListeners; Album m_albumGuess; int m_numCalls; bool m_fetchedTrackInfo; QPointer m_movie; }; #endif // METADATAWIDGET_H ================================================ FILE: app/client/Widgets/MetadataWidget.ui ================================================ MetadataWidget 0 0 445 590 Form 0 0 Back to Scrobbles true QFrame::NoFrame Qt::ScrollBarAlwaysOff true 0 0 445 567 true 0 0 QFrame::NoFrame QFrame::Raised 0 0 Album 10 Track title Qt::RichText true false Artist Qt::RichText true false QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised Album Qt::RichText true false Qt::Vertical 20 0 1 0 0 Context Qt::RichText true QFrame::NoFrame QFrame::Raised 0 0 Popular tags: Qt::RichText true Your tags: Qt::RichText true QFrame::NoFrame QFrame::Raised 0 0 Artist Qt::RichText false QFrame::NoFrame false false Edited QFrame::NoFrame QFrame::Raised 0 0 0 Listeners 0 0 Plays Plays in your library QFrame::NoFrame QFrame::Raised QFrame::NoFrame QFrame::Raised QFrame::NoFrame QFrame::Raised 0 0 Popular tags: Qt::RichText true Your tags: Qt::RichText true QFrame::StyledPanel QFrame::Raised Similar Artists Qt::RichText QFrame::NoFrame QFrame::Raised 0 0 Artist true Qt::Horizontal 0 20 Artist true Qt::Horizontal 0 20 Artist true Qt::Horizontal 0 20 Artist true Qt::Vertical 20 0 0 0 TextLabel Qt::AlignCenter unicorn::Label QLabel
    lib/unicorn/widgets/Label.h
    HttpImageWidget QLabel
    lib/unicorn/widgets/HttpImageWidget.h
    ScrobbleControls QFrame
    ../Widgets/ScrobbleControls.h
    1
    BioWidget QTextBrowser
    ../Widgets/BioWidget.h
    ContextLabel QLabel
    ../Widgets/ContextLabel.h
    SimilarArtistWidget QLabel
    ../Widgets/SimilarArtistWidget.h
    unicorn::StackedWidget QStackedWidget
    lib/unicorn/widgets/StackedWidget.h
    1
    ================================================ FILE: app/client/Widgets/NothingPlayingWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include "../Application.h" #include "lib/unicorn/widgets/Label.h" #ifdef Q_OS_WIN #include "lib/unicorn/plugins/IPluginInfo.h" #include "lib/unicorn/plugins/PluginList.h" #endif #ifdef Q_OS_MAC // the iTunes listener has a static method we can use to tell if iTunes is installed #include "lib/listener/mac/ITunesListener.h" #endif #include "NothingPlayingWidget.h" #include "ui_NothingPlayingWidget.h" NothingPlayingWidget::NothingPlayingWidget( QWidget* parent ) :QFrame( parent ), ui( new Ui::NothingPlayingWidget ) { ui->setupUi( this ); setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->top->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->contents->setAttribute( Qt::WA_LayoutUsesWidgetRect ); onSessionChanged( aApp->currentSession() ); ui->scrobble->setText( tr( "

    Scrobble from your music player

    " "

    Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.

    ") ); ui->itunes->hide(); ui->applemusic->hide(); ui->wmp->hide(); ui->winamp->hide(); ui->foobar->hide(); #if defined( Q_OS_MAC ) QString MusicOrItunesId = ITunesListener::getPlayerAppId(); ui->applemusic->setVisible( MusicOrItunesId.compare("com.apple.Music") == 0 ); ui->applemusic->setAttribute( Qt::WA_LayoutUsesWidgetRect ); connect( ui->applemusic, SIGNAL(clicked()), SLOT(onAppleMusicClicked())); ui->itunes->setVisible( MusicOrItunesId.compare("com.apple.iTunes") == 0 ); ui->itunes->setAttribute( Qt::WA_LayoutUsesWidgetRect ); connect( ui->itunes, SIGNAL(clicked()), SLOT(oniTunesClicked())); #endif #if defined( Q_OS_WIN ) unicorn::PluginList pluginList; ui->itunes->setVisible( pluginList.pluginById( "itw" )->isAppInstalled() ); ui->itunes->setAttribute( Qt::WA_LayoutUsesWidgetRect ); connect( ui->itunes, SIGNAL(clicked()), SLOT(oniTunesClicked())); ui->wmp->setVisible( pluginList.pluginById( "wmp" )->isAppInstalled() ); ui->wmp->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->winamp->setVisible( pluginList.pluginById( "wa2" )->isAppInstalled() ); ui->winamp->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->foobar->setVisible( pluginList.pluginById( "foo3" )->isAppInstalled() ); ui->foobar->setAttribute( Qt::WA_LayoutUsesWidgetRect ); connect( ui->wmp, SIGNAL(clicked()), SLOT(onWMPClicked())); connect( ui->winamp, SIGNAL(clicked()), SLOT(onWinampClicked())); connect( ui->foobar, SIGNAL(clicked()), SLOT(onFoobarClicked())); #endif connect( aApp, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onSessionChanged(unicorn::Session)) ); } void NothingPlayingWidget::onSessionChanged( const unicorn::Session& session ) { if ( !session.user().name().isEmpty() ) ui->top->setText( tr( "Hello, %1!" ).arg( session.user().name() ) ); } #ifdef Q_OS_WIN void NothingPlayingWidget::startApp( const QString& app ) { QString mediaPlayer = QString( qgetenv( "ProgramFiles(x86)" ) ).append( app ); if ( !QFile::exists( mediaPlayer ) ) mediaPlayer = QString( qgetenv( "ProgramFiles" ) ).append( app ); if ( QFile::exists( mediaPlayer ) ) { mediaPlayer = QString( "\"%1\"" ).arg( mediaPlayer ); QProcess::startDetached( mediaPlayer ); } } void NothingPlayingWidget::oniTunesClicked() { startApp( "/iTunes/iTunes.exe" ); } void NothingPlayingWidget::onAppleMusicClicked() { // doesn't yet exist on Windows } void NothingPlayingWidget::onWinampClicked() { startApp( "/Winamp/winamp.exe" ); } void NothingPlayingWidget::onWMPClicked() { startApp( "/Windows Media Player/wmplayer.exe" ); } void NothingPlayingWidget::onFoobarClicked() { startApp( "/foobar2000/foobar2000.exe" ); } #endif ================================================ FILE: app/client/Widgets/NothingPlayingWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef NOTHINGPLAYINGWIDGET_H #define NOTHINGPLAYINGWIDGET_H #include namespace lastfm { class User; } namespace unicorn { class Session; } namespace Ui { class NothingPlayingWidget; } class NothingPlayingWidget : public QFrame { Q_OBJECT public: explicit NothingPlayingWidget( QWidget* parent = 0 ); private: void setUser( const lastfm::User& user ); #ifdef Q_OS_WIN void startApp( const QString& app ); #endif private slots: void onSessionChanged( const unicorn::Session& session ); #if defined( Q_OS_MAC ) void onAppleMusicClicked(); #endif #if defined( Q_OS_MAC ) || defined( Q_OS_WIN ) void oniTunesClicked(); #endif #ifdef Q_OS_WIN void onWinampClicked(); void onWMPClicked(); void onFoobarClicked(); #endif private: Ui::NothingPlayingWidget* ui; }; #endif // NOTHINGPLAYINGWIDGET_H ================================================ FILE: app/client/Widgets/NothingPlayingWidget.ui ================================================ NothingPlayingWidget 0 0 482 688 Qt::StrongFocus Form 0 0 Hello! QFrame::NoFrame QFrame::Raised 0 0 QFrame::StyledPanel QFrame::Raised true Open iTunes Open Music Open Windows Media Player Open Winamp Open Foobar Qt::Vertical 20 0 ================================================ FILE: app/client/Widgets/NothingPlayingWidget_mac.mm ================================================ #include "NothingPlayingWidget.h" void NothingPlayingWidget::oniTunesClicked() { // launch iTunes! [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:@"com.apple.iTunes" options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifier:NULL]; } void NothingPlayingWidget::onAppleMusicClicked() { // launch Apple Music! [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:@"com.apple.Music" options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifier:NULL]; } ================================================ FILE: app/client/Widgets/NowPlayingStackedWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include "../Services/ScrobbleService/ScrobbleService.h" #include "NowPlayingStackedWidget.h" #include "NowPlayingWidget.h" #include "NothingPlayingWidget.h" NowPlayingStackedWidget::NowPlayingStackedWidget( QWidget* parent ) :unicorn::SlidingStackedWidget( parent ) { addWidget( ui.nothingPlaying = new NothingPlayingWidget( this ) ); addWidget( ui.nowPlaying = new NowPlayingWidget( this ) ); connect( &ScrobbleService::instance(), SIGNAL(trackStarted(lastfm::Track,lastfm::Track)), SLOT(showNowPlaying())); connect( &ScrobbleService::instance(), SIGNAL(stopped()), SLOT(showNothingPlaying())); } void NowPlayingStackedWidget::showNowPlaying() { slide( 1 ); } void NowPlayingStackedWidget::showNothingPlaying() { slide( 0 ); } ================================================ FILE: app/client/Widgets/NowPlayingStackedWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef NOWPLAYINGSTACKEDWIDGET_H #define NOWPLAYINGSTACKEDWIDGET_H #include #include #include "lib/unicorn/widgets/SlidingStackedWidget.h" class TrackItem; class QLabel; class QImage; class NowPlayingStackedWidget : public unicorn::SlidingStackedWidget { Q_OBJECT private: struct { class NothingPlayingWidget* nothingPlaying; class NowPlayingWidget* nowPlaying; } ui; public: NowPlayingStackedWidget( QWidget* parent = 0 ); class NowPlayingWidget* nowPlaying() const { return ui.nowPlaying; } private slots: void showNowPlaying(); void showNothingPlaying(); }; #endif // NOWPLAYINGSTACKEDWIDGET_H ================================================ FILE: app/client/Widgets/NowPlayingWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include "NowPlayingWidget.h" #include "PlaybackControlsWidget.h" #include "MetadataWidget.h" #include "../Services/ScrobbleService.h" NowPlayingWidget::NowPlayingWidget(QWidget *parent) :QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 0 ); layout->addWidget( ui.playbackControls = new PlaybackControlsWidget( this ) ); layout->addWidget( ui.stack = new QStackedWidget( this ) ); ui.stack->addWidget( ui.spinner = new QLabel() ); ui.spinner->setObjectName( "spinner" ); ui.spinner->setAlignment( Qt::AlignCenter ); m_movie = new QMovie( ":/loading_meta.gif", "GIF", this ); m_movie->setCacheMode( QMovie::CacheAll ); ui.spinner->setMovie ( m_movie ); ui.metadata = 0; connect( &ScrobbleService::instance(), SIGNAL(trackStarted(lastfm::Track,lastfm::Track)), SLOT(onTrackStarted(lastfm::Track,lastfm::Track)) ); connect( &ScrobbleService::instance(), SIGNAL(stopped()), SLOT(onStopped()) ); } PlaybackControlsWidget* NowPlayingWidget::playbackControls() const { return ui.playbackControls; } void NowPlayingWidget::onTrackStarted( const lastfm::Track& track, const lastfm::Track& ) { if ( track != Track() ) { setUpdatesEnabled( false ); if ( ui.metadata ) { ui.stack->removeWidget( ui.metadata ); ui.metadata->deleteLater(); } ui.stack->addWidget( ui.metadata = new MetadataWidget( track, this ) ); ui.metadata->setBackButtonVisible( false ); ui.stack->setCurrentWidget( ui.metadata ); m_movie->stop(); setUpdatesEnabled( true ); } } void NowPlayingWidget::onStopped() { ui.stack->setCurrentWidget( ui.spinner ); m_movie->stop(); } ================================================ FILE: app/client/Widgets/NowPlayingWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef NOW_PLAYING_WIDGET_H #define NOW_PLAYING_WIDGET_H #include #include class NowPlayingWidget : public QWidget { Q_OBJECT private: struct { class PlaybackControlsWidget* playbackControls; class QStackedWidget* stack; class QLabel* spinner; class MetadataWidget* metadata; } ui; public: explicit NowPlayingWidget(QWidget *parent = 0); class PlaybackControlsWidget* playbackControls() const; private slots: void onTrackStarted( const lastfm::Track& track, const lastfm::Track& ); void onStopped(); private: class QMovie* m_movie; }; #endif ================================================ FILE: app/client/Widgets/PlaybackControlsWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "../Application.h" #include "../Services/ScrobbleService.h" #include "../Services/AnalyticsService.h" #include "PlaybackControlsWidget.h" #include "ui_PlaybackControlsWidget.h" #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/widgets/Label.h" #include #include #include #include void setLayoutUsesWidgetRect( QWidget* widget ) { foreach ( QObject* object, widget->children() ) { if ( object->isWidgetType() ) { QWidget* widget = qobject_cast( object ); widget->setAttribute( Qt::WA_LayoutUsesWidgetRect ); setLayoutUsesWidgetRect( widget ); } } } PlaybackControlsWidget::PlaybackControlsWidget(QWidget *parent) : QFrame(parent), ui(new Ui::PlaybackControlsWidget), m_scrobbleTrack( false ) { ui->setupUi(this); setLayoutUsesWidgetRect( this ); ui->love->setAttribute( Qt::WA_MacNoClickThrough ); // make sure this widget updates if the actions are changed elsewhere connect( aApp->loveAction(), SIGNAL(changed()), SLOT(onActionsChanged()) ); connect( &ScrobbleService::instance(), SIGNAL(trackStarted(lastfm::Track,lastfm::Track)), SLOT(onTrackStarted(lastfm::Track,lastfm::Track)) ); connect( &ScrobbleService::instance(), SIGNAL(stopped()), SLOT(onStopped())); connect( &ScrobbleService::instance(), SIGNAL(scrobblingOnChanged(bool)), SLOT(update())); onActionsChanged(); // if our buttons are pressed we should trigger the actions connect( ui->love, SIGNAL(clicked()), aApp->loveAction(), SLOT(trigger())); connect( &ScrobbleService::instance(), SIGNAL(frameChanged(int)), SLOT(onFrameChanged(int)) ); } void PlaybackControlsWidget::addToMenu( QMenu& menu, QAction* before ) { menu.insertAction( before, m_playAction ); menu.insertSeparator( before ); menu.insertSeparator( before ); menu.insertAction( before, aApp->loveAction() ); menu.insertSeparator( before ); menu.insertAction( before, aApp->tagAction() ); menu.insertAction( before, aApp->shareAction() ); menu.insertSeparator( before ); //menu.addAction( tr( "Volume Up" ), &RadioService::instance(), SLOT(volumeUp()), QKeySequence( Qt::CTRL + Qt::Key_Up )); //menu.addAction( tr( "Volume Down" ), &RadioService::instance(), SLOT(volumeDown()), QKeySequence( Qt::CTRL + Qt::Key_Down )); } PlaybackControlsWidget::~PlaybackControlsWidget() { delete ui; } void PlaybackControlsWidget::setScrobbleTrack( bool scrobbleTrack ) { m_scrobbleTrack = scrobbleTrack; style()->polish( this ); style()->polish( ui->details ); style()->polish( ui->status ); style()->polish( ui->device ); style()->polish( ui->love ); } void PlaybackControlsWidget::onActionsChanged() { ui->love->setChecked( aApp->loveAction()->isChecked() ); ui->love->setEnabled( aApp->loveAction()->isEnabled() ); ui->love->setToolTip( ui->love->isChecked() ? tr("Unlove") : tr("Love") ); ui->love->setText( ui->love->isChecked() ? tr("Unlove") : tr("Love") ); } void PlaybackControlsWidget::onSpace() { aApp->playAction()->trigger(); } void PlaybackControlsWidget::onPlayClicked( bool checked ) { if ( checked ) { AnalyticsService::instance().sendEvent(NOW_PLAYING_CATEGORY, PLAY_CLICKED, "PlayButtonPressed"); } else { AnalyticsService::instance().sendEvent(NOW_PLAYING_CATEGORY, PLAY_CLICKED, "PauseButtonPressed"); } } void PlaybackControlsWidget::onLoveClicked( bool loved ) { if ( loved ) { AnalyticsService::instance().sendEvent(NOW_PLAYING_CATEGORY, LOVE_TRACK, "TrackLoved"); } else { AnalyticsService::instance().sendEvent(NOW_PLAYING_CATEGORY, LOVE_TRACK, "TrackUnLoved"); } } void PlaybackControlsWidget::onLoveTriggered( bool loved ) { ui->love->setChecked( loved ); onLoveClicked( loved ); } void PlaybackControlsWidget::onTrackStarted( const lastfm::Track& track, const lastfm::Track& /*oldTrack*/ ) { setTrack( track ); } void PlaybackControlsWidget::onError( int /*error*/, const QVariant& /*errorText*/ ) { } void PlaybackControlsWidget::onStopped() { aApp->playAction()->setChecked( false ); aApp->playAction()->setEnabled( true ); aApp->loveAction()->setEnabled( false ); aApp->tagAction()->setEnabled( false ); aApp->shareAction()->setEnabled( false ); } void PlaybackControlsWidget::setTrack( const Track& track ) { // we're about to change loads of stuff to don't update until the end setUpdatesEnabled( false ); disconnect( m_track.signalProxy(), SIGNAL(loveToggled(bool)), ui->love, SLOT(setChecked(bool))); disconnect( m_track.signalProxy(), SIGNAL(scrobbleStatusChanged(short)), this, SLOT(onScrobbleStatusChanged(short)) ); m_track = track; connect( m_track.signalProxy(), SIGNAL(scrobbleStatusChanged(short)), this, SLOT(onScrobbleStatusChanged(short)) ); ui->as->setPixmap( QPixmap( ":/scrobble_marker_OFF.png" ) ); ui->as->setToolTip( "" ); if ( track != Track() ) { // you can love tag and share all tracks aApp->loveAction()->setEnabled( true ); aApp->tagAction()->setEnabled( true ); aApp->shareAction()->setEnabled( true ); // play is always enabled as you should always // be able to start the radio aApp->playAction()->setEnabled( true ); aApp->playAction()->setChecked( false ); aApp->loveAction()->setChecked( track.isLoved() ); bool externalPlayer = track.extra( "playerId" ) == "spt" || track.extra( "playerId" ) == "mpris2"; bool scrobbleTrack = !externalPlayer; ui->love->setVisible( true ); // you can always love a track ui->as->setVisible( scrobbleTrack ); ui->as->setToolTip( tr( "Not scrobbled" ) ); ui->scrobbleMeter->setVisible( scrobbleTrack ); ui->scrobbleMeter->setRange( 0, ScrobbleService::instance().stopWatch()->scrobblePoint() * 1000 ); ui->scrobbleMeter->setValue( 0 ); ui->scrobbleMeter->setToolTip( tr( "Scrobble meter: %1%" ).arg( 0 ) ); ui->progressSpacer->setVisible( true ); connect( track.signalProxy(), SIGNAL(loveToggled(bool)), ui->love, SLOT(setChecked(bool))); ui->status->setText( externalPlayer ? tr("Listening to") : tr("Scrobbling from") ); setScrobbleTrack( true ); ui->message->setVisible( externalPlayer ); ui->message->setText( track.extra( "playerId" ) == "spt" ? unicorn::Label::boldLinkStyle( tr( "Enable scrobbling by getting the %1." ).arg( unicorn::Label::anchor( "https://www.last.fm/settings/applications", tr( "Last.fm app for Spotify" ) ) ), Qt::black ): "" ); ui->device->setText( track.extra( "playerName" ) ); // Set the icon! QString id = track.extra( "playerId" ); if ( id == "osx" || id == "itw" ) ui->icon->setPixmap( QPixmap( ":/control_bar_scrobble_itunes.png" ) ); else if (id == "mac") ui->icon->setPixmap( QPixmap( ":/control_bar_scrobble_applemusic.png" ) ); else if (id == "foo") ui->icon->setPixmap( QPixmap( ":/control_bar_scrobble_foobar.png" ) ); else if (id == "wa2") ui->icon->setPixmap( QPixmap( ":/control_bar_scrobble_winamp.png" ) ); else if (id == "wmp") ui->icon->setPixmap( QPixmap( ":/control_bar_scrobble_wmp.png" ) ); else if (id == "spt") ui->icon->setPixmap( QPixmap( ":/control_bar_scrobble_spotify.png" ) ); else if ( id == "mpris2" ) { if ( QIcon::hasThemeIcon( track.extra( "desktopEntry" ) ) ) ui->icon->setPixmap( QIcon::fromTheme( track.extra( "desktopEntry" ) ).pixmap( 44, 44 ) ); else if ( QIcon::hasThemeIcon( track.extra( "serviceName" ) ) ) ui->icon->setPixmap( QIcon::fromTheme( track.extra( "serviceName" ) ).pixmap( 44, 44 ) ); else if ( QIcon::hasThemeIcon( track.extra( "playerName" ) ) ) ui->icon->setPixmap( QIcon::fromTheme( track.extra( "playerName" ) ).pixmap( 44, 44 ) ); else ui->icon->setPixmap( QPixmap( ":/control_bar_radio_as.png" ) ); } else ui->icon->setPixmap( QPixmap( ":/control_bar_radio_as.png" ) ); } else { // what do we do with a null track??? } setUpdatesEnabled( true ); } bool PlaybackControlsWidget::eventFilter( QObject *obj, QEvent *event ) { return QFrame::eventFilter( obj, event ); } void PlaybackControlsWidget::onFrameChanged( int frame ) { if ( ui->scrobbleMeter->maximum() != 1 ) { int scrobbleValue = frame >= ui->scrobbleMeter->maximum() ? ui->scrobbleMeter->maximum() : frame; ui->scrobbleMeter->setRange( 0, ScrobbleService::instance().stopWatch()->scrobblePoint() * 1000 ); ui->scrobbleMeter->setValue( scrobbleValue ); ui->scrobbleMeter->setToolTip( tr( "Scrobble meter: %1%" ).arg( qRound( ( 100 * scrobbleValue ) / ui->scrobbleMeter->maximum() ) ) ); } } void PlaybackControlsWidget::onScrobbleStatusChanged( short scrobbleStatus ) { if ( scrobbleStatus != Track::Null ) ui->as->setPixmap( QPixmap( ":/scrobble_marker_ON.png" ) ); if ( scrobbleStatus == Track::Null ) ui->as->setToolTip( tr( "Not scrobbled" ) ); else if ( scrobbleStatus == Track::Cached || scrobbleStatus == Track::Submitted ) ui->as->setToolTip( tr( "Scrobbled" ) ); else if ( scrobbleStatus == Track::Error ) ui->as->setToolTip( tr( "Error: \"%1\"" ).arg( m_track.scrobbleErrorText() ) ); } ================================================ FILE: app/client/Widgets/PlaybackControlsWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLAYBACKCONTROLS_H #define PLAYBACKCONTROLS_H #include #include #include #include #include namespace unicorn { class Session; } namespace Ui { class PlaybackControlsWidget; } class QMovie; class PlaybackControlsWidget : public QFrame { Q_OBJECT public: explicit PlaybackControlsWidget(QWidget* parent = 0); ~PlaybackControlsWidget(); public: Q_PROPERTY( bool scrobbleTrack READ scrobbleTrack WRITE setScrobbleTrack ) bool scrobbleTrack() { return m_scrobbleTrack; } void setScrobbleTrack( bool scrobbleTrack ); void addToMenu( class QMenu& menu, QAction* before = 0 ); private: bool eventFilter( QObject *obj, QEvent *event ); private slots: void onActionsChanged(); void onSpace(); void onPlayClicked( bool checked ); void onLoveClicked( bool loved ); void onLoveTriggered( bool loved ); void onTrackStarted( const lastfm::Track& track, const lastfm::Track& oldTrack ); void onError( int error , const QVariant& errorData ); void onStopped(); void onFrameChanged( int frame ); void onScrobbleStatusChanged( short scrobbleStatus ); private: void setTrack( const Track& track ); private: Ui::PlaybackControlsWidget *ui; QPointer m_playAction; bool m_scrobbleTrack; lastfm::Track m_track; }; #endif // PLAYBACKCONTROLS_H ================================================ FILE: app/client/Widgets/PlaybackControlsWidget.ui ================================================ PlaybackControlsWidget 0 0 550 120 Form 0 0 QFrame::NoFrame QFrame::Raised 0 0 Icon QFrame::NoFrame QFrame::Raised 0 0 Status Device QFrame::NoFrame QFrame::Raised 3 0 false 0 0 Love Love true true QFrame::NoFrame QFrame::Raised 10 0 Qt::RichText 24 false false QFrame::NoFrame QFrame::Raised 5 0 24 false unicorn::Label QLabel
    lib/unicorn/widgets/Label.h
    ================================================ FILE: app/client/Widgets/PointyArrow.cpp ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "PointyArrow.h" #include #include #include #include #include #include PointyArrow::PointyArrow() { setAttribute( Qt::WA_TranslucentBackground ); setWindowFlags( Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint ); m_timeline = new QTimeLine( 1000 ); connect( m_timeline, SIGNAL( frameChanged( int )), SLOT( onFrameChanged( int ))); connect( m_timeline, SIGNAL( finished()), SLOT( onFinished())); } PointyArrow::~PointyArrow() { disconnect( m_timeline, 0, this, 0 ); delete m_timeline; } void PointyArrow::paintEvent( QPaintEvent* /*event*/ ) { QPainter p( this ); p.drawPixmap( QPoint( 0, 0 ), m_pm ); } void PointyArrow::onFrameChanged( int frame ) { if( m_currentDirection == DirectionUp || m_currentDirection == DirectionDown ) { move( geometry().left(), frame ); } else { move( frame, geometry().top() ); } } void PointyArrow::onFinished() { m_timeline->toggleDirection(); m_timeline->start(); } void PointyArrow::pointAt( const QPoint& point ) { m_timeline->stop(); QRect availRect = qApp->desktop()->availableGeometry( point ); QRect screenRect = qApp->desktop()->screenGeometry( point ); const bool autoHideSysTray = (availRect == screenRect); QRect taskbarRect; if( !autoHideSysTray ) { taskbarRect = (QRegion(screenRect) - QRegion(availRect)).rects().first(); } if( point.x() < availRect.left() ) { //taskbar is left m_currentDirection = DirectionLeft; m_pm = QPixmap( ":/pointyarrow.png" ).transformed(QTransform().rotate(270.0)); resize( m_pm.size()); move( taskbarRect.right(), point.y() - (height() / 2.0f)); m_timeline->setFrameRange( geometry().left(), geometry().left() + 40 ); } else if( point.y() < availRect.top() ) { //taskbar is top m_currentDirection = DirectionUp; m_pm = QPixmap( ":/pointyarrow.png" ); resize( m_pm.size()); move( point.x() - (width() / 2.0f), taskbarRect.bottom() ); m_timeline->setFrameRange( geometry().top(), geometry().top() + 40 ); } else if( point.x() > availRect.right() ) { //taskbar is right m_currentDirection = DirectionRight; m_pm = QPixmap( ":/pointyarrow.png" ).transformed(QTransform().rotate(90.0)); resize( m_pm.size()); move( taskbarRect.left()-width(), point.y() - (height() / 2.0f)); m_timeline->setFrameRange( geometry().left(), geometry().left() - 40 ); } else { //presume that taskbar is at the bottom m_currentDirection = DirectionDown; m_pm = QPixmap( ":/pointyarrow.png" ).transformed(QTransform().rotate(180.0)); resize( m_pm.size()); move( point.x() - (width() / 2.0f), taskbarRect.top()-height() ); m_timeline->setFrameRange( geometry().top(), geometry().top() - 40 ); } m_timeline->setCurveShape( QTimeLine::EaseInOutCurve ); m_timeline->start(); show(); } ================================================ FILE: app/client/Widgets/PointyArrow.h ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef POINTY_ARROW_H #define POINTY_ARROW_H #include #include class PointyArrow : public QWidget { Q_OBJECT public: PointyArrow(); ~PointyArrow(); void pointAt( const QPoint& ); protected: virtual void paintEvent( QPaintEvent* ); QPixmap m_pm; class QTimeLine* m_timeline; enum { DirectionUp, DirectionDown, DirectionLeft, DirectionRight } m_currentDirection; protected slots: void onFrameChanged( int ); void onFinished(); }; #endif //POINTY_ARROW_H ================================================ FILE: app/client/Widgets/ProfileArtistWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include "lib/unicorn/widgets/HttpImageWidget.h" #include "lib/unicorn/widgets/Label.h" #include "../Application.h" #include "ProfileArtistWidget.h" class PlaysLabel : public QLabel { public: explicit PlaysLabel( const QString& text, int plays, int maxPlays, QWidget* parent = 0 ) :QLabel( text, parent ), m_plays( plays ), m_maxPlays( maxPlays ) {} private: void paintEvent( QPaintEvent* event ) { QPainter p; p.begin( this ); QFontMetrics fm( font() ); p.setPen( QColor( 0xa6a6a6 ) ); p.setBrush( QColor( 0xdedede ) ); p.drawRoundedRect( rect().adjusted( 0, 0, -1, -1 ), 4, 4 ); int indent = fm.width( tr( "%L1 play(s)", "", 999999 ).arg( 999999 ) ); int chunk = ( (width() - indent ) * m_plays ) / m_maxPlays; int adjust = indent + chunk - width(); p.setPen( QColor( 0x2a8bad ) ); p.setBrush( QColor( 0x34bae8 ) ); p.drawRoundedRect( rect().adjusted( 0, 0, adjust - 1, -1 ), 4, 4 ); p.end(); QLabel::paintEvent( event ); } private: int m_plays; int m_maxPlays; }; ProfileArtistWidget::ProfileArtistWidget( const lastfm::XmlQuery& artist, int maxPlays, QWidget* parent) :QFrame( parent ) { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 0 ); HttpImageWidget* artistImage = new HttpImageWidget( this ); layout->addWidget( artistImage ); artistImage->setObjectName( "artistImage" ); QRegExp re( "/serve/(\\d*)s?/" ); artistImage->loadUrl( artist["image size=medium"].text().replace( re, "/serve/\\1s/" ), HttpImageWidget::ScaleNone ); artistImage->setHref( artist["url"].text() ); QVBoxLayout* vl = new QVBoxLayout(); vl->setContentsMargins( 0, 0, 0, 0 ); vl->setSpacing( 6 ); layout->addLayout( vl, 1 ); QHBoxLayout * hl = new QHBoxLayout(); hl->setContentsMargins( 0, 0, 0, 0 ); hl->setSpacing( 0 ); unicorn::Label* artistName = new unicorn::Label( this ); artistName->setTextFormat( Qt::RichText ); artistName->setText( unicorn::Label::boldLinkStyle( unicorn::Label::anchor( artist["url"].text(), artist["name"].text() ), Qt::black ) ); hl->addWidget( artistName, 1 ); artistName->setObjectName( "artistName" ); vl->addLayout( hl ); int playcount = artist["playcount"].text().toInt(); PlaysLabel* plays = new PlaysLabel( tr( "%L1 play(s)", "", playcount ).arg( playcount ), playcount, maxPlays, this ); vl->addWidget( plays ); plays->setObjectName( "plays" ); vl->addStretch(); } ================================================ FILE: app/client/Widgets/ProfileArtistWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef ProfileArtistWidget_H #define ProfileArtistWidget_H #include namespace lastfm { class XmlQuery; } class ProfileArtistWidget : public QFrame { Q_OBJECT public: explicit ProfileArtistWidget( const lastfm::XmlQuery& artist, int maxPlays, QWidget *parent = 0); }; #endif // ProfileArtistWidget_H ================================================ FILE: app/client/Widgets/ProfileWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include "lib/unicorn/widgets/Label.h" #include "lib/unicorn/widgets/AvatarWidget.h" #include "ProfileArtistWidget.h" #include "ContextLabel.h" #include "FriendWidget.h" #include "../Services/ScrobbleService/ScrobbleService.h" #include "../Application.h" #include "ProfileWidget.h" #include "ui_ProfileWidget.h" ProfileWidget::ProfileWidget(QWidget *parent) :QFrame(parent), ui( new Ui::ProfileWidget ) { ui->setupUi( this ); ui->scrobbles->setText( tr( "Scrobble(s)", "", 0 ) ); ui->loved->setText( tr( "Loved track(s)", "", 0 ) ); connect( aApp, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onSessionChanged(unicorn::Session)) ); connect( aApp, SIGNAL(gotUserInfo(lastfm::User)), SLOT(onGotUserInfo(lastfm::User)) ); connect( &ScrobbleService::instance(), SIGNAL(scrobblesCached(QList)), SLOT(onScrobblesCached(QList))); onSessionChanged( aApp->currentSession() ); } ProfileWidget::~ProfileWidget() { delete ui; } void ProfileWidget::onSessionChanged( const unicorn::Session& session ) { if ( session.user().name() != m_currentUser ) { m_currentUser = session.user().name(); ui->avatar->setPixmap( QPixmap( ":/user_default.png" ) ); onGotUserInfo( session.user() ); refresh(); } } void ProfileWidget::onGotUserInfo( const lastfm::User& user ) { ui->avatar->setAlignment( Qt::AlignCenter ); ui->avatar->setUser( user ); ui->avatar->loadUrl( user.imageUrl( User::LargeImage, true ), HttpImageWidget::ScaleNone ); ui->avatar->setHref( user.www() ); ui->infoString->setText( FriendWidget::userString( user ) ); ui->scrobbles->setText( tr( "Scrobble(s) since %1", "", user.scrobbleCount() ).arg( user.dateRegistered().toString( Qt::DefaultLocaleShortDate ) ) ); m_scrobbleCount = user.scrobbleCount(); setScrobbleCount(); ui->name->setText( unicorn::Label::boldLinkStyle( unicorn::Label::anchor( user.www().toString(), user.name() ), Qt::black ) ); } void ProfileWidget::refresh() { // Make sure we don't recieve any updates about the last session disconnect( this, SLOT(onGotLovedTracks())); disconnect( this, SLOT(onGotTopOverallArtists())); disconnect( this, SLOT(onGotTopWeeklyArtists())); disconnect( this, SLOT(onGotLibraryArtists())); connect( aApp->currentSession().user().getLovedTracks( 1 ), SIGNAL(finished()), SLOT(onGotLovedTracks()) ); connect( aApp->currentSession().user().getTopArtists( "overall", 5, 1 ), SIGNAL(finished()), SLOT(onGotTopOverallArtists())); connect( aApp->currentSession().user().getTopArtists( "7day", 5, 1 ), SIGNAL(finished()), SLOT(onGotTopWeeklyArtists())); connect( lastfm::Library::getArtists( aApp->currentSession().user().name(), 1 ), SIGNAL(finished()), SLOT(onGotLibraryArtists())); } void ProfileWidget::onCurrentChanged( int index ) { if ( index == 2 ) refresh(); } void ProfileWidget::onGotLibraryArtists() { lastfm::XmlQuery lfm; if ( lfm.parse( static_cast(sender()) ) ) { int scrobblesPerDay = aApp->currentSession().user().scrobbleCount() / (aApp->currentSession().user().dateRegistered().daysTo( QDateTime::currentDateTime() ) + 1 ); int totalArtists = lfm["artists"].attribute( "total" ).toInt(); QString artistsString = tr( "%L1 artist(s)", "", totalArtists ).arg( totalArtists ); QString tracksString = tr( "%L1 track(s)", "", scrobblesPerDay ).arg( scrobblesPerDay ); ui->userBlurb->setText( tr( "You have %1 in your library and on average listen to %2 per day." ).arg( artistsString , tracksString ) ); ui->userBlurb->show(); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } } void ProfileWidget::onGotTopWeeklyArtists() { lastfm::XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { ui->weekFrame->setUpdatesEnabled( false ); ui->weekFrame->layout()->takeAt( 0 )->widget()->deleteLater(); QFrame* temp = new QFrame( this ); ui->weekFrame->layout()->addWidget( temp ); QVBoxLayout* layout = new QVBoxLayout( temp ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 0 ); int maxPlays = lfm["topartists"]["artist"]["playcount"].text().toInt(); foreach ( const lastfm::XmlQuery& artist, lfm["topartists"].children("artist") ) layout->addWidget( new ProfileArtistWidget( artist, maxPlays, this ) ); ui->weekFrame->setUpdatesEnabled( true ); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } } void ProfileWidget::onGotTopOverallArtists() { lastfm::XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { ui->overallFrame->setUpdatesEnabled( false ); ui->overallFrame->layout()->takeAt( 0 )->widget()->deleteLater(); QFrame* temp = new QFrame( this ); ui->overallFrame->layout()->addWidget( temp ); QVBoxLayout* layout = new QVBoxLayout( temp ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 0 ); int maxPlays = lfm["topartists"]["artist"]["playcount"].text().toInt(); foreach ( const lastfm::XmlQuery& artist, lfm["topartists"].children("artist") ) layout->addWidget( new ProfileArtistWidget( artist, maxPlays, this ) ); ui->overallFrame->setUpdatesEnabled( true ); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } } void ProfileWidget::onGotLovedTracks() { lastfm::XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { int lovedTrackCount = lfm["lovedtracks"].attribute( "total" ).toInt(); ui->loved->setText( tr( "Loved track(s)", "", lovedTrackCount ) ); ui->lovedCount->setText( QString( "%L1" ).arg( lovedTrackCount ) ); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } } void ProfileWidget::onScrobblesCached( const QList& tracks ) { foreach ( lastfm::Track track, tracks ) connect( track.signalProxy(), SIGNAL(scrobbleStatusChanged( short )), SLOT(onScrobbleStatusChanged( short ))); } void ProfileWidget::onScrobbleStatusChanged( short scrobbleStatus ) { if (scrobbleStatus == lastfm::Track::Submitted) { disconnect( sender(), SIGNAL(scrobbleStatusChanged( short )), this, SLOT(onScrobbleStatusChanged( short ))); ++m_scrobbleCount; setScrobbleCount(); } } void ProfileWidget::setScrobbleCount() { ui->scrobbles->setText( tr( "Scrobble(s) since %1", "", aApp->currentSession().user().scrobbleCount() ).arg( aApp->currentSession().user().dateRegistered().toString( "d MMMM yyyy" ) ) ); ui->scrobbleCount->setText( QString( "%L1" ).arg( m_scrobbleCount ) ); } ================================================ FILE: app/client/Widgets/ProfileWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PROFILEWIDGET_H #define PROFILEWIDGET_H #include #include #include #include "lib/unicorn/UnicornSession.h" namespace unicorn { class Label; } namespace Ui { class ProfileWidget; } class ProfileWidget : public QFrame { Q_OBJECT public: explicit ProfileWidget(QWidget *parent = 0); ~ProfileWidget(); public slots: void onCurrentChanged( int index ); void refresh(); private slots: void onSessionChanged( const unicorn::Session& session ); void onGotUserInfo( const lastfm::User& userDetails ); void onGotTopWeeklyArtists(); void onGotTopOverallArtists(); void onGotLibraryArtists(); void onGotLovedTracks(); void onScrobblesCached( const QList& tracks ); void onScrobbleStatusChanged( short scrobbleStatus ); void setScrobbleCount(); private: Ui::ProfileWidget* ui; QString m_currentUser; int m_scrobbleCount; }; #endif // PROFILEWIDGET_H ================================================ FILE: app/client/Widgets/ProfileWidget.ui ================================================ ProfileWidget 0 0 485 685 Form 0 0 QFrame::StyledPanel QFrame::Raised 0 0 Qt::AlignCenter 3 Qt::RichText Qt::Vertical 20 0 0 Scrobbles Qt::Vertical 20 0 0 Loved tracks Qt::Vertical 20 12 context Qt::RichText true QFrame::StyledPanel QFrame::Raised Top Artists This Week QFrame::NoFrame QFrame::Raised 0 0 QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised Top Artists Overall QFrame::NoFrame QFrame::Raised 0 0 QFrame::StyledPanel QFrame::Raised Qt::Vertical 20 0 unicorn::Label QLabel
    lib/unicorn/widgets/Label.h
    ContextLabel QLabel
    ../Widgets/ContextLabel.h
    AvatarWidget QLabel
    lib/unicorn/widgets/AvatarWidget.h
    ================================================ FILE: app/client/Widgets/PushButton.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include "PushButton.h" PushButton::PushButton(QWidget *parent) : QPushButton(parent), m_hovered( false ), m_dark( false ) { setAttribute( Qt::WA_LayoutUsesWidgetRect ); setAttribute( Qt::WA_Hover ); setAttribute( Qt::WA_MacNoClickThrough ); setCursor( Qt::PointingHandCursor ); } bool PushButton::dark() const { return m_dark; } void PushButton::setDark( bool dark ) { m_dark = dark; //style()->polish( this ); } bool PushButton::event( QEvent* e ) { switch ( e->type() ) { case QEvent::HoverEnter: m_hovered = true; update(); break; case QEvent::HoverLeave: m_hovered = false; update(); break; default: break; } return QPushButton::event( e ); } void PushButton::paintEvent(QPaintEvent *) { static QPixmap leftHoverLight( ":/button_LEFT_HOVER.png" ); static QPixmap leftPressLight( ":/button_LEFT_PRESS.png" ); static QPixmap leftRestLight( ":/button_LEFT_REST.png" ); static QPixmap middleHoverLight( ":/button_MIDDLE_HOVER.png" ); static QPixmap middlePressLight( ":/button_MIDDLE_PRESS.png" ); static QPixmap middleRestLight( ":/button_MIDDLE_REST.png" ); static QPixmap rightHoverLight( ":/button_RIGHT_HOVER.png" ); static QPixmap rightPressLight( ":/button_RIGHT_PRESS.png" ); static QPixmap rightRestLight( ":/button_RIGHT_REST.png" ); static QPixmap leftHoverDark( ":/button_dark_LEFT_HOVER.png" ); static QPixmap leftPressDark( ":/button_dark_LEFT_PRESS.png" ); static QPixmap leftRestDark( ":/button_dark_LEFT_REST.png" ); static QPixmap middleHoverDark( ":/button_dark_MIDDLE_HOVER.png" ); static QPixmap middlePressDark( ":/button_dark_MIDDLE_PRESS.png" ); static QPixmap middleRestDark( ":/button_dark_MIDDLE_REST.png" ); static QPixmap rightHoverDark( ":/button_dark_RIGHT_HOVER.png" ); static QPixmap rightPressDark( ":/button_dark_RIGHT_PRESS.png" ); static QPixmap rightRestDark( ":/button_dark_RIGHT_REST.png" ); QPixmap* leftHover = m_dark ? &leftHoverDark : &leftHoverLight; QPixmap* leftPress = m_dark ? &leftPressDark : &leftPressLight; QPixmap* leftRest = m_dark ? &leftRestDark : &leftRestLight; QPixmap* middleHover = m_dark ? &middleHoverDark : &middleHoverLight; QPixmap* middlePress = m_dark ? &middlePressDark : &middlePressLight; QPixmap* middleRest = m_dark ? &middleRestDark : &middleRestLight; QPixmap* rightHover = m_dark ? &rightHoverDark : &rightHoverLight; QPixmap* rightPress = m_dark ? &rightPressDark : &rightPressLight; QPixmap* rightRest = m_dark ? &rightRestDark : &rightRestLight; QPainter p( this ); QRect middleRect = QRect( leftRest->width(), 0, rect().width() - leftRest->width() - rightRest->width(), 49 ); if ( isDown() ) { p.drawPixmap( rect().topLeft(), *leftPress ); p.drawPixmap( middleRect, *middlePress ); p.drawPixmap( rect().topLeft() + QPoint( rect().width() - rightPress->width(), 0 ), *rightPress ); } else if ( m_hovered ) { p.drawPixmap( rect().topLeft(), *leftHover ); p.drawPixmap( middleRect, *middleHover ); p.drawPixmap( rect().topLeft() + QPoint( rect().width() - rightHover->width(), 0 ), *rightHover ); } else { p.drawPixmap( rect().topLeft(), *leftRest ); p.drawPixmap( middleRect, *middleRest ); p.drawPixmap( rect().topLeft() + QPoint( rect().width() - rightRest->width(), 0 ), *rightRest ); } QFontMetrics fmText( font() ); QRect textRect = contentsRect(); p.drawText( textRect, Qt::AlignCenter, fmText.elidedText( text(), Qt::ElideRight, textRect.width() ) ); } ================================================ FILE: app/client/Widgets/PushButton.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PUSHBUTTON_H #define PUSHBUTTON_H #include class PushButton : public QPushButton { Q_OBJECT public: Q_PROPERTY(bool dark READ dark WRITE setDark) explicit PushButton(QWidget *parent = 0); bool dark() const; void setDark( bool dark ); private: bool event(QEvent *e); void paintEvent(QPaintEvent *); private: bool m_hovered; bool m_dark; }; #endif // PUSHBUTTON_H ================================================ FILE: app/client/Widgets/RefreshButton.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include "RefreshButton.h" RefreshButton::RefreshButton(QWidget *parent) : QPushButton(parent), m_pixmap( ":/scrobbles_refresh.png" ) { } void RefreshButton::paintEvent( QPaintEvent* e ) { QPushButton::paintEvent( e ); QPainter p( this ); QFontMetrics fm( font() ); p.drawPixmap( rect().center() - QPoint( ( fm.width( text() ) / 2 ) + m_pixmap.width() + 10, ( m_pixmap.height() / 2 ) - 1 ), m_pixmap ); } ================================================ FILE: app/client/Widgets/RefreshButton.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef REFRESHBUTTON_H #define REFRESHBUTTON_H #include #include class RefreshButton : public QPushButton { Q_OBJECT public: explicit RefreshButton(QWidget *parent = 0); private: void paintEvent(QPaintEvent *); private: QPixmap m_pixmap; }; #endif // REFRESHBUTTON_H ================================================ FILE: app/client/Widgets/ScrobbleControls.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include "lib/unicorn/dialogs/ShareDialog.h" #include "lib/unicorn/dialogs/TagDialog.h" #include "../Services/AnalyticsService.h" #include "ScrobbleControls.h" #include "../Application.h" ScrobbleControls::ScrobbleControls( QWidget* parent ) :QFrame( parent ) { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 5 ); layout->addWidget(ui.love = new QPushButton(tr("Love")), 0, Qt::AlignCenter); ui.love->setObjectName("love"); ui.love->setCheckable( true ); ui.love->setToolTip( tr( "Love track" ) ); connect( ui.love, SIGNAL( clicked(bool) ), SLOT( onLoveChanged( bool ) ) ); layout->addWidget(ui.tag = new QPushButton(tr("Tag")), 0, Qt::AlignCenter); ui.tag->setObjectName("tag"); ui.tag->setToolTip( tr( "Add tags" ) ); layout->addWidget(ui.share = new QPushButton(tr("Share")), 0, Qt::AlignCenter); ui.share->setObjectName("share"); ui.share->setToolTip( tr( "Share" ) ); QMenu* shareMenu = new QMenu( this ); shareMenu->addAction( tr( "Share on Last.fm" ), this, SLOT(onShareLastFm()) ); shareMenu->addAction( tr( "Share on Twitter" ), this, SLOT(onShareTwitter()) ); shareMenu->addAction( tr( "Share on Facebook" ), this, SLOT(onShareFacebook()) ); ui.share->setMenu( shareMenu ); layout->addWidget(ui.buy = new QPushButton(tr("Buy")), 0, Qt::AlignCenter); ui.buy->setObjectName("buy"); ui.buy->setToolTip( tr( "Buy" ) ); ui.buy->setVisible( false ); ui.love->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui.tag->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui.share->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui.buy->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui.love->setAttribute( Qt::WA_MacNoClickThrough ); ui.tag->setAttribute( Qt::WA_MacNoClickThrough ); ui.share->setAttribute( Qt::WA_MacNoClickThrough ); ui.buy->setAttribute( Qt::WA_MacNoClickThrough ); layout->addStretch( 1 ); new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_S ), ui.share, SLOT( click() ) ); new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_T ), ui.tag, SLOT( click() ) ); new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_L ), ui.love, SLOT( toggle() ) ); new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_B ), ui.buy, SLOT( click() ) ); connect( ui.tag, SIGNAL( clicked()), SLOT( onTag())); //connect( ui.share, SIGNAL( clicked()), SLOT( onShare())); } void ScrobbleControls::setTrack( const Track& track ) { disconnect( m_track.signalProxy(), SIGNAL(loveToggled(bool)), this, SLOT(setLoveChecked(bool))); m_track = track; connect( m_track.signalProxy(), SIGNAL(loveToggled(bool)), SLOT(setLoveChecked(bool))); } void ScrobbleControls::setLoveChecked( bool checked ) { /// This just changes the state of the love button ui.love->setChecked( checked ); if ( checked ) ui.love->setToolTip( tr( "Unlove track" ) ); else ui.love->setToolTip( tr( "Love track" ) ); } void ScrobbleControls::onLoveChanged( bool checked ) { /// This changes the state of the love button and /// loves the track on Last.fm // change the button state to the new state. Don't worry // it'll get changed back if the web service request fails setLoveChecked( checked ); MutableTrack track( m_track ); if ( checked ) track.love(); else track.unlove(); } void ScrobbleControls::onShareLastFm() { ShareDialog* sd = new ShareDialog( m_track, window() ); sd->raise(); sd->show(); sd->activateWindow(); AnalyticsService::instance().sendEvent( aApp->currentCategory(), SHARE_CLICKED, "ShareDialog"); } void ScrobbleControls::onShareTwitter() { ShareDialog::shareTwitter( m_track ); AnalyticsService::instance().sendEvent( aApp->currentCategory(), SHARE_CLICKED, "TwitterShare"); } void ScrobbleControls::onShareFacebook() { ShareDialog::shareFacebook( m_track ); AnalyticsService::instance().sendEvent( aApp->currentCategory(), SHARE_CLICKED, "FacebookShare"); } void ScrobbleControls::onTag() { TagDialog* td = new TagDialog( m_track, window() ); td->raise(); td->show(); td->activateWindow(); AnalyticsService::instance().sendEvent( aApp->currentCategory(), TAG_CLICKED, "TagButtonPressed"); } ================================================ FILE: app/client/Widgets/ScrobbleControls.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SCROBBLE_CONTROLS_H #define SCROBBLE_CONTROLS_H #include #include #include namespace unicorn{ class Session; } namespace lastfm{ class User; } class QPushButton; class ScrobbleControls : public QFrame { Q_OBJECT public: struct { QPushButton* love; QPushButton* tag; QPushButton* share; QPushButton* buy; } ui; public: ScrobbleControls( QWidget* parent = 0 ); void setTrack( const Track& track ); public slots: void setLoveChecked( bool checked ); private slots: void onLoveChanged( bool checked ); void onShareLastFm(); void onShareTwitter(); void onShareFacebook(); void onTag(); private: Track m_track; }; #endif //SCROBBLE_CONTROLS_H ================================================ FILE: app/client/Widgets/ScrobblesListWidget.cpp ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include "lib/unicorn/dialogs/ShareDialog.h" #include "lib/unicorn/dialogs/TagDialog.h" #include "lib/unicorn/DesktopServices.h" #include "../Services/ScrobbleService.h" #include "../Application.h" #include "RefreshButton.h" #include "TrackWidget.h" #include "ScrobblesListWidget.h" #define kScrobbleLimit 30 class ScrobblesListWidgetItem : public QListWidgetItem { public: ScrobblesListWidgetItem( QListWidget* parent ); bool operator<( const QListWidgetItem& that ) const; bool isNowPlaying() const; void setNowPlaying( bool nowPlaying ); void setTrack( lastfm::Track& track ); private: bool m_nowPlaying; }; ScrobblesListWidgetItem::ScrobblesListWidgetItem( QListWidget* parent ) :QListWidgetItem( parent ), m_nowPlaying( false ) { } bool ScrobblesListWidgetItem::isNowPlaying() const { return m_nowPlaying; } void ScrobblesListWidgetItem::setNowPlaying( bool nowPlaying ) { m_nowPlaying = nowPlaying; static_cast( listWidget()->itemWidget( this ) )->setNowPlaying( true ); } void ScrobblesListWidgetItem::setTrack( lastfm::Track& track ) { static_cast( listWidget()->itemWidget( this ) )->setTrack( track ); } bool ScrobblesListWidgetItem::operator<( const QListWidgetItem& that ) const { if ( !qobject_cast(listWidget()->itemWidget( const_cast(this) )) ) { // this isn't a track widget if ( qobject_cast(listWidget()->itemWidget( const_cast(this) )) ) return true; // this is a refresh button else return false; // more button goes at the bottom } if ( qobject_cast(listWidget()->itemWidget( const_cast(&that) )) ) return false; // that is a refresh button so this is not less than. refresh button goes at the top if ( !qobject_cast(listWidget()->itemWidget( const_cast(&that) )) ) return true; // that is the more push button. everything else is less than it // at this point both this and that are of type TrackWidget // check if it's the now playing track if ( m_nowPlaying ) return true; // now playing goes at the top of the tracks else if ( static_cast(&that)->m_nowPlaying ) return false; // now order by timestamp return static_cast( listWidget()->itemWidget( const_cast( this ) ) )->track().timestamp().toTime_t() > static_cast( listWidget()->itemWidget( const_cast( &that ) ) )->track().timestamp().toTime_t(); } ScrobblesListWidget::ScrobblesListWidget( QWidget* parent ) :QListWidget( parent ), m_trackItem( 0 ) { setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); #ifdef Q_OS_MAC connect( verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(scroll()) ); #endif setAttribute( Qt::WA_MacNoClickThrough ); setAttribute( Qt::WA_MacShowFocusRect, false ); setUniformItemSizes( false ); setSortingEnabled( false ); setSelectionMode( QAbstractItemView::NoSelection ); setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); connect( qApp, SIGNAL( sessionChanged(unicorn::Session)), SLOT(onSessionChanged(unicorn::Session))); connect( &ScrobbleService::instance(), SIGNAL(scrobblesCached(QList)), SLOT(onScrobblesSubmitted(QList) ) ); connect( &ScrobbleService::instance(), SIGNAL(scrobblesSubmitted(QList)), SLOT(onScrobblesSubmitted(QList) ) ); connect( &ScrobbleService::instance(), SIGNAL(trackStarted(lastfm::Track,lastfm::Track)), SLOT(onTrackStarted(lastfm::Track,lastfm::Track))); connect( &ScrobbleService::instance(), SIGNAL(paused()), SLOT(onPaused())); connect( &ScrobbleService::instance(), SIGNAL(resumed()), SLOT(onResumed())); connect( &ScrobbleService::instance(), SIGNAL(stopped()), SLOT(onStopped())); onSessionChanged( aApp->currentSession() ); } #ifdef Q_OS_MAC void ScrobblesListWidget::scroll() { // KLUDGE: The friend list widgets don't move unless we do this sortItems( Qt::AscendingOrder ); } #endif void ScrobblesListWidget::showEvent(QShowEvent *) { QList tracks; for ( int i = 0 ; i < count() ; ++i ) { TrackWidget* trackWidget = qobject_cast( itemWidget( item( i ) ) ); if ( trackWidget && !item( i )->isHidden() ) tracks << trackWidget->track(); } fetchTrackInfo( tracks ); } void ScrobblesListWidget::fetchTrackInfo( const QList& tracks ) { if ( isVisible() ) { // Make sure we fetch info for any tracks with unknown loved status foreach ( const lastfm::Track& track, tracks ) if ( track.loveStatus() == lastfm::Track::UnknownLoveStatus ) track.getInfo( this, "write", User().name() ); } } void ScrobblesListWidget::mousePressEvent( QMouseEvent* event ) { event->setAccepted( false ); } void ScrobblesListWidget::mouseReleaseEvent( QMouseEvent* event ) { event->setAccepted( false ); } void ScrobblesListWidget::onItemClicked( TrackWidget& trackWidget ) { emit trackClicked( trackWidget ); } void ScrobblesListWidget::onMoreClicked() { unicorn::DesktopServices::openUrl( lastfm::UrlBuilder( "user" ).slash( User().name() ).slash( "tracks" ).url() ); } void ScrobblesListWidget::onSessionChanged( const unicorn::Session& session ) { if ( !session.user().name().isEmpty() ) { QString path = lastfm::dir::runtimeData().filePath( session.user().name() + "_recent_tracks.xml" ); if ( m_path != path ) { m_path = path; read(); refresh(); } } } void ScrobblesListWidget::read() { clear(); // always have a now playing item in the list m_trackItem = new ScrobblesListWidgetItem( this ); TrackWidget* trackWidget = new TrackWidget( m_track, this ); trackWidget->setObjectName( "nowPlaying" ); setItemWidget( m_trackItem, trackWidget ); m_trackItem->setSizeHint( trackWidget->sizeHint() ); m_trackItem->setHidden( true ); m_trackItem->setNowPlaying( true ); connect( trackWidget, SIGNAL(clicked(TrackWidget&)), SLOT(onItemClicked(TrackWidget&)) ); // always have the refresh button in the list m_refreshItem = new ScrobblesListWidgetItem( this ); RefreshButton* refreshButton = new RefreshButton( this ); refreshButton->setObjectName( "refresh" ); setItemWidget( m_refreshItem, refreshButton ); m_refreshItem->setSizeHint( refreshButton->sizeHint() ); connect( refreshButton, SIGNAL(clicked()), SLOT(refresh()) ); onRefreshing( false ); // always have a view more item in the list m_moreItem = new ScrobblesListWidgetItem( this ); QPushButton* moreButton = new QPushButton( tr( "More Scrobbles at Last.fm" ), this ); moreButton->setObjectName( "more" ); setItemWidget( m_moreItem, moreButton ); m_moreItem->setSizeHint( moreButton->sizeHint() ); connect( moreButton, SIGNAL(clicked()), SLOT(onMoreClicked()) ); QFile file( m_path ); file.open( QFile::Text | QFile::ReadOnly ); QTextStream stream( &file ); stream.setCodec( "UTF-8" ); QDomDocument xml; xml.setContent( stream.readAll() ); QList tracks; for (QDomNode n = xml.documentElement().lastChild(); !n.isNull(); n = n.previousSibling()) tracks << Track( n.toElement() ); addTracks( tracks ); fetchTrackInfo( tracks ); limit( kScrobbleLimit ); } void ScrobblesListWidget::write() { if ( !m_writeTimer ) { m_writeTimer = new QTimer( this ); connect( m_writeTimer, SIGNAL(timeout()), this, SLOT(doWrite()) ); m_writeTimer->setSingleShot( true ); } m_writeTimer->start( 500 ); } void ScrobblesListWidget::doWrite() { if ( count() == 0 ) QFile::remove( m_path ); else { QDomDocument xml; QDomElement e = xml.createElement( "recent_tracks" ); e.setAttribute( "product", QCoreApplication::applicationName() ); e.setAttribute( "version", "2" ); for ( int i = 0 ; i < count() ; ++i ) { TrackWidget* trackWidget = qobject_cast( itemWidget( item( i ) ) ); if ( trackWidget && !static_cast( item( i ) )->isNowPlaying() ) e.appendChild( static_cast( itemWidget( item( i ) ) )->track().toDomElement( xml ) ); } xml.appendChild( e ); QFile file( m_path ); file.open( QIODevice::WriteOnly | QIODevice::Text ); QTextStream stream( &file ); stream.setCodec( "UTF-8" ); stream << "\n"; stream << xml.toString( 2 ); } } void ScrobblesListWidget::onTrackStarted( const Track& track, const Track& ) { // Don't display Spotify here as we don't know if the current user is the one scrobbling // If it is the current user it will be fetch by user.getRecentTracks if ( track.extra( "playerId" ) != "spt" ) { m_track = track; m_trackItem->setTrack( m_track ); m_trackItem->setHidden( false ); connect( m_track.signalProxy(), SIGNAL(loveToggled(bool)), SLOT(write())); QList tracks; tracks << track; fetchTrackInfo( tracks ); } hideScrobbledNowPlaying(); } void ScrobblesListWidget::onResumed() { m_trackItem->setHidden( false ); hideScrobbledNowPlaying(); } void ScrobblesListWidget::onPaused() { m_trackItem->setHidden( true ); hideScrobbledNowPlaying(); } void ScrobblesListWidget::onStopped() { m_trackItem->setHidden( true ); hideScrobbledNowPlaying(); } void ScrobblesListWidget::hideScrobbledNowPlaying() { for ( int i = 0 ; i < count() ; ++i ) { if ( item( i ) != m_trackItem ) { TrackWidget* trackWidget = qobject_cast( itemWidget( item( i ) ) ); item( i )->setHidden( trackWidget && !m_trackItem->isHidden() && ( trackWidget->track().timestamp().toTime_t() == m_track.timestamp().toTime_t() || trackWidget->track().timestamp().toTime_t() == ScrobbleService::instance().currentTrack().timestamp().toTime_t() ) ); } } } void ScrobblesListWidget::refresh() { if ( !m_recentTrackReply ) { m_recentTrackReply = User().getRecentTracks( kScrobbleLimit, 1 ); connect( m_recentTrackReply, SIGNAL(finished()), SLOT(onGotRecentTracks()) ); onRefreshing( true ); } } void ScrobblesListWidget::onRefreshing( bool refreshing ) { RefreshButton* refreshButton = qobject_cast( itemWidget( m_refreshItem ) ); refreshButton->setText( refreshing ? tr( "Refreshing..." ) : tr( "Refresh Scrobbles" ) ); refreshButton->setEnabled( !refreshing ); } void ScrobblesListWidget::onGotRecentTracks() { XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { m_trackItem->setHidden( true ); QList tracks; lastfm::MutableTrack nowPlayingTrack; bool checkedFirstScrobble( false ); foreach ( const XmlQuery& trackXml, lfm["recenttracks"].children("track") ) { if ( trackXml.attribute( "nowplaying" ) == "true" ) { nowPlayingTrack.setTitle( trackXml["name"].text() ); nowPlayingTrack.setArtist( trackXml["artist"]["name"].text() ); nowPlayingTrack.setAlbum( trackXml["album"].text() ); if ( nowPlayingTrack != m_track ) { // This is a different track so change to it nowPlayingTrack.setTimeStamp( QDateTime::fromTime_t( trackXml["date"].attribute("uts").toUInt() ) ); nowPlayingTrack.setImageUrl( Track::SmallImage, trackXml["image size=small"].text() ); nowPlayingTrack.setImageUrl( Track::MediumImage, trackXml["image size=medium"].text() ); nowPlayingTrack.setImageUrl( Track::LargeImage, trackXml["image size=large"].text() ); nowPlayingTrack.setImageUrl( Track::ExtraLargeImage, trackXml["image size=extralarge"].text() ); m_track = nowPlayingTrack; m_trackItem->setTrack( m_track ); connect( m_track.signalProxy(), SIGNAL(loveToggled(bool)), SLOT(write())); QString loved = trackXml["loved"].text(); if ( !loved.isEmpty() ) nowPlayingTrack.setLoved( loved == "1" ); else m_track.getInfo( this, "write", User().name() ); } m_trackItem->setHidden( false ); } else { MutableTrack track; track.setTitle( trackXml["name"].text() ); track.setArtist( trackXml["artist"]["name"].text() ); track.setAlbum( trackXml["album"].text() ); track.setTimeStamp( QDateTime::fromTime_t( trackXml["date"].attribute("uts").toUInt() ) ); if ( checkedFirstScrobble || (!checkedFirstScrobble && ( track != nowPlayingTrack || (track == nowPlayingTrack && track.timestamp().secsTo( QDateTime::currentDateTime() ) > 10 * 60 ) ) ) ) { track.setImageUrl( Track::SmallImage, trackXml["image size=small"].text() ); track.setImageUrl( Track::MediumImage, trackXml["image size=medium"].text() ); track.setImageUrl( Track::LargeImage, trackXml["image size=large"].text() ); track.setImageUrl( Track::ExtraLargeImage, trackXml["image size=extralarge"].text() ); QString loved = trackXml["loved"].text(); if ( !loved.isEmpty() ) track.setLoved( loved == "1" ); tracks << track; } checkedFirstScrobble = true; } } QList addedTracks = addTracks( tracks ); // get info for track if we don't know the loved state. This is so it will // work before and after the loved field is added to user.getRecentTracks fetchTrackInfo( addedTracks ); write(); } onRefreshing( false ); m_recentTrackReply->deleteLater(); hideScrobbledNowPlaying(); } void ScrobblesListWidget::onScrobblesSubmitted( const QList& tracks ) { // We need to find out if info has already been fetched for this track or not. // If the now playing view wasn't visible it won't have been. // Also, should also only fetch if the scrobbles list is visible too QList addedTracks = addTracks( tracks ); fetchTrackInfo( addedTracks ); } void ScrobblesListWidget::onTrackWidgetRemoved() { for ( int i = 0 ; i < count() ; ++i ) { if ( itemWidget( item( i ) ) == sender() ) { itemWidget( takeItem( i ) )->deleteLater(); refresh(); break; } } } QList ScrobblesListWidget::addTracks( const QList& tracks ) { QList addedTracks; for ( int i = 0 ; i < tracks.count() ; ++i ) { if ( tracks[i].scrobbleError() != Track::Invalid ) { // the track was not filtered client side for being invalid int pos = -1; for ( int j = 0 ; j < count() ; ++j ) { TrackWidget* trackWidget = qobject_cast( itemWidget( item( j ) ) ); if ( trackWidget && !static_cast( item( j ) )->isNowPlaying() && tracks[i].timestamp().toTime_t() == trackWidget->track().timestamp().toTime_t() ) { pos = j; break; } } if ( pos == -1 ) { // the track was not in the list ScrobblesListWidgetItem* item = new ScrobblesListWidgetItem( this ); Track track = tracks[i]; TrackWidget* trackWidget = new TrackWidget( track, this ); setItemWidget( item, trackWidget ); item->setSizeHint( trackWidget->sizeHint() ); connect( trackWidget, SIGNAL(removed()), SLOT(onTrackWidgetRemoved())); connect( trackWidget, SIGNAL(clicked(TrackWidget&)), SLOT(onItemClicked(TrackWidget&)) ); connect( track.signalProxy(), SIGNAL(loveToggled(bool)), SLOT(write())); connect( track.signalProxy(), SIGNAL(scrobbleStatusChanged(short)), SLOT(write())); } else { // update the track in the list with the new infos! TrackWidget* trackWidget = qobject_cast( itemWidget( item( pos ) ) ); trackWidget->update( tracks[i] ); } } } limit( kScrobbleLimit ); write(); hideScrobbledNowPlaying(); return addedTracks; } void ScrobblesListWidget::limit( int limit ) { sortItems(); // we add three to the limit here to account for the refresh button, // the now playing track (usually hidden), and the more button if ( count() > limit + 3 ) { while ( count() > limit + 3 ) { QListWidgetItem* item = takeItem( count() - 2 ); itemWidget( item )->deleteLater(); delete item; } write(); } } ================================================ FILE: app/client/Widgets/ScrobblesListWidget.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SCROBBLES_LIST_WIDGET_H #define SCROBBLES_LIST_WIDGET_H #include #include #include #include #include namespace lastfm { class Track; } using lastfm::Track; namespace unicorn { class Session; } class QNetworkReply; class ScrobblesListWidget : public QListWidget { Q_OBJECT public: ScrobblesListWidget( QWidget* parent = 0 ); signals: void trackClicked( class TrackWidget& ); public slots: void refresh(); private slots: void onItemClicked( class TrackWidget& index ); void onMoreClicked(); void onTrackStarted( const lastfm::Track& track, const lastfm::Track& ); void onSessionChanged( const unicorn::Session& session ); void onResumed(); void onPaused(); void onStopped(); void onGotRecentTracks(); void onScrobblesSubmitted( const QList& tracks ); void onTrackWidgetRemoved(); void write(); void doWrite(); #ifdef Q_OS_MAC void scroll(); #endif private: QString price( const QString& price, const QString& currency ) const; void read(); QList addTracks( const QList& tracks ); void limit( int limit ); void hideScrobbledNowPlaying(); void showEvent(QShowEvent *); void fetchTrackInfo( const QList& tracks ); void mousePressEvent( QMouseEvent* event ); void mouseReleaseEvent( QMouseEvent* event ); void onRefreshing( bool refreshing ); private: QString m_path; QPointer m_writeTimer; QPointer m_recentTrackReply; lastfm::Track m_track; class ScrobblesListWidgetItem* m_refreshItem; class ScrobblesListWidgetItem* m_trackItem; class ScrobblesListWidgetItem* m_moreItem; }; #endif //ACTIVITY_LIST_WIDGET_H ================================================ FILE: app/client/Widgets/ScrobblesWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include "../Services/AnalyticsService/AnalyticsService.h" #include "ScrobblesListWidget.h" #include "TrackWidget.h" #include "MetadataWidget.h" #include "ScrobblesWidget.h" #include "ui_ScrobblesWidget.h" ScrobblesWidget::ScrobblesWidget( QWidget* parent ) :QWidget( parent ), ui( new Ui::ScrobblesWidget ), m_lastIndex( 0 ) { ui->setupUi( this ); connect( ui->scrobbles, SIGNAL( trackClicked(TrackWidget&)), SLOT( onTrackClicked(TrackWidget&))); connect( ui->slidingWidget, SIGNAL( animationFinished()), SLOT(onMoveFinished())); ui->stackedWidget->setCurrentWidget( ui->scrobbles ); } ScrobblesWidget::~ScrobblesWidget() { delete ui; } void ScrobblesWidget::refresh() { ui->scrobbles->refresh(); } void ScrobblesWidget::onCurrentChanged( int index ) { if ( index == 1 && (m_lastIndex != index || ui->slidingWidget->currentWidget() != ui->stackedWidget ) ) { // We've switch to this tab OR are moving back from Scrobble to ScrobbleList ui->scrobbles->refresh(); AnalyticsService::instance().sendPageView( "Scrobbles" ); } m_lastIndex = index; ui->slidingWidget->slide( ui->slidingWidget->indexOf( ui->stackedWidget ) ); } void ScrobblesWidget::onTrackClicked( TrackWidget& trackWidget ) { MetadataWidget* w; ui->slidingWidget->addWidget( w = new MetadataWidget( trackWidget.track() )); w->fetchTrackInfo( true ); w->setBackButtonVisible( true ); trackWidget.startSpinner(); connect( ui->slidingWidget, SIGNAL( animationFinished()), &trackWidget, SLOT(clearSpinner()) ); connect( w, SIGNAL(finished()), SLOT(onMetadataWidgetFinished())); connect( w, SIGNAL(backClicked()), SLOT(onBackClicked())); } void ScrobblesWidget::onMetadataWidgetFinished() { ui->slidingWidget->slide( ui->slidingWidget->currentIndex() + 1 ); AnalyticsService::instance().sendPageView( "Scrobbles/Scrobble" ); } void ScrobblesWidget::onBackClicked() { ui->slidingWidget->slide( ui->slidingWidget->indexOf( ui->stackedWidget ) ); AnalyticsService::instance().sendPageView( "Scrobbles" ); } void ScrobblesWidget::onMoveFinished() { if( ui->slidingWidget->currentWidget() == ui->stackedWidget ) { while ( ui->slidingWidget->count() > 1 ) { QWidget* widget = ui->slidingWidget->widget( 1 ); ui->slidingWidget->removeWidget( widget ); delete widget; } } } ================================================ FILE: app/client/Widgets/ScrobblesWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef RECENT_TRACKS_WIDGET_H_ #define RECENT_TRACKS_WIDGET_H_ #include namespace lastfm { class Track; } using lastfm::Track; namespace Ui { class ScrobblesWidget; } class ScrobblesWidget : public QWidget { Q_OBJECT public: explicit ScrobblesWidget( QWidget* parent = 0 ); ~ScrobblesWidget(); public slots: void onCurrentChanged( int index ); void refresh(); protected slots: void onTrackClicked( class TrackWidget& trackWidget ); void onBackClicked(); void onMoveFinished(); void onMetadataWidgetFinished(); protected: Ui::ScrobblesWidget* ui; int m_lastIndex; }; #endif //RECENT_TRACKS_WIDGET_H_ ================================================ FILE: app/client/Widgets/ScrobblesWidget.ui ================================================ ScrobblesWidget 0 0 429 519 Form 0 0 0 12 0 60 405 242 TextLabel Qt::AlignCenter 12 20 You haven't scrobbled any music to Last.fm yet. true Start listening to some music in your media player: true Qt::Vertical 20 40 ScrobblesListWidget QWidget
    ../Widgets/ScrobblesListWidget.h
    1
    unicorn::SlidingStackedWidget QStackedWidget
    lib/unicorn/widgets/SlidingStackedWidget.h
    1
    ================================================ FILE: app/client/Widgets/ShortcutEdit.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include "ShortcutEdit.h" ShortcutEdit::ShortcutEdit( QWidget* parent ) :QComboBox( parent ) { setEditable( true ); QStringList keys; #ifdef Q_OS_MAC keys << QString::fromUtf8("⌃⌘ S") #else keys << QString::fromUtf8("Ctrl+Shift+S") #endif << "F1" << "F3" << "F2" << "F4" << "F5" << "F6" << "F7" << "F8" << "F9"; QStringListModel* model = new QStringListModel(keys, this); setModel(model); } void ShortcutEdit::setTextValue( QString str ) { setCurrentIndex( findText(str)); lineEdit()->setText( str ); } Qt::KeyboardModifiers ShortcutEdit::modifiers() const { return m_modifiers; } void ShortcutEdit::setModifiers( Qt::KeyboardModifiers modifiers ) { m_modifiers = modifiers; } int ShortcutEdit::key() const { return m_key; } void ShortcutEdit::setKey( int key ) { m_key = key; } void ShortcutEdit::keyPressEvent( QKeyEvent* e ) { QString text; Qt::KeyboardModifiers modifiers; //Modifier to symbol if( e->modifiers() & Qt::ControlModifier ) { #ifdef Q_OS_MAC text += QString::fromUtf8( "⌘" ); #else text += "Ctrl+"; #endif modifiers |= Qt::ControlModifier; } if( e->modifiers() & Qt::AltModifier ) { #ifdef Q_OS_MAC text += QString::fromUtf8( "⌥" ); #else text += "Alt+"; #endif modifiers |= Qt::AltModifier; } if( e->modifiers() & Qt::ShiftModifier ) { #ifdef Q_OS_MAC text += QString::fromUtf8( "⇧" ); #else text += "Shift+"; #endif modifiers |= Qt::ShiftModifier; } if( e->modifiers() & Qt::MetaModifier ) { #ifdef Q_OS_MAC text += QString::fromUtf8( "⌃" ); #else text += "Win+"; #endif modifiers |= Qt::MetaModifier; } //Backspace key to clear shortcut if( !e->modifiers() && e->key() == Qt::Key_Backspace) { lineEdit()->clear(); m_modifiers = 0; m_key = 0; return; } //Don't allow shortcuts with no modifier unless they're F keys if( !e->modifiers() && !( e->key() >= 0x01000030 && e->key() <= 0x01000052 )) return; //Don't allow pure modifier shortcuts switch(e->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Meta: case Qt::Key_Shift: return; } //Literal Space to Space text if( e->key() == Qt::Key_Space) text += " Space" ; else if( e->key() == Qt::Key_Escape ) text += QString::fromUtf8(" ⎋"); else if( e->key() == Qt::Key_Backspace ) text += QString::fromUtf8(" ⌫"); else if( e->key() >= 0x01000030 && e->key() <= 0x01000052 ) text += QString( " F%1" ).arg(((int)e->key() - 0x0100002F) ); else text = text + " " + e->key(); m_key = e->nativeVirtualKey(); m_modifiers = e->modifiers(); setCurrentIndex( findText( text, Qt::MatchFixedString ) ); lineEdit()->setText( text ); } QString ShortcutEdit::textValue() const { return lineEdit()->text(); } ================================================ FILE: app/client/Widgets/ShortcutEdit.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SHORTCUTEDIT_H #define SHORTCUTEDIT_H #include class ShortcutEdit : public QComboBox { Q_OBJECT public: ShortcutEdit( QWidget* parent = 0 ); void setTextValue( QString str ); void keyPressEvent( QKeyEvent* e ); QString textValue() const; Qt::KeyboardModifiers modifiers() const; void setModifiers( Qt::KeyboardModifiers modifiers ); int key() const; void setKey( int key ); private: Qt::KeyboardModifiers m_modifiers; int m_key; }; #endif // SHORTCUTEDIT_H ================================================ FILE: app/client/Widgets/SideBar.cpp ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include "lib/unicorn/widgets/AvatarWidget.h" #include "../Services/AnalyticsService.h" #include "../Application.h" #include "SideBar.h" QAbstractButton* newButton( const QString& text, QButtonGroup* buttonGroup, QWidget* parent = 0 ) { QAbstractButton* pushButton = new QPushButton( parent ); pushButton->setText( text ); pushButton->setCheckable( true ); pushButton->setAutoExclusive( true ); pushButton->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ); pushButton->setAttribute( Qt::WA_LayoutUsesWidgetRect ); pushButton->setAttribute( Qt::WA_MacNoClickThrough ); buttonGroup->addButton( pushButton ); return pushButton; } SideBar::SideBar(QWidget *parent) :QFrame(parent), m_lastButton( 0 ) { QVBoxLayout* layout = new QVBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 0 ); m_buttonGroup = new QButtonGroup( this ); m_buttonGroup->setExclusive( true ); layout->addWidget( ui.nowPlaying = newButton( tr( "Now Playing" ), m_buttonGroup, this ), Qt::AlignHCenter ); ui.nowPlaying->setObjectName( "nowPlaying" ); layout->addWidget( ui.scrobbles = newButton( tr( "Scrobbles" ), m_buttonGroup, this ), Qt::AlignHCenter); ui.scrobbles->setObjectName( "scrobbles" ); layout->addWidget( ui.profile = newButton( tr( "Profile" ), m_buttonGroup, this ), Qt::AlignHCenter); ui.profile->setObjectName( "profile" ); layout->addWidget( ui.friends = newButton( tr( "Friends" ), m_buttonGroup, this ), Qt::AlignHCenter); ui.friends->setObjectName( "friends" ); layout->addWidget( ui.radio = newButton( tr( "Radio" ), m_buttonGroup, this ), Qt::AlignHCenter); ui.radio->setObjectName( "radio" ); layout->addStretch( 1 ); connect( m_buttonGroup, SIGNAL(buttonClicked(QAbstractButton*)), SLOT(onButtonClicked(QAbstractButton*))); ui.nowPlaying->click(); connect( aApp, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onSessionChanged(unicorn::Session)) ); onSessionChanged( aApp->currentSession() ); } void SideBar::onSessionChanged( const unicorn::Session& session ) { if ( session.isValid() ) { ui.radio->setVisible( session.subscriberRadio() ); if ( m_radioAction ) m_radioAction->setVisible( session.subscriberRadio() ); if ( !session.subscriberRadio() && m_buttonGroup->checkedButton() == ui.radio ) ui.nowPlaying->click(); } } QString SideBar::currentCategory() const { if ( m_buttonGroup->checkedButton() == ui.nowPlaying ) return NOW_PLAYING_CATEGORY; else if ( m_buttonGroup->checkedButton() == ui.scrobbles ) return SCROBBLES_CATEGORY; else if ( m_buttonGroup->checkedButton() == ui.profile ) return PROFILE_CATEGORY; else if ( m_buttonGroup->checkedButton() == ui.friends ) return FRIENDS_CATEGORY; else if ( m_buttonGroup->checkedButton() == ui.radio ) return RADIO_CATEGORY; return "Unknown"; } void SideBar::addToMenu( QMenu& menu ) { menu.addAction( ui.nowPlaying->text(), ui.nowPlaying, SLOT(click()), Qt::CTRL + Qt::Key_1); menu.addAction( ui.scrobbles->text(), ui.scrobbles, SLOT(click()), Qt::CTRL + Qt::Key_2); menu.addAction( ui.profile->text(), ui.profile, SLOT(click()), Qt::CTRL + Qt::Key_3); menu.addAction( ui.friends->text(), ui.friends, SLOT(click()), Qt::CTRL + Qt::Key_4); m_radioAction = menu.addAction( ui.radio->text(), ui.radio, SLOT(click()), Qt::CTRL + Qt::Key_5); m_radioAction->setVisible( !aApp->currentSession().isValid() || aApp->currentSession().subscriberRadio() ); menu.addSeparator(); menu.addAction( tr("Next Section"), this, SLOT(onDown()), Qt::CTRL + Qt::Key_BracketRight); menu.addAction( tr("Previous Section"), this, SLOT(onUp()), Qt::CTRL + Qt::Key_BracketLeft); } void SideBar::onUp() { if ( ui.nowPlaying->isChecked() ) { if ( !aApp->currentSession().isValid() || aApp->currentSession().subscriberRadio() ) ui.radio->click(); else ui.friends->click(); } else if ( ui.scrobbles->isChecked() ) ui.nowPlaying->click(); else if ( ui.profile->isChecked() ) ui.scrobbles->click(); else if ( ui.friends->isChecked() ) ui.profile->click(); else if ( ui.radio->isChecked() ) ui.friends->click(); } void SideBar::onDown() { if ( ui.nowPlaying->isChecked() ) ui.scrobbles->click(); else if ( ui.scrobbles->isChecked() ) ui.profile->click(); else if ( ui.profile->isChecked() ) ui.friends->click(); else if ( ui.friends->isChecked() ) { if ( !aApp->currentSession().isValid() || aApp->currentSession().subscriberRadio() ) ui.radio->click(); else ui.nowPlaying->click(); } else if ( ui.radio->isChecked() ) ui.nowPlaying->click(); } void SideBar::click( int index ) { qobject_cast( layout()->itemAt( index )->widget() )->click(); } void SideBar::onButtonClicked( QAbstractButton* button ) { int index = layout()->indexOf( button ); if ( button != m_lastButton ) { if ( button == ui.nowPlaying ) AnalyticsService::instance().sendPageView( NOW_PLAYING_CATEGORY ); // the scrobble tab is a bit more complicataed so it sends its own for now //else if ( button == ui.scrobbles ) AnalyticsService::instance().sendPageView( SCROBBLES_CATEGORY ); else if ( button == ui.profile ) AnalyticsService::instance().sendPageView( PROFILE_CATEGORY ); else if ( button == ui.friends ) AnalyticsService::instance().sendPageView( FRIENDS_CATEGORY ); else if ( button == ui.radio ) AnalyticsService::instance().sendPageView( RADIO_CATEGORY ); } m_lastButton = button; emit currentChanged( index ); } ================================================ FILE: app/client/Widgets/SideBar.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SIDEBAR_H #define SIDEBAR_H #include #include namespace lastfm { class User; } namespace unicorn { class Session; } class SideBar : public QFrame { Q_OBJECT private: struct { class QAbstractButton* nowPlaying; class QAbstractButton* scrobbles; class QAbstractButton* profile; class QAbstractButton* friends; class QAbstractButton* radio; class QAbstractButton* sash; } ui; public: explicit SideBar(QWidget *parent = 0); void addToMenu( class QMenu& menu ); void click( int index ); QString currentCategory() const; signals: void currentChanged( int index ); public slots: void onButtonClicked( class QAbstractButton* button ); void onUp(); void onDown(); private slots: void onSessionChanged( const unicorn::Session& session ); private: class QButtonGroup* m_buttonGroup; class QAbstractButton* m_lastButton; QPointer m_radioAction; }; #endif // SIDEBAR_H ================================================ FILE: app/client/Widgets/SimilarArtistWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "SimilarArtistWidget.h" SimilarArtistWidget::SimilarArtistWidget(QWidget *parent) : HttpImageWidget(parent) { } void SimilarArtistWidget::setArtist( const QString& artist ) { m_artist = artist; } void SimilarArtistWidget::paintEvent( QPaintEvent* event ) { HttpImageWidget::paintEvent( event ); QPainter p; p.begin( this ); QTextOption to; to.setAlignment( Qt::AlignBottom | Qt::AlignLeft ); QFontMetrics fm( font() ); QRect rect = contentsRect().adjusted( -3, 0, 13, 22 ); p.drawText( rect, fm.elidedText( m_artist, Qt::ElideRight, rect.width() ), to ); p.end(); } ================================================ FILE: app/client/Widgets/SimilarArtistWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef SIMILARARTISTWIDGET_H #define SIMILARARTISTWIDGET_H #include "lib/unicorn/widgets/HttpImageWidget.h" class SimilarArtistWidget : public HttpImageWidget { Q_OBJECT public: explicit SimilarArtistWidget(QWidget *parent = 0); void setArtist( const QString& artist ); private: void paintEvent(QPaintEvent * event); private: QString m_artist; }; #endif // SIMILARARTISTWIDGET_H ================================================ FILE: app/client/Widgets/StatusBar.cpp ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole and Micahel Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include "lib/unicorn/widgets/Label.h" #include "StatusBar.h" #include "../Services/ScrobbleService.h" #include "../MediaDevices/DeviceScrobbler.h" #include "../Application.h" StatusBar::StatusBar( QWidget* parent ) :QStatusBar( parent ), m_online( false ) { addWidget( ui.widget = new QFrame( this) ); QHBoxLayout* widgetLayout = new QHBoxLayout( ui.widget ); widgetLayout->setContentsMargins( 0, 0, 0, 0 ); widgetLayout->setSpacing( 0 ); widgetLayout->addWidget( ui.cog = new QPushButton(this), 0, Qt::AlignVCenter); ui.cog->setObjectName( "cog" ); ui.cog->setAttribute( Qt::WA_LayoutUsesWidgetRect ); widgetLayout->addWidget( ui.message = new unicorn::Label(this), 1, Qt::AlignVCenter); ui.message->setObjectName( "message" ); ui.message->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui.message->setAlignment( Qt::AlignRight | Qt::AlignVCenter ); setStatus(); addPermanentWidget( ui.scrobbleWidget = new QFrame( this )); ui.scrobbleWidget->setObjectName("scrobbleWidget"); QHBoxLayout* scrobbleWidgetLayout = new QHBoxLayout( ui.scrobbleWidget ); scrobbleWidgetLayout->setContentsMargins( 50, 0, 0, 0 ); scrobbleWidgetLayout->setSpacing( 0 ); bool isScrobblingOff = unicorn::UserSettings().value("scrobblingOn", true).toBool(); scrobbleWidgetLayout->addWidget( ui.scrobbleIcon = new QLabel(this) ); ui.scrobbleIcon->setObjectName("scrobbleIcon"); ui.scrobbleIcon->setHidden(isScrobblingOff); scrobbleWidgetLayout->addWidget( ui.scrobbleMessage = new unicorn::Label(this), 1, Qt::AlignVCenter); ui.scrobbleMessage->setObjectName("scrobbleMessage"); ui.scrobbleMessage->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui.scrobbleMessage->setAlignment(Qt::AlignRight | Qt::AlignVCenter ); ui.scrobbleMessage->setStyleSheet("QLabel { color : red; }"); ui.scrobbleMessage->setText( tr("Scrobbling is off") ); ui.scrobbleMessage->setHidden(isScrobblingOff); aApp->isInternetConnectionUp() ? onConnectionUp() : onConnectionDown(); connect( aApp, SIGNAL( internetConnectionDown() ), SLOT( onConnectionDown() ) ); connect( aApp, SIGNAL( internetConnectionUp() ), SLOT( onConnectionUp() ) ); connect( this, SIGNAL(messageChanged(QString)), SLOT(onMessagedChanged(QString))); connect( aApp, SIGNAL(scrobbleToggled(bool)), ui.scrobbleMessage, SLOT(setHidden(bool))); connect( aApp, SIGNAL(scrobbleToggled(bool)), ui.scrobbleIcon, SLOT(setHidden(bool))); connect( aApp, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onSessionChanged(unicorn::Session))); connect( ui.cog, SIGNAL(clicked()), aApp, SLOT(onPrefsTriggered())); } void StatusBar::onSessionChanged( const unicorn::Session& /*session*/ ) { setStatus(); } void StatusBar::onMessagedChanged( const QString& message ) { if ( message.isEmpty() ) setStatus(); } void StatusBar::setStatus() { ui.message->setText( tr("%1 (%2)").arg( lastfm::ws::Username, m_online ? tr( "Online" ) : tr( "Offline" ) )); } void StatusBar::onConnectionUp() { m_online = true; setStatus(); } void StatusBar::onConnectionDown() { m_online = false; setStatus(); } ================================================ FILE: app/client/Widgets/StatusBar.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole and Micahel Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include class QLabel; namespace Phonon { class VolumeSlider; } namespace unicorn { class Session; } namespace unicorn { class Label; } class StatusBar : public QStatusBar { Q_OBJECT public: StatusBar( QWidget* parent = 0); private slots: void onMessagedChanged( const QString& text ); void setStatus(); void onConnectionUp(); void onConnectionDown(); void onSessionChanged( const unicorn::Session& session ); private: struct { class QFrame* widget; class QPushButton* cog; unicorn::Label* message; class QFrame* scrobbleWidget; unicorn::Label* scrobbleMessage; class QLabel* scrobbleIcon; } ui; bool m_online; }; ================================================ FILE: app/client/Widgets/TagWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include "../Application.h" #include "lib/unicorn/widgets/Label.h" #include "lib/unicorn/DesktopServices.h" #include "../Services/AnalyticsService.h" #include "TagWidget.h" TagWidget::TagWidget( const QString& tag, const QString& url, QWidget *parent ) :QPushButton( tag, parent ), m_url( url ), m_hovered( false ), m_left_rest( ":/meta_tag_LEFT_REST.png" ), m_middle_rest( ":/meta_tag_MIDDLE_REST.png" ), m_right_rest( ":/meta_tag_RIGHT_REST.png" ), m_left_hover( ":/meta_tag_LEFT_HOVER.png" ), m_middle_hover( ":/meta_tag_MIDDLE_HOVER.png" ), m_right_hover( ":/meta_tag_RIGHT_HOVER.png" ) { connect( this, SIGNAL(clicked()), SLOT(onClicked())); setAttribute( Qt::WA_LayoutUsesWidgetRect ); setAttribute( Qt::WA_Hover ); this->setCursor( Qt::PointingHandCursor ); } bool TagWidget::event( QEvent* e ) { switch ( e->type()) { case QEvent::HoverEnter: m_hovered = true; update(); break; case QEvent::HoverLeave: m_hovered = false; update(); break; default: break; } return QPushButton::event( e ); } void TagWidget::paintEvent( QPaintEvent* /*event*/ ) { QPainter p( this ); QFontMetrics fm = fontMetrics(); QRect middleRect = rect(); middleRect.adjust( m_left_rest.width(), 0, 0, 0 ); middleRect.setWidth( fm.width( text() ) + 8 ); if ( !m_hovered ) { p.drawPixmap( rect().topLeft(), m_left_rest ); p.drawPixmap( middleRect, m_middle_rest ); p.drawPixmap( QPoint( m_left_rest.width() + middleRect.width(), 0 ), m_right_rest ); } else { p.drawPixmap( rect().topLeft(), m_left_hover ); p.drawPixmap( middleRect, m_middle_hover ); p.drawPixmap( QPoint( m_left_hover.width() + middleRect.width(), 0 ), m_right_hover ); } QTextOption to; to.setAlignment( Qt::AlignCenter ); p.drawText( middleRect.adjusted( 0, 0, 0, -1 ), text(), to ); } QSize TagWidget::sizeHint() const { QFontMetrics fm = fontMetrics(); QSize size = QPushButton::sizeHint(); size.setWidth( m_left_rest.width() + m_right_rest.width() + fm.width( text() ) + 8 ); size.setHeight( 19 ); return size; } void TagWidget::onClicked() { unicorn::DesktopServices::openUrl( m_url ); AnalyticsService::instance().sendEvent( aApp->currentCategory(), TAG_CLICKED, "TagButtonPressed"); } ================================================ FILE: app/client/Widgets/TagWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TAGWIDGET_H #define TAGWIDGET_H #include #include #include #include class TagWidget : public QPushButton { Q_OBJECT public: explicit TagWidget( const QString& tag, const QString& url, QWidget *parent = 0); private: void paintEvent( QPaintEvent* event ); QSize sizeHint() const; bool event( QEvent* e ); private slots: void onClicked(); private: QUrl m_url; bool m_hovered; QPixmap m_left_rest; QPixmap m_middle_rest; QPixmap m_right_rest; QPixmap m_left_hover; QPixmap m_middle_hover; QPixmap m_right_hover; }; #endif // TAGWIDGET_H ================================================ FILE: app/client/Widgets/TitleBar.cpp ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole and Micahel Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include "lib/unicorn/widgets/GhostWidget.h" #include "TitleBar.h" #include "../Application.h" TitleBar::TitleBar( QWidget* parent ) :QFrame( parent ) { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 0 ); QPushButton* pb = new QPushButton( "Close" ); pb->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); pb->setCheckable( true ); connect( pb, SIGNAL(clicked()), SIGNAL( closeClicked())); #ifdef Q_OS_MAC pb->setShortcut( Qt::CTRL + Qt::Key_H ); layout->addWidget( pb ); #else GhostWidget* ghost = new GhostWidget( this ); ghost->setOrigin( pb ); layout->addWidget( ghost ); #endif layout->addStretch( 1 ); QLabel* title = new QLabel( QApplication::applicationName(), this ); title->setMargin( 0 ); layout->addWidget( title, 0 ); layout->addStretch( 1 ); #ifndef Q_OS_MAC layout->addWidget( pb ); #else GhostWidget* gw; layout->addWidget( gw = new GhostWidget( this ) ); gw->setOrigin( pb ); #endif } ================================================ FILE: app/client/Widgets/TitleBar.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole and Micahel Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include class QLabel; class TitleBar : public QFrame { Q_OBJECT public: TitleBar( QWidget* parent = 0 ); signals: void closeClicked(); }; ================================================ FILE: app/client/Widgets/TrackWidget.cpp ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include "../Application.h" #include "../Services/ScrobbleService/ScrobbleService.h" #include "../Services/AnalyticsService.h" #include "TrackWidget.h" #include "ui_TrackWidget.h" TrackWidget::TrackWidget( Track& track, QWidget *parent ) :QPushButton(parent), ui( new Ui::TrackWidget ), m_nowPlaying( false ), m_triedFetchAlbumArt( false ) { ui->setupUi( this ); m_spinner = new QLabel( this ); m_spinner->setAlignment( Qt::AlignHCenter | Qt::AlignVCenter ); m_spinner->hide(); m_movie = new QMovie( ":/icon_eq.gif", "GIF", this ); m_movie->setCacheMode( QMovie::CacheAll ); ui->equaliser->setMovie( m_movie ); ui->buttonLayout->setAlignment( ui->love, Qt::AlignTop ); ui->buttonLayout->setAlignment( ui->tag, Qt::AlignTop ); ui->buttonLayout->setAlignment( ui->share, Qt::AlignTop ); ui->buttonLayout->setAlignment( ui->buy, Qt::AlignTop ); ui->albumArt->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->love->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->tag->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->share->setAttribute( Qt::WA_LayoutUsesWidgetRect ); ui->buy->setAttribute( Qt::WA_LayoutUsesWidgetRect ); setAttribute( Qt::WA_MacNoClickThrough ); ui->albumArt->setAttribute( Qt::WA_MacNoClickThrough ); ui->love->setAttribute( Qt::WA_MacNoClickThrough ); ui->tag->setAttribute( Qt::WA_MacNoClickThrough ); ui->share->setAttribute( Qt::WA_MacNoClickThrough ); ui->buy->setAttribute( Qt::WA_MacNoClickThrough ); connect( ui->love, SIGNAL(clicked(bool)), SLOT(onLoveClicked(bool))); connect( ui->tag, SIGNAL(clicked()), SLOT(onTagClicked())); connect( ui->share, SIGNAL(clicked()), SLOT(onShareClicked())); connect( ui->buy, SIGNAL(clicked()), SLOT(onBuyClicked())); connect( this, SIGNAL(clicked()), SLOT(onClicked()) ); setTrack( track ); } TrackWidget::~TrackWidget() { delete ui; } QSize TrackWidget::sizeHint() const { QSize sizeHint = ui->frame->sizeHint(); sizeHint.setWidth( QPushButton::sizeHint().width() ); return sizeHint; } void TrackWidget::onClicked() { emit clicked( *this ); } void TrackWidget::contextMenuEvent( QContextMenuEvent* event ) { QMenu* contextMenu = new QMenu( this ); if ( ! m_nowPlaying ) { contextMenu->addAction( tr( "Delete this scrobble from your profile" ), this, SLOT(onRemoveClicked())); contextMenu->addSeparator(); } if ( contextMenu->actions().count() ) contextMenu->exec( event->globalPos() ); } void TrackWidget::startSpinner() { m_spinner->setGeometry( rect() ); if ( !m_spinnerMovie ) { m_spinnerMovie = new QMovie( ":/loading_meta.gif", "GIF", this ); m_spinnerMovie->setCacheMode( QMovie::CacheAll ); m_spinner->setMovie( m_spinnerMovie ); } setEnabled( false ); m_spinnerMovie->start(); m_spinner->show(); } void TrackWidget::clearSpinner() { if ( !m_spinnerMovie ) { m_spinnerMovie = new QMovie( ":/loading_meta.gif", "GIF", this ); m_spinnerMovie->setCacheMode( QMovie::CacheAll ); m_spinner->setMovie( m_spinnerMovie ); } setEnabled( true ); m_spinnerMovie->stop(); m_spinner->hide(); } void TrackWidget::showEvent(QShowEvent *) { if ( m_nowPlaying ) m_movie->start(); fetchAlbumArt(); } void TrackWidget::hideEvent( QHideEvent * ) { m_movie->stop(); } void TrackWidget::fetchAlbumArt() { if ( isVisible() && !m_triedFetchAlbumArt ) { m_triedFetchAlbumArt = true; delete m_trackImageFetcher; m_trackImageFetcher = new TrackImageFetcher( m_track, Track::MediumImage ); connect( m_trackImageFetcher, SIGNAL(finished(QPixmap)), ui->albumArt, SLOT(setPixmap(QPixmap)) ); m_trackImageFetcher->startAlbum(); } } void TrackWidget::resizeEvent(QResizeEvent *) { setTrackTitleWidth(); } void TrackWidget::setTrackTitleWidth() { int width = qMin( ui->trackTitleFrame->width(), ui->trackTitle->fontMetrics().width( ui->trackTitle->text() ) + 1 ); ui->trackTitle->setFixedWidth( width ); } void TrackWidget::update( const lastfm::Track& track ) { // we're getting an update from a track fetched from user.getRecentTracks MutableTrack mt( m_track ); mt.setScrobbleStatus( Track::Submitted ); // it's definitely been scrobbled mt.setLoved( track.isLoved() ); // make sure the love state is consistent with Last.fm } void TrackWidget::setTrack( lastfm::Track& track ) { disconnect( m_track.signalProxy(), 0, this, 0 ); m_track = track; connect( m_track.signalProxy(), SIGNAL(loveToggled(bool)), SLOT(onLoveToggled(bool)) ); connect( m_track.signalProxy(), SIGNAL(scrobbleStatusChanged(short)), SLOT(onScrobbleStatusChanged())); connect( m_track.signalProxy(), SIGNAL(corrected(QString)), SLOT(onCorrected(QString))); m_movie->stop(); ui->equaliser->hide(); setTrackDetails(); ui->albumArt->setPixmap( QPixmap( ":/meta_album_no_art.png" ) ); ui->albumArt->setHref( track.www() ); m_triedFetchAlbumArt = false; fetchAlbumArt(); } void TrackWidget::setTrackDetails() { ui->trackTitle->setText( m_track.title() ); ui->artist->setText( m_track.artist().name() ); if ( m_timestampTimer ) m_timestampTimer->stop(); if ( m_track.scrobbleStatus() == lastfm::Track::Cached && !m_nowPlaying ) ui->timestamp->setText( tr( "Cached" ) ); else if ( m_track.scrobbleStatus() == lastfm::Track::Error && !m_nowPlaying ) ui->timestamp->setText( tr( "Error: %1" ).arg( m_track.scrobbleErrorText() ) ); else updateTimestamp(); ui->love->setChecked( m_track.isLoved() ); setTrackTitleWidth(); } void TrackWidget::onLoveToggled( bool loved ) { ui->love->setChecked( loved ); } void TrackWidget::onScrobbleStatusChanged() { setTrackDetails(); } void TrackWidget::onCorrected( QString /*correction*/ ) { setTrackDetails(); } lastfm::Track TrackWidget::track() const { return m_track; } void TrackWidget::onLoveClicked( bool loved ) { if ( loved ) { MutableTrack( m_track ).love(); AnalyticsService::instance().sendEvent( aApp->currentCategory(), LOVE_TRACK, "TrackLoved"); } else { MutableTrack( m_track ).unlove(); AnalyticsService::instance().sendEvent( aApp->currentCategory(), LOVE_TRACK, "TrackUnLoved"); } } void TrackWidget::onTagClicked() { TagDialog* td = new TagDialog( m_track, window() ); td->raise(); td->show(); td->activateWindow(); AnalyticsService::instance().sendEvent( aApp->currentCategory(), TAG_CLICKED, "TagButtonPressed"); } void TrackWidget::onShareClicked() { QMenu* shareMenu = new QMenu( this ); shareMenu->addAction( tr( "Share on Last.fm" ), this, SLOT(onShareLastFm()) ); shareMenu->addAction( tr( "Share on Twitter" ), this, SLOT(onShareTwitter()) ); shareMenu->addAction( tr( "Share on Facebook" ), this, SLOT(onShareFacebook()) ); shareMenu->exec( cursor().pos() ); } void TrackWidget::onBuyClicked() { if ( !ui->buy->menu() ) { // show the buy links please! QString country = aApp->currentSession().user().country(); connect( m_track.getBuyLinks( country ), SIGNAL(finished()), SLOT(onGotBuyLinks())); } } void TrackWidget::onRemoveClicked() { connect( lastfm::Library::removeScrobble( m_track ), SIGNAL(finished()), SLOT(onRemovedScrobble())); } void TrackWidget::onRemovedScrobble() { lastfm::XmlQuery lfm; if ( lfm.parse( static_cast( sender() ) ) ) { emit removed(); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } } void TrackWidget::setNowPlaying( bool nowPlaying ) { m_nowPlaying = nowPlaying; updateTimestamp(); } void TrackWidget::updateTimestamp() { if ( !m_timestampTimer ) { m_timestampTimer = new QTimer( this ); m_timestampTimer->setSingleShot( true ); connect( m_timestampTimer, SIGNAL(timeout()), SLOT(updateTimestamp())); } if ( m_nowPlaying ) { if ( isVisible() ) m_movie->start(); ui->equaliser->show(); ui->timestamp->setText( tr( "Now listening" ) ); ui->timestamp->setToolTip( "" ); } else { m_movie->stop(); ui->equaliser->hide(); unicorn::Label::prettyTime( *ui->timestamp, m_track.timestamp(), m_timestampTimer ); } } void TrackWidget::onGotBuyLinks() { if ( !ui->buy->menu() ) { // make sure we don't process this again if the button was clicked twice XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { bool thingsToBuy = false; QMenu* menu = new QMenu( this ); menu->addAction( tr("Downloads") )->setEnabled( false ); // USD EUR GBP foreach ( const XmlQuery& affiliation, lfm["affiliations"]["downloads"].children( "affiliation" ) ) { bool isSearch = affiliation["isSearch"].text() == "1"; QAction* buyAction = 0; if ( isSearch ) buyAction = menu->addAction( tr("Search on %1").arg( affiliation["supplierName"].text() ) ); else buyAction = menu->addAction( tr("Buy on %1 %2").arg( affiliation["supplierName"].text(), unicorn::Label::price( affiliation["price"]["amount"].text(), affiliation["price"]["currency"].text() ) ) ); buyAction->setData( affiliation["buyLink"].text() ); thingsToBuy = true; } menu->addSeparator(); menu->addAction( tr("Physical") )->setEnabled( false ); foreach ( const XmlQuery& affiliation, lfm["affiliations"]["physicals"].children( "affiliation" ) ) { bool isSearch = affiliation["isSearch"].text() == "1"; QAction* buyAction = 0; if ( isSearch ) buyAction = menu->addAction( tr("Search on %1").arg( affiliation["supplierName"].text() ) ); else buyAction = menu->addAction( tr("Buy on %1 %2").arg( affiliation["supplierName"].text(), unicorn::Label::price( affiliation["price"]["amount"].text(), affiliation["price"]["currency"].text() ) ) ); buyAction->setData( affiliation["buyLink"].text() ); thingsToBuy = true; } ui->buy->setMenu( menu ); connect( menu, SIGNAL(triggered(QAction*)), SLOT(onBuyActionTriggered(QAction*)) ); ui->buy->click(); } else { // TODO: what happens when we fail? qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } } } void TrackWidget::onBuyActionTriggered( QAction* buyAction ) { unicorn::DesktopServices::openUrl( buyAction->data().toString() ); AnalyticsService::instance().sendEvent( aApp->currentCategory(), BUY_CLICKED, "BuyButtonPressed" ); } void TrackWidget::onShareLastFm() { ShareDialog* sd = new ShareDialog( m_track, window() ); sd->raise(); sd->show(); sd->activateWindow(); AnalyticsService::instance().sendEvent( aApp->currentCategory(), SHARE_CLICKED, "LastfmShare" ); } void TrackWidget::onShareTwitter() { ShareDialog::shareTwitter( m_track ); AnalyticsService::instance().sendEvent( aApp->currentCategory(), SHARE_CLICKED, "TwitterShare" ); } void TrackWidget::onShareFacebook() { ShareDialog::shareFacebook( m_track ); AnalyticsService::instance().sendEvent( aApp->currentCategory(), SHARE_CLICKED, "FacebookShare" ); } ================================================ FILE: app/client/Widgets/TrackWidget.h ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TRACKWIDGET_H #define TRACKWIDGET_H #include #include namespace Ui { class TrackWidget; } class QMovie; class TrackImageFetcher; class TrackWidget : public QPushButton { Q_OBJECT public: explicit TrackWidget( Track& track, QWidget *parent = 0 ); ~TrackWidget(); void update( const lastfm::Track& track ); void setTrack( lastfm::Track& track ); lastfm::Track track() const; void setNowPlaying( bool nowPlaying ); public slots: void startSpinner(); void clearSpinner(); signals: void clicked( TrackWidget& trackWidget ); void removed(); private slots: void onClicked(); void onLoveClicked( bool loved ); void onTagClicked(); void onShareClicked(); void onBuyClicked(); void onGotBuyLinks(); void onRemoveClicked(); void onRemovedScrobble(); void onBuyActionTriggered( QAction* buyAction ); void onShareLastFm(); void onShareTwitter(); void onShareFacebook(); // signals from the track void onLoveToggled( bool loved ); void onScrobbleStatusChanged(); void onCorrected( QString correction ); void updateTimestamp(); public: QSize sizeHint() const; private: void setTrackDetails(); void setTrackTitleWidth(); void resizeEvent(QResizeEvent *); void showEvent(QShowEvent *); void hideEvent(QHideEvent *); void contextMenuEvent( QContextMenuEvent* event ); void fetchAlbumArt(); private: Ui::TrackWidget* ui; lastfm::Track m_track; QPointer m_movie; QPointer m_timestampTimer; QPointer m_trackImageFetcher; bool m_nowPlaying; bool m_triedFetchAlbumArt; QPointer m_spinnerMovie; class QLabel* m_spinner; }; #endif // TRACKWIDGET_H ================================================ FILE: app/client/Widgets/TrackWidget.ui ================================================ TrackWidget 0 0 526 119 0 0 Form 0 0 QFrame::NoFrame QFrame::Raised 0 0 TextLabel true 0 0 0 0 0 0 0 Title Qt::Horizontal QSizePolicy::Expanding 0 20 Artist Qt::Vertical 20 0 0 e Timestamp 5 Love Love true Tag Tag Share Share Buy Buy unicorn::Label QLabel
    lib/unicorn/widgets/Label.h
    HttpImageWidget QLabel
    lib/unicorn/widgets/HttpImageWidget.h
    ================================================ FILE: app/client/Widgets/WidgetTextObject.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include "WidgetTextObject.h" WidgetTextObject::WidgetTextObject() :kMargin( 10, 5 ) { } QSizeF WidgetTextObject::intrinsicSize(QTextDocument*, int /*posInDocument*/, const QTextFormat& format) { QWidget* widget = qVariantValue(format.property(1)); return QSizeF( widget->size() + kMargin ); } void WidgetTextObject::drawObject(QPainter *painter, const QRectF &rect, QTextDocument * /*doc*/, int /*posInDocument*/, const QTextFormat &format) { QWidget* widget = qVariantValue(format.property( 1 )); widget->render( painter, QPoint( 0, 0 )); //Adjusted to allow for the margin QRect contentsRect = rect.toRect().adjusted(0, 0, -kMargin.width(), -kMargin.height()); m_widgetRects[widget] = contentsRect; } QWidget* WidgetTextObject::widgetAtPoint( const QPoint& p ) { QMapIterator i(m_widgetRects); while (i.hasNext()) { i.next(); if( i.value().contains(p)) return i.key(); } return 0; } QRectF WidgetTextObject::widgetRect( QWidget* w ) { return m_widgetRects[w]; } ================================================ FILE: app/client/Widgets/WidgetTextObject.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef WIDGETTEXTOBJECT_H #define WIDGETTEXTOBJECT_H #include #include /** An embeddable widget text object wrapper */ class WidgetTextObject : public QObject, QTextObjectInterface { Q_OBJECT Q_INTERFACES(QTextObjectInterface) const QSize kMargin; public: WidgetTextObject(); QSizeF intrinsicSize(QTextDocument*, int /*posInDocument*/, const QTextFormat& format); void drawObject(QPainter *painter, const QRectF &rect, QTextDocument * /*doc*/, int /*posInDocument*/, const QTextFormat &format); QWidget* widgetAtPoint( const QPoint& p ); QRectF widgetRect( QWidget* w ); protected: QMap m_widgetRects; }; #endif // WIDGETTEXTOBJECT_H ================================================ FILE: app/client/Wizard/AccessPage.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include "lib/unicorn/LoginProcess.h" #include "lib/unicorn/UnicornSession.h" #include "../Application.h" #include "FirstRunWizard.h" #include "AccessPage.h" AccessPage::AccessPage() :m_valid( false ), m_gotUserInfo( false ) { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 20 ); layout->addWidget( ui.image = new QLabel(), 1, Qt::AlignTop | Qt::AlignHCenter ); ui.image->setObjectName( "image" ); QVBoxLayout* rL = new QVBoxLayout( this ); rL->setContentsMargins( 0, 0, 0, 0 ); rL->setSpacing( 20 ); layout->addLayout( rL, 1 ); rL->addWidget( ui.description = new QLabel( "" ), 1, Qt::AlignTop); ui.description->setObjectName( "description" ); ui.description->setWordWrap( true ); rL->addWidget( ui.authUrl = new QTextEdit( "" ) ); ui.authUrl->setTextInteractionFlags( Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard ); ui.authUrl->setObjectName( "authUrl" ); } void AccessPage::initializePage() { setTitle( tr( "We're waiting for you to connect to Last.fm" )); ui.description->setText( tr( "

    Please click the Yes, Allow Access button in your web browser to connect your Last.fm account to the Last.fm Desktop App.

    " "

    If you haven't connected because you closed the browser window or you clicked cancel, please try again.

    " ) ); wizard()->setCommitPage( true ); if ( wizard()->canGoBack() ) wizard()->setButton( FirstRunWizard::BackButton, tr( "<< Back" ) ); wizard()->setButton( FirstRunWizard::NextButton, tr( "Continue" ) ); QAbstractButton* custom = wizard()->setButton( FirstRunWizard::CustomButton, tr( "Try Again" ) ); connect( custom, SIGNAL(clicked()), SLOT(tryAgain())); disconnect( aApp, SIGNAL(sessionChanged(unicorn::Session)), this, SLOT(onSessionChanged(unicorn::Session))); disconnect( aApp, SIGNAL(gotUserInfo(lastfm::User)), this, SLOT(onGotUserInfo(lastfm::User))); connect( aApp, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onSessionChanged(unicorn::Session))); connect( aApp, SIGNAL(gotUserInfo(lastfm::User)), SLOT(onGotUserInfo(lastfm::User))); tryAgain(); } void AccessPage::tryAgain() { if ( !m_loginProcess ) { m_loginProcess = new unicorn::LoginProcess( this ); connect( m_loginProcess, SIGNAL(authUrlChanged(QString)), SLOT(onAuthUrlChanged(QString))); } m_loginProcess->authenticate(); // disable the try again and coninue buttons wizard()->setButton( FirstRunWizard::NextButton, tr( "Continue" ) )->setEnabled( false ); wizard()->setButton( FirstRunWizard::CustomButton, tr( "Try Again" ) )->setEnabled( false ); } void AccessPage::onAuthUrlChanged( const QString& authUrl ) { ui.description->setText( tr( "

    Please click the Yes, Allow Access button in your web browser to connect your Last.fm account to the Last.fm Desktop App.

    " "

    If you haven't connected because you closed the browser window or you clicked cancel, please try again.

    " ) + tr( "

    If your web browser didn't open, copy and paste the link below into your address bar.

    " ) ); ui.authUrl->setText( authUrl ); wizard()->setButton( FirstRunWizard::CustomButton, tr( "Try Again" ) )->setEnabled( true ); if ( !authUrl.isEmpty() ) wizard()->setButton( FirstRunWizard::NextButton, tr( "Continue" ) )->setEnabled( true ); } void AccessPage::onGotUserInfo( const lastfm::User& /*user*/ ) { m_gotUserInfo = true; checkComplete(); } void AccessPage::onSessionChanged( const unicorn::Session& session ) { if ( session.isValid() ) checkComplete(); } void AccessPage::checkComplete() { if ( aApp->currentSession().isValid() && m_gotUserInfo && !m_valid ) { disconnect( aApp, SIGNAL(sessionChanged(unicorn::Session)), this, SLOT(onSessionChanged(unicorn::Session))); disconnect( aApp, SIGNAL(gotUserInfo(lastfm::User)), this, SLOT(onGotUserInfo(lastfm::User))); // we've now got both the session info and the user info m_valid = true; wizard()->showWelcome(); wizard()->next(); m_loginProcess->deleteLater(); } } void AccessPage::cleanupPage() { ui.description->clear(); ui.authUrl->clear(); } bool AccessPage::validatePage() { if ( m_valid ) return true; // try to get the session key! m_loginProcess->getSession(); return false; } ================================================ FILE: app/client/Wizard/AccessPage.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef ACCESS_PAGE_H #define ACCESS_PAGE_H #include "WizardPage.h" #include namespace lastfm { class User; } namespace unicorn{ class LoginProcess; class Session; } class AccessPage : public WizardPage { Q_OBJECT private: struct { class QLabel* image; class QLabel* description; class QTextEdit* authUrl; } ui; public: AccessPage(); void initializePage(); bool validatePage(); void cleanupPage(); private: void checkComplete(); private slots: void tryAgain(); void onGotUserInfo( const lastfm::User& user ); void onSessionChanged( const unicorn::Session& session ); void onAuthUrlChanged( const QString& authUrl ); protected: QPointer m_loginProcess; bool m_valid; bool m_gotUserInfo; }; #endif //AUTH_IN_PROGRESS_PAGE_H ================================================ FILE: app/client/Wizard/BootstrapInProgressPage.h ================================================ /* Copyright 2010-2011 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef BOOTSTRAP_IN_PROGRESS_PAGE_H #define BOOTSTRAP_IN_PROGRESS_PAGE_H #include #include #ifdef Q_OS_WIN #include "lib/unicorn/Updater/PluginList.h" #endif class BootstrapInProgressPage : public QWizardPage { Q_OBJECT public: BootstrapInProgressPage( QWizard* p ); void initializePage(); virtual bool isComplete() const; protected slots: void startBootstrap(); void onTrackProcessed( int percentage, const Track& ); void onPercentageUpload( int percentage ); void onBootstrapDone( int ); void onTrackStarted( const Track& ); protected: class QProgressBar* m_progressBar; class QLabel* m_label; bool m_isComplete; class AbstractBootstrapper* m_bootstrapper; #ifdef Q_OS_WIN PluginList m_pluginList; #endif }; #endif //BOOTSTRAP_IN_PROGRESS_PAGE_H ================================================ FILE: app/client/Wizard/BootstrapPage.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/dialogs/CloseAppsDialog.h" #include "FirstRunWizard.h" #include "BootstrapPage.h" BootstrapPage::BootstrapPage() { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 20 ); m_pluginsLayout = new QVBoxLayout(); m_pluginsLayout->setContentsMargins( 0, 0, 0, 0 ); m_pluginsLayout->setSpacing( 6 ); layout->addLayout( m_pluginsLayout ); layout->addWidget( ui.description = new QLabel( tr( "

    For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.

    " "

    Please select your preferred media player and click Start Import

    " ) ), 0, Qt::AlignTop); ui.description->setObjectName( "description" ); ui.description->setWordWrap( true ); } void BootstrapPage::playerSelected() { m_playerId = qobject_cast(sender())->objectName(); emit playerChanged(); } bool BootstrapPage::validatePage() { #ifdef Q_OS_WIN // make sure the user has closed their chosen plugin (except iTunes) unicorn::IPluginInfo* plugin = wizard()->pluginList()->pluginById( m_playerId ); if ( plugin->bootstrapType() == unicorn::IPluginInfo::PluginBootstrap ) { QList plugins; plugins << plugin; unicorn::CloseAppsDialog* closeApps = new unicorn::CloseAppsDialog( plugins, this ); if ( closeApps->result() != QDialog::Accepted ) closeApps->exec(); else closeApps->deleteLater(); if ( closeApps->result() == QDialog::Accepted ) aApp->startBootstrap( m_playerId ); else { // The user didn't close their media players QMessageBoxBuilder( this ).setTitle( tr( "Your plugins haven't been installed" ) ) .setText( tr( "You can install them later through the file menu" ) ) .setButtons( QMessageBox::Ok ) .exec(); } } else #endif aApp->startBootstrap( m_playerId ); // once you start importing you can't go back // if they didn't click "Start Import" they won't get here // so we allow them to go back and bootstrap wizard()->setCommitPage( true ); return true; } void BootstrapPage::initializePage() { QRadioButton* rb; #ifdef Q_OS_WIN QList plugins = wizard()->pluginList()->bootstrappablePlugins(); bool first = true; foreach ( unicorn::IPluginInfo* plugin, plugins ) { m_pluginsLayout->addWidget( rb = new QRadioButton( plugin->name())); rb->setObjectName( plugin->id() ); rb->style()->polish( rb ); connect( rb, SIGNAL(clicked()), SLOT(playerSelected())); if ( first ) { rb->setChecked( true ); m_playerId = plugin->id(); } first = false; } #else m_pluginsLayout->addWidget( rb = new QRadioButton( tr( "iTunes" ) ) ); rb->setChecked( true ); m_playerId = "osx"; #endif m_pluginsLayout->addStretch(); setTitle( tr( "Now let's import your listening history" ) ); wizard()->setButton( FirstRunWizard::NextButton, tr( "Start Import" ) ); if ( wizard()->canGoBack() ) wizard()->setButton( FirstRunWizard::BackButton, tr( "<< Back" ) ); wizard()->setButton( FirstRunWizard::SkipButton, tr( "Skip >>" ) ); } void BootstrapPage::cleanupPage() { while ( m_pluginsLayout->count() > 0 ) m_pluginsLayout->takeAt( 0 )->widget()->deleteLater(); } ================================================ FILE: app/client/Wizard/BootstrapPage.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef BOOTSTRAP_WIZARD_H #define BOOTSTRAP_WIZARD_H #include "WizardPage.h" #include #include #include #include #include #include #ifdef Q_OS_WIN32 #include "lib/unicorn/plugins/PluginList.h" #endif #include "../Application.h" class BootstrapPage: public WizardPage { Q_OBJECT private: Q_PROPERTY( QString playerId READ playerId WRITE setPlayerId ) struct { class QLabel* description; } ui; public: BootstrapPage(); QString playerId() const { return m_playerId; } void setPlayerId( const QString& playerId ) { m_playerId = playerId; } private: void initializePage(); bool validatePage(); void cleanupPage(); signals: void playerChanged(); private slots: void playerSelected(); protected: QString m_playerId; QVBoxLayout* m_pluginsLayout; }; #endif //BOOTSTRAP_WIZARD_H ================================================ FILE: app/client/Wizard/BootstrapProgressPage.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "../Application.h" #include "FirstRunWizard.h" #include "BootstrapProgressPage.h" BootstrapProgressPage::BootstrapProgressPage() { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 20 ); layout->addWidget( ui.image = new QLabel( this ), 0, Qt::AlignTop | Qt::AlignHCenter ); ui.image->setObjectName( "image" ); layout->addWidget( ui.description = new QLabel( tr( "

    Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.

    " "

    While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click Continue to take the tour.

    "), this ), 0, Qt::AlignTop); ui.description->setObjectName( "description" ); ui.description->setWordWrap( true ); } void BootstrapProgressPage::setPluginId( const QString& pluginId ) { if ( pluginId == "itw" || pluginId == "osx" ) ui.image->setPixmap( QPixmap( ":/graphic_import_itunes.png" ) ); else if ( pluginId == "wmp" ) ui.image->setPixmap( QPixmap( ":/graphic_import_wmp.png" ) ); else if ( pluginId == "wa2" ) ui.image->setPixmap( QPixmap( ":/graphic_import_winamp.png" ) ); else if ( pluginId == "foo2" || pluginId == "foo3" ) ui.image->setPixmap( QPixmap( ":/graphic_import_foobar.png" ) ); } void BootstrapProgressPage::initializePage() { wizard()->setButton( FirstRunWizard::NextButton, tr( "Continue" ) ); if ( wizard()->canGoBack() ) wizard()->setButton( FirstRunWizard::BackButton, tr( "<< Back" ) ); } void BootstrapProgressPage::cleanupPage() { } ================================================ FILE: app/client/Wizard/BootstrapProgressPage.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef BOOTSTRAPPROGRESSPAGE_H #define BOOTSTRAPPROGRESSPAGE_H #include "WizardPage.h" class BootstrapProgressPage : public WizardPage { Q_OBJECT private: struct { class QLabel* image; class QLabel* description; } ui; public: explicit BootstrapProgressPage(); void setPluginId( const QString& pluginId ); private: void initializePage(); void cleanupPage(); }; #endif // BOOTSTRAPPROGRESSPAGE_H ================================================ FILE: app/client/Wizard/FirstRunWizard.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include "lib/unicorn/UnicornSettings.h" #include "../Application.h" #include "LoginPage.h" #include "AccessPage.h" #include "PluginsPage.h" #include "PluginsInstallPage.h" #include "BootstrapPage.h" #include "TourMetadataPage.h" #include "TourFinishPage.h" #include "TourScrobblesPage.h" #include "TourLocationPage.h" #include "ui_FirstRunWizard.h" #include "FirstRunWizard.h" FirstRunWizard::FirstRunWizard( bool startFromTour, QWidget* parent ) :QDialog( parent ), ui( new Ui::FirstRunWizard ), m_commitPage( false ), m_showWelcome( false ), m_bootstrapState( NoBootstrap ) { #ifdef Q_OS_WIN32 m_plugins = new unicorn::PluginList; #endif ui->setupUi( this ); ui->welcome->hide(); for ( int i = 0 ; i < ui->stackedWidget->count() ; ++i ) { qobject_cast(ui->stackedWidget->widget( i ))->setWizard( this ); } connect( ui->next, SIGNAL(clicked()), SLOT(next())); connect( ui->back, SIGNAL(clicked()), SLOT(back())); connect( ui->skip, SIGNAL(clicked()), SLOT(skip())); connect( ui->finish, SIGNAL(clicked()), SLOT(accept())); connect( this, SIGNAL( rejected() ), this, SLOT( onRejected() ) ); connect( this, SIGNAL( accepted() ), this, SLOT( onWizardCompleted() ) ); connect( aApp, SIGNAL(bootstrapStarted(QString)), SLOT(onBootstrapStarted(QString))); connect( aApp, SIGNAL(bootstrapDone(AbstractBootstrapper::BootstrapStatus)), SLOT(onBootstrapDone(AbstractBootstrapper::BootstrapStatus))); if ( startFromTour ) ui->stackedWidget->setCurrentWidget( ui->tourScrobblesPage ); else ui->stackedWidget->setCurrentWidget( ui->loginPage ); initializePage( ui->stackedWidget->currentWidget() ); setFixedSize( 750, 450 ); } FirstRunWizard::~FirstRunWizard() { #ifdef Q_OS_WIN32 delete m_plugins; #endif } FirstRunWizard::BootstrapState FirstRunWizard::bootstrapState() const { return m_bootstrapState; } AbstractBootstrapper::BootstrapStatus FirstRunWizard::bootstrapStatus() const { return m_bootstrapStatus; } void FirstRunWizard::setTitle( const QString& title ) { ui->title->setText( title ); } void FirstRunWizard::setCommitPage( bool commitPage ) { m_commitPage = commitPage; } bool FirstRunWizard::canGoBack() const { return m_pages.count() > 0; } void FirstRunWizard::showWelcome() { m_showWelcome = true; } QAbstractButton* FirstRunWizard::setButton( Button button, const QString& text ) { QAbstractButton* returnButton; switch ( button ) { case CustomButton: returnButton = ui->custom; break; case BackButton: returnButton = ui->back; break; case SkipButton: returnButton = ui->skip; break; case NextButton: returnButton = ui->next; break; case FinishButton: returnButton = ui->finish; break; } returnButton->setText( text ); returnButton->show(); return returnButton; } void FirstRunWizard::next() { setUpdatesEnabled( false ); QWidget* currentPage = ui->stackedWidget->currentWidget(); if ( qobject_cast(currentPage)->validatePage() ) { // go to the next page cleanupPage( currentPage ); // remember what the last page was so we can go back() m_pages << currentPage; if ( m_commitPage ) { m_commitPage = false; m_pages.clear(); } if ( currentPage == ui->loginPage ) ui->stackedWidget->setCurrentWidget( ui->accessPage ); else if ( currentPage == ui->accessPage ) #ifdef Q_OS_WIN32 ui->stackedWidget->setCurrentWidget( ui->pluginsPage ); else if ( currentPage == ui->pluginsPage ) ui->stackedWidget->setCurrentWidget( ui->pluginsInstallPage ); else if ( currentPage == ui->pluginsInstallPage ) if( aApp->currentSession().user().canBootstrap() && (m_plugins->bootstrappablePlugins().count() > 0) ) ui->stackedWidget->setCurrentWidget( ui->bootstrapPage ); else ui->stackedWidget->setCurrentWidget( ui->tourScrobblesPage ); #elif defined Q_OS_MAC if( aApp->currentSession().user().canBootstrap() ) ui->stackedWidget->setCurrentWidget( ui->bootstrapPage ); else ui->stackedWidget->setCurrentWidget( ui->tourScrobblesPage ); #elif defined Q_WS_X11 ui->stackedWidget->setCurrentWidget( ui->tourScrobblesPage ); #endif else if ( currentPage == ui->bootstrapPage ) ui->stackedWidget->setCurrentWidget( ui->tourScrobblesPage ); else if ( currentPage == ui->tourScrobblesPage ) ui->stackedWidget->setCurrentWidget( ui->tourMetadataPage ); // only show the radio page if you can subscribe to get radio else if ( currentPage == ui->tourMetadataPage ) { #ifndef Q_WS_X11 // don't show the sys tray page on linux because there isn't one ui->stackedWidget->setCurrentWidget( ui->tourLocationPage ); #else ui->stackedWidget->setCurrentWidget( ui->tourFinishPage ); #endif } else if ( currentPage == ui->tourLocationPage ) #ifndef Q_WS_X11 // don't show the sys tray page on linux because there isn't one ui->stackedWidget->setCurrentWidget( ui->tourFinishPage ); #endif ui->welcome->hide(); if ( m_showWelcome ) { m_showWelcome = false; ui->welcome->setText( tr( "Thanks %1, your account is now connected!" ).arg( aApp->currentSession().user().name() ) ); ui->welcome->show(); } initializePage( ui->stackedWidget->currentWidget() ); } setUpdatesEnabled( true ); } void FirstRunWizard::back() { if ( canGoBack() ) { setCommitPage( false ); ui->welcome->hide(); cleanupPage( ui->stackedWidget->currentWidget() ); ui->stackedWidget->setCurrentWidget( m_pages.takeLast() ); initializePage( ui->stackedWidget->currentWidget() ); } } void FirstRunWizard::skip() { // skip is mostly the same as next but we don't call validatePage() // and the tour pages all go ot the last page QWidget* currentPage = ui->stackedWidget->currentWidget(); // go to the next page cleanupPage( currentPage ); // remember what the last page was so we can go back() m_pages << currentPage; if ( currentPage == ui->loginPage ) ui->stackedWidget->setCurrentWidget( ui->accessPage ); else if ( currentPage == ui->accessPage ) #ifdef Q_OS_WIN ui->stackedWidget->setCurrentWidget( ui->pluginsPage ); else if ( currentPage == ui->pluginsPage ) ui->stackedWidget->setCurrentWidget( ui->pluginsInstallPage ); else if ( currentPage == ui->pluginsInstallPage ) if( aApp->currentSession().user().canBootstrap() && (m_plugins->bootstrappablePlugins().count() > 0) ) ui->stackedWidget->setCurrentWidget( ui->bootstrapPage ); else ui->stackedWidget->setCurrentWidget( ui->tourScrobblesPage ); #elif defined Q_OS_MAC if( aApp->currentSession().user().canBootstrap() ) ui->stackedWidget->setCurrentWidget( ui->bootstrapPage ); else ui->stackedWidget->setCurrentWidget( ui->tourScrobblesPage ); #elif defined Q_WS_X11 ui->stackedWidget->setCurrentWidget( ui->tourScrobblesPage ); #endif else if ( currentPage == ui->bootstrapPage ) ui->stackedWidget->setCurrentWidget( ui->tourScrobblesPage ); else if ( currentPage == ui->bootstrapProgressPage ) ui->stackedWidget->setCurrentWidget( ui->tourScrobblesPage ); else if ( currentPage == ui->tourScrobblesPage || currentPage == ui->tourMetadataPage || currentPage == ui->tourLocationPage ) ui->stackedWidget->setCurrentWidget( ui->tourFinishPage ); ui->welcome->hide(); initializePage( ui->stackedWidget->currentWidget() ); } void FirstRunWizard::cleanupPage( QWidget* widget ) { WizardPage* page = qobject_cast(widget); // disconect any buttons from page disconnect( ui->next, 0, page, 0 ); disconnect( ui->back, 0, page, 0 ); disconnect( ui->finish, 0, page, 0 ); disconnect( ui->custom, 0, page, 0 ); disconnect( ui->skip, 0, page, 0 ); page->cleanupPage(); } void FirstRunWizard::initializePage( QWidget* widget ) { WizardPage* page = qobject_cast(widget); // hide all the buttons ui->next->hide(); ui->back->hide(); ui->finish->hide(); ui->custom->hide(); ui->skip->hide(); page->initializePage(); } void FirstRunWizard::onBootstrapStarted( const QString& pluginId ) { m_bootstrapState = BootstrapStarted; ui->bootstrapProgressPage->setPluginId( pluginId ); ui->importLabel->setText( tr( "Importing..." ) ); QMovie* movie = new QMovie( ":/graphic_import.gif", "GIF", this ); movie->setCacheMode( QMovie::CacheAll ); ui->importIcon->setMovie( movie ); movie->start(); ui->importIcon->show(); ui->importLabel->show(); } void FirstRunWizard::onBootstrapDone( AbstractBootstrapper::BootstrapStatus status ) { m_bootstrapState = BootstrapFinished; m_bootstrapStatus = status; ui->importIcon->setPixmap( QPixmap( ":/lastfm_icon_32.png" ) ); ui->importLabel->setText( tr( "Import complete!" ) ); } void FirstRunWizard::onWizardCompleted() { unicorn::Settings().setFirstRunWizardCompleted( true ); } void FirstRunWizard::onRejected() { } ================================================ FILE: app/client/Wizard/FirstRunWizard.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef FIRST_RUN_WIZARD_H_ #define FIRST_RUN_WIZARD_H_ #include #include #include #include #include "lib/unicorn/UnicornSession.h" #include "../Bootstrapper/AbstractBootstrapper.h" #ifdef Q_OS_WIN32 #include "lib/unicorn/plugins/PluginList.h" #endif namespace Ui { class FirstRunWizard; } class FirstRunWizard : public QDialog { Q_OBJECT public: enum Button { CustomButton, BackButton, SkipButton, NextButton, FinishButton }; enum BootstrapState { NoBootstrap, BootstrapStarted, BootstrapFinished }; public: FirstRunWizard( bool startFromTour = false, QWidget* parent = 0 ); ~FirstRunWizard(); void setTitle( const QString& title ); QAbstractButton* setButton( Button button, const QString& text ); void setCommitPage( bool commitPage ); bool canGoBack() const; void showWelcome(); BootstrapState bootstrapState() const; AbstractBootstrapper::BootstrapStatus bootstrapStatus() const; #ifdef Q_OS_WIN32 class unicorn::PluginList* pluginList() const { return m_plugins; } #endif public slots: void next(); void back(); void skip(); private slots: void onWizardCompleted(); void onRejected(); void onBootstrapStarted( const QString& pluginId ); void onBootstrapDone( AbstractBootstrapper::BootstrapStatus status ); private: void initializePage( QWidget* page ); void cleanupPage( QWidget* page ); private: Ui::FirstRunWizard* ui; #ifdef Q_OS_WIN32 QPointer m_plugins; #endif QList m_pages; bool m_commitPage; bool m_showWelcome; BootstrapState m_bootstrapState; AbstractBootstrapper::BootstrapStatus m_bootstrapStatus; }; #endif //FIRST_RUN_WIZARD_H_ ================================================ FILE: app/client/Wizard/FirstRunWizard.ui ================================================ FirstRunWizard 0 0 725 390 0 0 Last.fm Desktop App 0 0 Welcome Title 40 40 0 Qt::Horizontal 0 20 Back Skip Custom Next Finish LoginPage QWidget
    ../Wizard/LoginPage.h
    1
    AccessPage QWidget
    ../Wizard/AccessPage.h
    1
    PluginsPage QWidget
    ../Wizard/PluginsPage.h
    1
    PluginsInstallPage QWidget
    ../Wizard/PluginsInstallPage.h
    1
    BootstrapPage QWidget
    ../Wizard/BootstrapPage.h
    1
    TourScrobblesPage QWidget
    ../Wizard/TourScrobblesPage.h
    1
    TourMetadataPage QWidget
    ../Wizard/TourMetadataPage.h
    1
    TourFinishPage QWidget
    ../Wizard/TourFinishPage.h
    1
    TourLocationPage QWidget
    ../Wizard/TourLocationPage.h
    1
    BootstrapProgressPage QWidget
    ../Wizard/BootstrapProgressPage.h
    1
    ================================================ FILE: app/client/Wizard/LoginPage.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "lib/unicorn/LoginProcess.h" #include "lib/unicorn/UnicornSession.h" #include "lib/unicorn/DesktopServices.h" #include "FirstRunWizard.h" #include "LoginPage.h" #include "../Application.h" #include "lib/unicorn/dialogs/ProxyDialog.h" LoginPage::LoginPage() { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 20 ); layout->addWidget( ui.image = new QLabel( this ), 0, Qt::AlignTop | Qt::AlignHCenter ); ui.image->setObjectName( "image" ); layout->addWidget( ui.description = new QLabel( tr( "

    Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.

    " "

    If you don't have an account you can sign up now for free now.

    " ) ), 0, Qt::AlignTop ); ui.description->setObjectName( "description" ); ui.description->setWordWrap( true ); } void LoginPage::initializePage() { setTitle( tr( "Let's get started by connecting your Last.fm account" ) ); wizard()->setButton( FirstRunWizard::NextButton, tr( "Connect Your Account" ) )->setEnabled( true ); QAbstractButton* custom = wizard()->setButton( FirstRunWizard::CustomButton, tr( "Sign up" ) ); custom->setEnabled( true ); QAbstractButton* proxy = wizard()->setButton( FirstRunWizard::BackButton, tr( "Proxy?" ) ); proxy->setEnabled( true ); connect( custom, SIGNAL(clicked()), SLOT(onSignUpClicked())); connect( proxy, SIGNAL(clicked()), SLOT(onProxyClicked())); } void LoginPage::cleanupPage() { } void LoginPage::onSignUpClicked() { unicorn::DesktopServices::openUrl( lastfm::UrlBuilder( "join" ).url() ); } void LoginPage::onProxyClicked() { unicorn::ProxyDialog proxy; proxy.exec(); } ================================================ FILE: app/client/Wizard/LoginPage.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef LOGIN_PAGE_H_ #define LOGIN_PAGE_H_ #include "WizardPage.h" #include class LoginPage : public WizardPage { Q_OBJECT private: struct { class QLabel* image; class QLabel* description; } ui; public: LoginPage(); void initializePage(); void cleanupPage(); private slots: void onSignUpClicked(); void onProxyClicked(); }; #endif //LOGIN_PAGE_H_ ================================================ FILE: app/client/Wizard/PluginsInstallPage.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/dialogs/CloseAppsDialog.h" #include "FirstRunWizard.h" #include "PluginsInstallPage.h" PluginsInstallPage::PluginsInstallPage() { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 20 ); layout->addWidget( ui.image = new QLabel( this ), 0, Qt::AlignTop ); ui.image->setObjectName( "image" ); layout->addWidget( ui.description = new QLabel( tr( "

    Please follow the instructions that appear from your operating system to install the plugins.

    " "

    Once the plugins have been installed on you computer, click Continue.

    "), this ), 0, Qt::AlignTop); ui.description->setObjectName( "description" ); ui.description->setWordWrap( true ); } void PluginsInstallPage::initializePage() { setTitle( tr( "Your plugins are now being installed" ) ); wizard()->setCommitPage( true ); #ifdef Q_OS_WIN32 QAbstractButton* continueButton = wizard()->setButton( FirstRunWizard::NextButton, tr( "Continue" ) ); if ( wizard()->canGoBack() ) wizard()->setButton( FirstRunWizard::BackButton, tr( "<< Back" ) ); if ( wizard()->pluginList()->installList().count() > 0 ) { // get the install to happen a bit later so that // we actually show this page of the wizard // and the user has time to read what's happening QTimer::singleShot( 1000, this, SLOT(install()) ); continueButton->setEnabled( false ); } else { continueButton->click(); } #endif } void PluginsInstallPage::install() { #ifdef Q_OS_WIN32 // Tell the user to close any aplications we are about to install // plugins for unicorn::CloseAppsDialog* closeApps = new unicorn::CloseAppsDialog( wizard()->pluginList()->installList(), this ); if ( closeApps->result() != QDialog::Accepted ) closeApps->exec(); else closeApps->deleteLater(); if ( closeApps->result() == QDialog::Accepted ) { foreach( unicorn::IPluginInfo* plugin, wizard()->pluginList()->installList() ) { if ( !plugin->doInstall() ) { // The plugin failed to install // should probably do something } } } else { // The user didn't close their media players QMessageBoxBuilder( this ).setTitle( tr( "Your plugins haven't been installed" ) ) .setIcon( QMessageBox::Warning ) .setText( tr( "You can install them later through the file menu" ) ) .setButtons( QMessageBox::Ok ) .exec(); } #endif QAbstractButton* continueButton = wizard()->setButton( FirstRunWizard::NextButton, tr( "Continue" ) ); continueButton->setEnabled( true ); QTimer::singleShot( 1000, continueButton, SLOT(click()) ); } void PluginsInstallPage::cleanupPage() { } ================================================ FILE: app/client/Wizard/PluginsInstallPage.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLUGINSINSTALLPAGE_H #define PLUGINSINSTALLPAGE_H #include "WizardPage.h" class PluginsInstallPage : public WizardPage { Q_OBJECT private: struct { class QLabel* image; class QLabel* description; } ui; public: explicit PluginsInstallPage(); private slots: void install(); private: void initializePage(); void cleanupPage(); }; #endif // PLUGINSINSTALLPAGE_H ================================================ FILE: app/client/Wizard/PluginsPage.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include #include #include #include #ifdef Q_OS_WIN32 #include "lib/unicorn/plugins/PluginList.h" #endif #include "FirstRunWizard.h" #include "PluginsPage.h" PluginsPage::PluginsPage() { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 20 ); // add the radio buttons m_pluginsLayout = new QVBoxLayout(); m_pluginsLayout->setContentsMargins( 0, 0, 0, 0 ); m_pluginsLayout->setSpacing( 6 ); layout->addLayout( m_pluginsLayout ); layout->addWidget( ui.description = new QLabel( tr( "

    Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.

    " "

    Please select the media players that you would like to scrobble your music from and click Install Plugins

    "), this ), 0, Qt::AlignTop ); ui.description->setObjectName( "description" ); ui.description->setWordWrap( true ); } bool PluginsPage::validatePage() { return true; } void PluginsPage::cleanupPage() { } void PluginsPage::initializePage() { #ifdef Q_OS_WIN32 QList supportedPlugins = wizard()->pluginList()->supportedList(); foreach( unicorn::IPluginInfo* plugin, supportedPlugins ) { QCheckBox* cb; m_pluginsLayout->addWidget( cb = new QCheckBox( plugin->name(), this )); connect( cb, SIGNAL(toggled(bool)), plugin, SLOT(install(bool))); connect( cb, SIGNAL(toggled(bool)), SLOT(checkPluginsSelected())); cb->setObjectName( plugin->id() ); cb->setChecked( plugin->isAppInstalled() ); cb->style()->polish( cb ); if ( plugin->isInstalled() ) { if ( plugin->version() > plugin->installedVersion() ) { cb->setChecked( true ); cb->setText( cb->text() + " " + tr( "(newer version)" )); } else { cb->setChecked( false ); cb->setText( cb->text() + " " + tr( "(Plugin installed tick to reinstall)" )); } } } m_pluginsLayout->addStretch(); setTitle( tr( "Next step, install the Last.fm plugins to be able to scrobble the music you listen to." )); checkPluginsSelected(); if ( wizard()->canGoBack() ) wizard()->setButton( FirstRunWizard::BackButton, tr( "<< Back" ) ); #endif } void PluginsPage::checkPluginsSelected() { #ifdef Q_OS_WIN32 wizard()->setButton( FirstRunWizard::SkipButton, tr( "Skip >>" ) )->setVisible( wizard()->pluginList()->installList().count() > 0 ); wizard()->setButton( FirstRunWizard::NextButton, wizard()->pluginList()->installList().count() > 0 ? tr( "Install Plugins" ): tr( "Continue" ) ); #endif } ================================================ FILE: app/client/Wizard/PluginsPage.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLUGINS_PAGE_H #define PLUGINS_PAGE_H #include "WizardPage.h" class PluginsPage : public WizardPage { Q_OBJECT private: struct { class QCheckBox* iTunes; class QCheckBox* iWMP; class QCheckBox* iWinAmp; class QCheckBox* iFoo; class QLabel* description; } ui; public: PluginsPage(); private: bool validatePage(); void cleanupPage(); void initializePage(); private slots: void checkPluginsSelected(); private: class QVBoxLayout* m_pluginsLayout; }; #endif //PLUGINS_PAGE_H ================================================ FILE: app/client/Wizard/TourFinishPage.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "TourFinishPage.h" #include "../Application.h" #include "../Bootstrapper/AbstractBootstrapper.h" TourFinishPage::TourFinishPage() { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 20 ); layout->addWidget( ui.image = new QLabel( this ), 0, Qt::AlignTop | Qt::AlignHCenter ); ui.image->setObjectName( "image" ); layout->addWidget( ui.description = new QLabel( this ), 0, Qt::AlignTop ); ui.description->setObjectName( "description" ); ui.description->setWordWrap( true ); connect( aApp, SIGNAL(bootstrapDone(AbstractBootstrapper::BootstrapStatus)), SLOT(onBootstrapDone(AbstractBootstrapper::BootstrapStatus))); } void TourFinishPage::setDescription( FirstRunWizard::BootstrapState state, AbstractBootstrapper::BootstrapStatus status ) { if ( state == FirstRunWizard::BootstrapFinished ) { if ( status == AbstractBootstrapper::Bootstrap_Ok ) { ui.description->setText( tr( "

    Now you're ready to get started! Just click Finish and start exploring.

    " "

    We've also finished importing your listening history and have added it to your Last.fm profile.

    " "

    Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!

    ") ); } else { QMap reasons; reasons[AbstractBootstrapper::Bootstrap_UploadError] = tr( "there was an upload error" ); reasons[AbstractBootstrapper::Bootstrap_Denied] = tr( "the submission was denied by Last.fm" ); reasons[AbstractBootstrapper::Bootstrap_Spam] = tr( "it was detected as spam (too high playcounts?)" ); reasons[AbstractBootstrapper::Bootstrap_Cancelled] = tr( "the submission was cancelled" ); ui.description->setText( tr( "

    Now you're ready to get started! Just click Finish and start exploring.

    " "

    Importing your listening history to Last.fm failed because %1. Sorry about that!

    " "

    Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!

    ").arg( reasons[status] ) ); } } else if ( state == FirstRunWizard::BootstrapStarted ) { ui.description->setText( tr( "

    Now you're ready to get started! Just click Finish and start exploring.

    " "

    We're still importing your listening history and it will be added to your Last.fm profile soon.

    " "

    Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!

    ") ); } else { ui.description->setText( tr( "

    Now you're ready to get started! Just click Finish and start exploring.

    " "

    Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!

    ") ); } } void TourFinishPage::initializePage() { setTitle( tr( "That's it, you're good to go!" ) ); setDescription( wizard()->bootstrapState(), wizard()->bootstrapStatus() ); wizard()->setButton( FirstRunWizard::FinishButton, tr( "Finish" ) ); if ( wizard()->canGoBack() ) wizard()->setButton( FirstRunWizard::BackButton, tr( "<< Back" ) ); } void TourFinishPage::cleanupPage() { } void TourFinishPage::onBootstrapDone( AbstractBootstrapper::BootstrapStatus status ) { setDescription( FirstRunWizard::BootstrapFinished, status); } ================================================ FILE: app/client/Wizard/TourFinishPage.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TOURFINISHPAGE_H #define TOURFINISHPAGE_H #include "WizardPage.h" #include "../Bootstrapper/AbstractBootstrapper.h" #include "FirstRunWizard.h" class TourFinishPage : public WizardPage { Q_OBJECT private: struct { class QLabel* image; class QLabel* description; } ui; public: explicit TourFinishPage(); private slots: void onBootstrapDone( AbstractBootstrapper::BootstrapStatus status ); private: void setDescription( FirstRunWizard::BootstrapState state, AbstractBootstrapper::BootstrapStatus status ); void initializePage(); void cleanupPage(); }; #endif // TOURFINISHPAGE_H ================================================ FILE: app/client/Wizard/TourLocationPage.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "TourLocationPage.h" #include #include #include #include #include #include "FirstRunWizard.h" #include "../Application.h" #include "../Widgets/PointyArrow.h" TourLocationPage::TourLocationPage() :m_flash( true ) { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 20 ); layout->addWidget( ui.image = new QLabel( this ), 0, Qt::AlignTop | Qt::AlignHCenter ); #ifdef Q_OS_MAC ui.image->setObjectName( "imagemac" ); #else ui.image->setObjectName( "imagewin" ); #endif layout->addWidget( ui.description = new QLabel( tr( "

    The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.

    " "

    Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.

    " ), this ), 0, Qt::AlignTop); ui.description->setObjectName( "description" ); ui.description->setWordWrap( true ); } TourLocationPage::~TourLocationPage() { if ( m_flashTimer ) m_flashTimer->stop(); if ( !m_normalIcon.isNull() ) aApp->tray()->setIcon( m_normalIcon ); delete m_arrow; } void TourLocationPage::initializePage() { delete m_arrow; m_arrow = new PointyArrow; delete m_flashTimer; m_flashTimer = new QTimer(this); m_flashTimer->setInterval( 300 ); connect( m_flashTimer, SIGNAL(timeout()), SLOT(flashSysTray())); #ifdef Q_OS_MAC setTitle( tr( "The Last.fm Desktop App in your menu bar" ) ); ui.image->setPixmap( QPixmap( ":/graphic_location_MAC.png" ) ); #else setTitle( tr( "The Last.fm Desktop App in your system tray" ) ); ui.image->setPixmap( QPixmap( ":/graphic_location_WIN.png" ) ); #endif QSystemTrayIcon* tray = aApp->tray(); m_arrow->pointAt( QPoint( tray->geometry().left() + (tray->geometry().width() / 2.0f ), tray->geometry().top() + (tray->geometry().height() / 2.0f ) )); m_flashTimer->start(); m_normalIcon = tray->icon(); m_transparentIcon = QPixmap( ":22x22_transparent.png" ).scaled( m_normalIcon.availableSizes().first()); m_flash = false; wizard()->setButton( FirstRunWizard::NextButton, tr( "Continue" ) ); if ( wizard()->canGoBack() ) wizard()->setButton( FirstRunWizard::BackButton, tr( "<< Back" ) ); } void TourLocationPage::cleanupPage() { delete m_arrow; delete m_flashTimer; aApp->tray()->setIcon( m_normalIcon ); } void TourLocationPage::flashSysTray() { QSystemTrayIcon* tray = aApp->tray(); if( m_flash ) tray->setIcon( m_transparentIcon ); else tray->setIcon( m_normalIcon ); m_flash = !m_flash; } ================================================ FILE: app/client/Wizard/TourLocationPage.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TOUR_LOCATION_PAGE_H #define TOUR_LOCATION_PAGE_H #include "WizardPage.h" #include #include class PointyArrow; class TourLocationPage : public WizardPage { Q_OBJECT private: struct { class QLabel* image; class QLabel* description; } ui; public: TourLocationPage(); ~TourLocationPage(); void initializePage(); void cleanupPage(); protected slots: void flashSysTray(); private: QPointer m_arrow; QPointer m_flashTimer; QIcon m_transparentIcon; QIcon m_normalIcon; bool m_flash; }; #endif // TOUR_METADATA_PAGE_H ================================================ FILE: app/client/Wizard/TourMetadataPage.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "TourMetadataPage.h" #include #include #include #include #include #include "FirstRunWizard.h" #include "../Application.h" #include "../Widgets/PointyArrow.h" TourMetadataPage::TourMetadataPage() { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 20 ); layout->addWidget( ui.image = new QLabel( this ), 0, Qt::AlignTop | Qt::AlignHCenter ); ui.image->setObjectName( "image" ); layout->addWidget( ui.description = new QLabel( tr( "

    Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.

    " "

    Check out the Now Playing tab, or simply click on any track in your Scrobbles tab to learn more.

    "), this ), 0, Qt::AlignTop); ui.description->setObjectName( "description" ); ui.description->setWordWrap( true ); } void TourMetadataPage::initializePage() { setTitle( tr( "Discover more about the artists you love" ) ); wizard()->setButton( FirstRunWizard::NextButton, tr( "Continue" ) ); if ( wizard()->canGoBack() ) wizard()->setButton( FirstRunWizard::BackButton, tr( "<< Back" ) ); wizard()->setButton( FirstRunWizard::SkipButton, tr( "Skip Tour >>" ) ); } void TourMetadataPage::cleanupPage() { } ================================================ FILE: app/client/Wizard/TourMetadataPage.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TOUR_METADATA_PAGE_H #define TOUR_METADATA_PAGE_H #include "WizardPage.h" #include class TourMetadataPage : public WizardPage { Q_OBJECT private: struct { class QLabel* image; class QLabel* description; } ui; public: TourMetadataPage(); private: void initializePage(); void cleanupPage(); }; #endif // TOUR_METADATA_PAGE_H ================================================ FILE: app/client/Wizard/TourScrobblesPage.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #include #include "FirstRunWizard.h" #include "TourScrobblesPage.h" TourScrobblesPage::TourScrobblesPage() { QHBoxLayout* layout = new QHBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 20 ); layout->addWidget( ui.image = new QLabel( this ), 0, Qt::AlignTop | Qt::AlignHCenter ); ui.image->setObjectName( "image" ); layout->addWidget( ui.description = new QLabel( tr( "

    The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more.

    " "

    You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.

    "), this ), 0, Qt::AlignTop); ui.description->setObjectName( "description" ); ui.description->setWordWrap( true ); } void TourScrobblesPage::initializePage() { setTitle( tr( "Welcome to the Last.fm Desktop App!" ) ); wizard()->setButton( FirstRunWizard::NextButton, tr( "Continue" ) ); if ( wizard()->canGoBack() ) wizard()->setButton( FirstRunWizard::BackButton, tr( "<< Back" ) ); wizard()->setButton( FirstRunWizard::SkipButton, tr( "Skip Tour >>" ) ); } void TourScrobblesPage::cleanupPage() { } ================================================ FILE: app/client/Wizard/TourScrobblesPage.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef TOURSCROBBLESPAGE_H #define TOURSCROBBLESPAGE_H #include "WizardPage.h" class TourScrobblesPage : public WizardPage { Q_OBJECT private: struct { class QLabel* image; class QLabel* description; } ui; public: explicit TourScrobblesPage(); private: void initializePage(); void cleanupPage(); }; #endif // TOURSCROBBLESPAGE_H ================================================ FILE: app/client/Wizard/WizardPage.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "FirstRunWizard.h" #include "WizardPage.h" WizardPage::WizardPage() { } bool WizardPage::validatePage() { return true; } void WizardPage::setTitle( const QString &title ) { wizard()->setTitle( title ); } void WizardPage::setWizard( FirstRunWizard* wizard ) { m_wizard = wizard; } FirstRunWizard* WizardPage::wizard() const { return m_wizard; } ================================================ FILE: app/client/Wizard/WizardPage.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef WIZARDPAGE_H #define WIZARDPAGE_H #include class FirstRunWizard; class WizardPage : public QWidget { Q_OBJECT public: explicit WizardPage(); void setWizard( FirstRunWizard* wizard ); virtual void initializePage() = 0; virtual bool validatePage(); virtual void cleanupPage() = 0; void setTitle( const QString& title ); protected: FirstRunWizard* wizard() const; private: FirstRunWizard* m_wizard; }; #endif // WIZARDPAGE_H ================================================ FILE: app/client/audioscrobbler.rc ================================================ IDI_ICON1 ICON DISCARDABLE "audioscrobbler.ico" #include // Get updates from this appcast feed: FeedURL APPCAST {"https://cdn.last.fm/client/Win/updates.xml"} // Version information; this, too, is used by WinSparkle 1 VERSIONINFO FILEVERSION 2,1,36,0 PRODUCTVERSION 2,1,36,0 FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGS 0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_APP FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" // US English, Unicode BEGIN VALUE "Comments", "Last.fm\0" VALUE "CompanyName", "Last.fm\0" VALUE "FileDescription", "Last.fm\0" VALUE "FileVersion", "2.1.36\0" VALUE "InternalName", "Last.fm\0" VALUE "OriginalFilename", "Last.fm.exe\0" VALUE "LegalCopyright", "Public Domain\0" VALUE "ProductName", "Last.fm\0" VALUE "ProductVersion", "2.1.36\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x0409, 0x04B0 END END ================================================ FILE: app/client/client.pro ================================================ TEMPLATE = app TARGET = "Last.fm Scrobbler" unix:!mac { TARGET = lastfm-scrobbler } VERSION = 2.1.39 DEFINES += APP_VERSION=\\\"$$VERSION\\\" QT = core gui xml network sql CONFIG += lastfm unicorn listener logger phonon analytics win32:LIBS += user32.lib kernel32.lib psapi.lib DEFINES += LASTFM_COLLAPSE_NAMESPACE macx:LIBS += -weak_framework Cocoa win32:release { LIBS += -lAdvAPI32 } include( ../../admin/include.qmake ) DEFINES += LASTFM_COLLAPSE_NAMESPACE SOURCES -= LegacyTuner.cpp HEADERS -= LegacyTuner.h macx:ICON = ./audioscrobbler.icns !win32:LIBS += -lz win32:LIBS += shell32.lib User32.lib RC_FILE = audioscrobbler.rc unix:!mac { CONFIG += link_pkgconfig PKGCONFIG += libgpod-1.0 } SUBDIRS=PrefPane SOURCES += \ AudioscrobblerSettings.cpp \ Application.cpp \ ScrobSocket.cpp \ MediaDevices/MediaDevice.cpp \ MediaDevices/IpodDevice.cpp \ MediaDevices/DeviceScrobbler.cpp \ MainWindow.cpp \ main.cpp \ Settings/SettingsWidget.cpp \ Settings/ScrobbleSettingsWidget.cpp \ Settings/IpodSettingsWidget.cpp \ Settings/AccountSettingsWidget.cpp \ Settings/PreferencesDialog.cpp \ Settings/AdvancedSettingsWidget.cpp \ Settings/GeneralSettingsWidget.cpp \ Services/ScrobbleService/StopWatch.cpp \ Services/ScrobbleService/ScrobbleService.cpp \ Dialogs/DiagnosticsDialog.cpp \ Bootstrapper/PluginBootstrapper.cpp \ Bootstrapper/ITunesDevice/itunesdevice.cpp \ Bootstrapper/iTunesBootstrapper.cpp \ Bootstrapper/AbstractFileBootstrapper.cpp \ Bootstrapper/AbstractBootstrapper.cpp \ Widgets/TitleBar.cpp \ Widgets/StatusBar.cpp \ Widgets/SideBar.cpp \ Widgets/NothingPlayingWidget.cpp \ Widgets/NowPlayingStackedWidget.cpp \ Widgets/ProfileWidget.cpp \ Widgets/FriendListWidget.cpp \ Widgets/FriendWidget.cpp \ Widgets/BioWidget.cpp \ Widgets/MetadataWidget.cpp \ Widgets/TagWidget.cpp \ Widgets/ShortcutEdit.cpp \ Widgets/ProfileArtistWidget.cpp \ Widgets/ScrobbleControls.cpp \ Widgets/PointyArrow.cpp \ Widgets/PlaybackControlsWidget.cpp \ Widgets/NowPlayingWidget.cpp \ Widgets/RefreshButton.cpp \ Widgets/WidgetTextObject.cpp \ Wizard/LoginPage.cpp \ Wizard/BootstrapPage.cpp \ Wizard/FirstRunWizard.cpp \ Wizard/AccessPage.cpp \ Wizard/TourMetadataPage.cpp \ Wizard/PluginsPage.cpp \ Wizard/TourFinishPage.cpp \ Wizard/PluginsInstallPage.cpp \ Wizard/BootstrapProgressPage.cpp \ Wizard/TourScrobblesPage.cpp \ Wizard/TourLocationPage.cpp \ Wizard/WizardPage.cpp \ Widgets/ContextLabel.cpp \ Widgets/SimilarArtistWidget.cpp \ Widgets/PushButton.cpp \ Widgets/TrackWidget.cpp \ Dialogs/LicensesDialog.cpp \ Widgets/ScrobblesWidget.cpp \ Widgets/ScrobblesListWidget.cpp \ Services/AnalyticsService/AnalyticsService.cpp \ Services/AnalyticsService/PersistentCookieJar.cpp \ Settings/CheckFileSystemModel.cpp \ Settings/CheckFileSystemView.cpp \ HEADERS += \ ScrobSocket.h \ AudioscrobblerSettings.h \ Application.h \ MainWindow.h \ Services/ScrobbleService.h \ Services/ScrobbleService/StopWatch.h \ Services/ScrobbleService/ScrobbleService.h \ MediaDevices/MediaDevice.h \ MediaDevices/IpodDevice.h \ MediaDevices/DeviceScrobbler.h \ Dialogs/DiagnosticsDialog.h \ Bootstrapper/PluginBootstrapper.h \ Bootstrapper/ITunesDevice/MediaDeviceInterface.h \ Bootstrapper/ITunesDevice/ITunesParser.h \ Bootstrapper/ITunesDevice/itunesdevice.h \ Bootstrapper/iTunesBootstrapper.h \ Bootstrapper/AbstractFileBootstrapper.h \ Bootstrapper/AbstractBootstrapper.h \ Settings/SettingsWidget.h \ Settings/ScrobbleSettingsWidget.h \ Settings/PreferencesDialog.h \ Settings/AdvancedSettingsWidget.h \ Settings/GeneralSettingsWidget.h \ Settings/IpodSettingsWidget.h \ Settings/AccountSettingsWidget.h \ Widgets/ShortcutEdit.h \ Widgets/TitleBar.h \ Widgets/StatusBar.h \ Widgets/SideBar.h \ Widgets/ScrobbleControls.h \ Widgets/PointyArrow.h \ Widgets/PlaybackControlsWidget.h \ Widgets/NowPlayingWidget.h \ Widgets/NothingPlayingWidget.h \ Widgets/NowPlayingStackedWidget.h \ Widgets/ProfileWidget.h \ Widgets/FriendListWidget.h \ Widgets/FriendWidget.h \ Widgets/BioWidget.h \ Widgets/MetadataWidget.h \ Widgets/TagWidget.h \ Widgets/ProfileArtistWidget.h \ Widgets/RefreshButton.h \ Widgets/WidgetTextObject.h \ Wizard/AccessPage.h \ Wizard/TourMetadataPage.h \ Wizard/PluginsPage.h \ Wizard/TourFinishPage.h \ Wizard/PluginsInstallPage.h \ Wizard/BootstrapProgressPage.h \ Wizard/TourScrobblesPage.h \ Wizard/TourLocationPage.h \ Wizard/LoginPage.h \ Wizard/FirstRunWizard.h \ Wizard/BootstrapPage.h \ Wizard/WizardPage.h \ Widgets/ContextLabel.h \ Widgets/SimilarArtistWidget.h \ Widgets/PushButton.h \ Widgets/TrackWidget.h \ Dialogs/LicensesDialog.h \ Widgets/ScrobblesListWidget.h \ Widgets/ScrobblesWidget.h \ Services/AnalyticsService.h \ Services/AnalyticsService/AnalyticsService.h \ Services/AnalyticsService/PersistentCookieJar.h \ Settings/CheckFileSystemModel.h \ Settings/CheckFileSystemView.h \ mac:HEADERS += CommandReciever/CommandReciever.h \ mac:OBJECTIVE_SOURCES += CommandReciever/CommandReciever.mm \ Widgets/NothingPlayingWidget_mac.mm \ FORMS += \ Widgets/PlaybackControlsWidget.ui \ Dialogs/DiagnosticsDialog.ui \ Widgets/MetadataWidget.ui \ Settings/PreferencesDialog.ui \ Settings/GeneralSettingsWidget.ui \ Settings/AccountSettingsWidget.ui \ Settings/IpodSettingsWidget.ui \ Settings/ScrobbleSettingsWidget.ui \ Settings/AdvancedSettingsWidget.ui \ Wizard/FirstRunWizard.ui \ Widgets/NothingPlayingWidget.ui \ Widgets/FriendWidget.ui \ Widgets/FriendListWidget.ui \ Widgets/TrackWidget.ui \ Dialogs/LicensesDialog.ui \ Widgets/ScrobblesWidget.ui \ Widgets/ProfileWidget.ui \ unix:!mac { CONFIG += qdbus SOURCES += MediaDevices/IpodDevice_linux.cpp \ Mpris2/Mpris2.cpp \ Mpris2/DBusAbstractAdaptor.cpp \ Mpris2/MediaPlayer2.cpp \ Mpris2/MediaPlayer2Player.cpp HEADERS += MediaDevices/IpodDevice_linux.h \ Mpris2/Mpris2.h \ Mpris2/DBusAbstractAdaptor.h \ Mpris2/MediaPlayer2.h \ Mpris2/MediaPlayer2Player.h } RESOURCES += \ qrc/audioscrobbler.qrc unix:!mac { target.path = $$BINDIR css.files = 'Last.fm Scrobbler.css' css.path = $$DATADIR/lastfm-scrobbler desktop.files += lastfm-scrobbler.desktop desktop.path = $$DATADIR/applications icon16.files += icons/16x16/lastfm-scrobbler.png icon16.path = $$DATADIR/icons/hicolor/16x16/apps icon22.files += icons/22x22/lastfm-scrobbler.png icon22.path = $$DATADIR/icons/hicolor/22x22/apps icon32.files += icons/32x32/lastfm-scrobbler.png icon32.path = $$DATADIR/icons/hicolor/32x32/apps icon48.files += icons/48x48/lastfm-scrobbler.png icon48.path = $$DATADIR/icons/hicolor/48x48/apps icon64.files += icons/64x64/lastfm-scrobbler.png icon64.path = $$DATADIR/icons/hicolor/64x64/apps icon128.files += icons/128x128/lastfm-scrobbler.png icon128.path = $$DATADIR/icons/hicolor/128x128/apps INSTALLS += target css icon16 icon22 icon32 icon48 icon64 icon128 desktop } ================================================ FILE: app/client/lastfm-scrobbler.desktop ================================================ [Desktop Entry] Version=1.0 Name=Last.fm Scrobbler Comment=Listen to Last.fm radio Exec=lastfm-scrobbler %u Icon=lastfm-scrobbler Terminal=false Type=Application MimeType=x-scheme-handler/lastfm; Categories=Qt;AudioVideo;Audio;Player; X-KDE-Protocols=lastfm StartupNotify=true Keywords=Player;Audio; ================================================ FILE: app/client/main.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include #ifdef Q_OS_MAC64 #include static pascal OSErr appleEventHandler( const AppleEvent*, AppleEvent*, void* ); #elif defined Q_OS_MAC32 #include static pascal OSErr appleEventHandler( const AppleEvent*, AppleEvent*, long ); #endif #include #include #include #include #include "Application.h" #include "ScrobSocket.h" #include "lib/unicorn/UnicornApplication.h" #include "lib/unicorn/qtsingleapplication/qtsinglecoreapplication.h" #include "lib/unicorn/UnicornSettings.h" #include "Services/ScrobbleService.h" #include "lib/unicorn/CrashReporter/CrashReporter.h" void cleanup(); namespace lastfm { extern LASTFM_DLLEXPORT QByteArray UserAgent; } int main( int argc, char** argv ) { //unicorn::CrashReporter* crashReporter = new unicorn::CrashReporter; QtSingleCoreApplication::setApplicationName( "Last.fm Scrobbler" ); QtSingleCoreApplication::setOrganizationName( "Last.fm" ); QtSingleCoreApplication::setApplicationVersion( APP_VERSION ); // ATTENTION! Under no circumstance change these strings! --mxcl #ifdef WIN32 lastfm::UserAgent = "Last.fm Client " APP_VERSION " (Windows)"; #elif __APPLE__ lastfm::UserAgent = "Last.fm Client " APP_VERSION " (OS X)"; #elif defined (Q_WS_X11) lastfm::UserAgent = "Last.fm Client " APP_VERSION " (X11)"; #endif try { audioscrobbler::Application app( argc, argv ); #ifdef Q_OS_WIN32 QStringList args = app.arguments(); #else QStringList args = app.arguments().mid( 1 ); #endif if ( !args.contains( "--new" ) ) { if ( app.sendMessage( args ) || args.contains("--exit") ) return 0; // It's possible that we were unable to send the // message, but the app is actually running if ( app.isRunning() ) return 0; } qAddPostRoutine(cleanup); #ifdef Q_OS_MAC AEEventHandlerUPP h = NewAEEventHandlerUPP( appleEventHandler ); AEInstallEventHandler( 'GURL', 'GURL', h, 0, false ); #endif app.init(); app.parseArguments( args ); return app.exec(); } catch (std::exception& e) { qDebug() << "unhandled exception " << e.what(); } catch (unicorn::Application::StubbornUserException&) { // user wouldn't log in return 0; } //delete crashReporter; } #ifdef Q_OS_MAC #ifdef Q_OS_MAC64 static pascal OSErr appleEventHandler( const AppleEvent* e, AppleEvent*, void* ) #elif defined Q_OS_MAC32 static pascal OSErr appleEventHandler( const AppleEvent* e, AppleEvent*, long ) #endif //Q_OS_MAC64/32 { OSType id = typeWildCard; AEGetAttributePtr( e, keyEventIDAttr, typeType, 0, &id, sizeof(id), 0 ); switch (id) { default: return unimpErr; } } #endif //Q_OS_MAC void cleanup() { } ================================================ FILE: app/client/qrc/audioscrobbler.qrc ================================================ loading_meta.gif icon_eq.gif radio_eq_small.gif wizard/arrow_even_bigger_than_dropbox.png wizard/bg_clouds.png wizard/button_grey_LEFT_HOVER.png wizard/button_grey_LEFT_REST.png wizard/button_grey_MIDDLE_HOVER.png wizard/button_grey_MIDDLE_REST.png wizard/button_grey_RIGHT_HOVER.png wizard/button_grey_RIGHT_REST.png wizard/button_red_LEFT_HOVER.png wizard/button_red_LEFT_REST.png wizard/button_red_MIDDLE_HOVER.png wizard/button_red_MIDDLE_REST.png wizard/button_red_RIGHT_HOVER.png wizard/button_red_RIGHT_REST.png wizard/graphic_access.png wizard/graphic_connect.png wizard/graphic_finish.png wizard/graphic_import_foobar.png wizard/graphic_import_itunes.png wizard/graphic_import_winamp.png wizard/graphic_import_wmp.png wizard/graphic_importing.gif wizard/graphic_location_MAC.png wizard/graphic_location_WIN.png wizard/graphic_metadata.png wizard/graphic_plugins.png wizard/graphic_radio.png wizard/graphic_scrobbles.png wizard/player_foobar_64.png wizard/player_itunes_64.png wizard/player_winamp_64.png wizard/player_wmp_64.png wizard/lastfm_icon_32.png wizard/lastfm_icon_64.png button_LEFT_HOVER.png button_LEFT_PRESS.png button_LEFT_REST.png button_MIDDLE_HOVER.png button_MIDDLE_PRESS.png button_MIDDLE_REST.png button_RIGHT_HOVER.png button_RIGHT_PRESS.png button_RIGHT_REST.png message_close.png message_error.png message_info.png start_bg.png user_default.png meta_album_no_art.png meta_artist_no_photo.png scrobbles_refresh.png tab_now_playing_REST.png tab_now_playing_HOVER.png tab_now_playing_ACTIVE.png tab_scrobbles_REST.png tab_scrobbles_HOVER.png tab_scrobbles_ACTIVE.png tab_profile_REST.png tab_profile_HOVER.png tab_profile_ACTIVE.png tab_friends_REST.png tab_friends_HOVER.png tab_friends_ACTIVE.png tab_radio_REST.png tab_radio_HOVER.png tab_radio_ACTIVE.png radio_play_small_REST.png radio_play_small_HOVER.png radio_play_small_PRESS.png radio_play_large_REST.png radio_play_large_HOVER.png radio_play_large_PRESS.png radio_library_REST.png radio_library_HOVER.png radio_library_PRESS.png radio_mix_REST.png radio_mix_HOVER.png radio_mix_PRESS.png radio_rec_REST.png radio_rec_HOVER.png radio_rec_PRESS.png radio_friends_REST.png radio_friends_HOVER.png radio_friends_PRESS.png control_bar_back.png control_bar_scrobble_foobar.png control_bar_scrobble_itunes.png control_bar_scrobble_applemusic.png control_bar_scrobble_winamp.png control_bar_scrobble_wmp.png control_bar_scrobble_spotify.png controls_love_OFF_REST.png controls_love_OFF_HOVER.png controls_love_OFF_PRESS.png controls_love_ON_REST.png controls_love_ON_HOVER.png controls_love_ON_PRESS.png scrobble_marker_OFF.png scrobble_marker_ON.png controls_ban_REST.png controls_ban_HOVER.png controls_ban_PRESS.png controls_play_REST.png controls_play_HOVER.png controls_play_PRESS.png controls_pause_REST.png controls_pause_HOVER.png controls_pause_PRESS.png controls_skip_REST.png controls_skip_HOVER.png controls_skip_PRESS.png meta_context_arrow.png meta_buy_HOVER.png meta_buy_PRESS.png meta_buy_REST.png meta_love_OFF_HOVER.png meta_love_OFF_PRESS.png meta_love_OFF_REST.png meta_love_ON_HOVER.png meta_love_ON_PRESS.png meta_love_ON_REST.png meta_share_HOVER.png meta_share_PRESS.png meta_share_REST.png meta_tag_HOVER.png meta_tag_PRESS.png meta_tag_REST.png meta_tag_LEFT_HOVER.png meta_tag_LEFT_REST.png meta_tag_MIDDLE_HOVER.png meta_tag_MIDDLE_REST.png meta_tag_RIGHT_HOVER.png meta_tag_RIGHT_REST.png meta_radio_LEFT_HOVER.png meta_radio_LEFT_PRESS.png meta_radio_LEFT_REST.png meta_radio_MIDDLE_HOVER.png meta_radio_MIDDLE_PRESS.png meta_radio_MIDDLE_REST.png meta_radio_RIGHT_HOVER.png meta_radio_RIGHT_PRESS.png meta_radio_RIGHT_REST.png 16x16.png 22x22.png 22x22_transparent.png systray_icon_rest_mac.png systray_icon_pressed_mac.png tag_29x29_rest.png tag_29x29_hover.png tag_29x29_pressed.png tag_29x29_disabled.png share_29x29_rest.png share_29x29_hover.png share_29x29_pressed.png share_29x29_disabled.png delete.png asterisk_small.png as.png highlight.png titlebar_highlight.png slider_nubbin.png settings_HOVER.png settings_PRESS.png settings_REST.png volume_knob_REST.png volume_knob_PRESS.png on_tour.png now-playing-REST.png now-playing-PRESS.png taskbar-love-OFF-16x16.png taskbar-love-ON-16x16.png taskbar-ban-16x16.png taskbar-info-16x16.png taskbar-play-16x16.png taskbar-skip-16x16.png preferences_devices.png preferences_advanced.png preferences_general.png preferences_scrobbling.png preferences_accounts.png control_bar_radio_as.png scrobble_OFF.png subscribe_bg.png subscribe_radio.png button_dark_LEFT_HOVER.png button_dark_LEFT_PRESS.png button_dark_LEFT_REST.png button_dark_MIDDLE_HOVER.png button_dark_MIDDLE_PRESS.png button_dark_MIDDLE_REST.png button_dark_RIGHT_HOVER.png button_dark_RIGHT_PRESS.png button_dark_RIGHT_REST.png meta_ban_REST.png meta_skip_REST.png lastfm_icon_16_grayscale.png mac_control_bar_as_OFF.png lastfm_icon_22_grayscale.png scrobble_progress_OFF.png scrobble_progress_ON.png volume_high.png volume_low.png volume_mid.png volume_mute.png progress_slot_OFF.png progress_slot_ON.png ================================================ FILE: app/fingerprinter/Fingerprinter.cpp ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "LAV_Source.h" #include "Fingerprinter.h" Fingerprinter::Fingerprinter( const lastfm::Track& track, QObject* parent ) :QObject( parent ), m_fp( track ), m_fpSource( 0 ) { if ( m_fp.id().isNull() ) { m_fpSource = new LAV_Source(); if ( m_fpSource ) { try { m_fp.generate( m_fpSource ); connect( m_fp.submit(), SIGNAL(finished()), SLOT(onFingerprintSubmitted()) ); } catch ( const lastfm::Fingerprint::Error& error ) { qWarning() << "Fingerprint error: " << error; QTimer::singleShot(250, qApp, SLOT(quit())); } } } else { qDebug() << "Already Fingerprinted: " << m_fp.id(); #ifndef NDEBUG // This code will fetch the suggestions from the fingerprint id, one // day we might do something with this info, like offer corrections connect( m_fp.id().getSuggestions(), SIGNAL(finished()), SLOT(onGotSuggestions()) ); #else QTimer::singleShot(250, qApp, SLOT(quit())); #endif } } Fingerprinter::~Fingerprinter() { delete m_fpSource; } void Fingerprinter::onFingerprintSubmitted() { try { m_fp.decode( static_cast( sender() ) ); qDebug() << "Fingerprint success: " << m_fp.id(); } catch ( const lastfm::Fingerprint::Error& error ) { qWarning() << "Fingerprint error: " << error; QTimer::singleShot(250, qApp, SLOT(quit())); return; } #ifndef NDEBUG // This code will fetch the suggestions from the fingerprint id, one // day we might do something with this info, like offer corrections connect( m_fp.id().getSuggestions(), SIGNAL(finished()), SLOT(onGotSuggestions()) ); #else QTimer::singleShot(250, qApp, SLOT(quit())); #endif } void Fingerprinter::onGotSuggestions() { QMap suggestions = lastfm::FingerprintId::getSuggestions( static_cast( sender() ) ); qDebug() << suggestions; QTimer::singleShot(250, qApp, SLOT(quit())); } ================================================ FILE: app/fingerprinter/Fingerprinter.h ================================================ /* Copyright 2009 Last.fm Ltd. Copyright 2009 John Stamp This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #include #include #include namespace lastfm { class FingerprintableSource; } class Fingerprinter : public QObject { Q_OBJECT public: explicit Fingerprinter( const lastfm::Track& track, QObject* parent = 0 ); ~Fingerprinter(); private slots: void onFingerprintSubmitted(); void onGotSuggestions(); private: lastfm::Fingerprint m_fp; lastfm::FingerprintableSource* m_fpSource; lastfm::Track m_track; }; ================================================ FILE: app/fingerprinter/LAV_Source.cpp ================================================ /* * LAV_Source: use FFmpeg/Libav to extract audio for Last.fm fingerprinting * Copyright (C) 2012 John Stamp * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General 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 "LAV_Source.h" // Needed by libavutil/common.h #ifndef __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS 1 #endif #ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE #define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 #endif extern "C" { #include #include #include #if defined(HAVE_SWRESAMPLE) # include #elif defined (HAVE_AVRESAMPLE) # include # include #endif } #include #include #include #include /* defining the constant here to avoid using deprecated AVCODEC_MAX_AUDIO_FRAME_SIZE */ #define MAX_AUDIO_FRAME_SIZE 192000 using namespace std; /* Don't change these values! The fingerprinter expects signed 16 bit pcm w/ 1 or 2 channels */ const AVSampleFormat outSampleFmt = AV_SAMPLE_FMT_S16; const int outSampleSize = av_get_bytes_per_sample(outSampleFmt); const int outMaxChannels = 2; class LAV_SourcePrivate { public: LAV_SourcePrivate() : inFormatContext(NULL) , inCodecContext(NULL) #if defined(HAVE_SWRESAMPLE) || defined(HAVE_AVRESAMPLE) , resampleContext(NULL) #endif , streamIndex(-1) , duration(0) , timestamp(0) , bitrate(0) , eof(false) , overflowSize(0) { outBuffer = (uint8_t*)av_malloc(sizeof(uint8_t)*MAX_AUDIO_FRAME_SIZE*4); outBufferSize = sizeof(uint8_t)*MAX_AUDIO_FRAME_SIZE*4; overflow = (uint8_t*)av_malloc(sizeof(uint8_t)*MAX_AUDIO_FRAME_SIZE*4); } ~LAV_SourcePrivate() { av_free(outBuffer); av_free(overflow); } uint8_t * decodeOneFrame(int &dataSize, int &channels, int& nSamples); AVFormatContext *inFormatContext; AVCodecContext *inCodecContext; #if defined(HAVE_SWRESAMPLE) SwrContext *resampleContext; #elif defined(HAVE_AVRESAMPLE) AVAudioResampleContext *resampleContext; #endif int streamIndex; int duration; double timestamp; int bitrate; bool eof; uint8_t *outBuffer; size_t outBufferSize; uint8_t *overflow; size_t overflowSize; }; /** This reads the audio data from one frame, converts it to an acceptable * format (if needed), and returns a pointer to the the decoded data. * * @param dataSize bytes of decoded data * @param channels number of decoded channels * @param nSamples number of decoded samples */ uint8_t * LAV_SourcePrivate::decodeOneFrame(int &dataSize, int &channels, int& nSamples) { char buf[256]; AVPacket packet; AVFrame *decodedFrame = av_frame_alloc(); av_init_packet(&packet); int frameFinished = 0; dataSize = 0; channels = 0; nSamples = 0; while (!frameFinished) { int ret = av_read_frame(inFormatContext, &packet); if ( ret < 0) { if (ret == AVERROR_EOF || inFormatContext->pb->eof_reached) { eof = true; } else { av_strerror(ret, buf, sizeof(buf)); cerr << "Error reading frame: " << buf << endl; } av_free_packet(&packet); break; } if ( packet.stream_index != streamIndex ) { // Not the frame we're looking for; just read another av_free_packet(&packet); continue; } // Just skip this frame if there's a decode error ret = avcodec_decode_audio4(inCodecContext, decodedFrame, &frameFinished, &packet); if (ret < 0) { av_strerror(ret, buf, sizeof(buf)); cerr << "Error decoding audio: " << buf << endl; av_free_packet(&packet); break; } if (frameFinished) { dataSize = av_samples_get_buffer_size(NULL, inCodecContext->channels, decodedFrame->nb_samples, inCodecContext->sample_fmt, 1); channels = inCodecContext->channels; // As necessary, convert to outSampleFmt and outMaxChannels #if defined(HAVE_SWRESAMPLE) if (channels > outMaxChannels) channels = outMaxChannels; if (!resampleContext && (inCodecContext->sample_fmt != outSampleFmt || inCodecContext->channels != channels)) { int64_t inChannelLayout = (inCodecContext->channel_layout && inCodecContext->channels == av_get_channel_layout_nb_channels(inCodecContext->channel_layout)) ? inCodecContext->channel_layout : av_get_default_channel_layout(inCodecContext->channels); int64_t outChannelLayout = av_get_default_channel_layout(channels); resampleContext = swr_alloc_set_opts(NULL, outChannelLayout, outSampleFmt, decodedFrame->sample_rate, inChannelLayout, inCodecContext->sample_fmt, inCodecContext->sample_rate, 0, NULL); if (!resampleContext || swr_init(resampleContext) < 0) { cerr << "Cannot create sample rate converter from " << inCodecContext->sample_rate << " Hz " << av_get_sample_fmt_name(inCodecContext->sample_fmt) << " " << inCodecContext->channels << " channels to " << inCodecContext->sample_rate << " Hz " << av_get_sample_fmt_name(outSampleFmt) << " " << channels << " channels." << endl; av_free_packet(&packet); break; } } if (resampleContext) { int maxOutSamples = outBufferSize / channels / outSampleSize; int nSamplesOut = swr_convert(resampleContext, &outBuffer, maxOutSamples, const_cast(decodedFrame->extended_data), decodedFrame->nb_samples); if (nSamplesOut < 0) { cerr << "swr_convert failed" << endl; av_free_packet(&packet); break; } nSamples = nSamplesOut; dataSize = nSamplesOut * channels * outSampleSize; } else #elif defined(HAVE_AVRESAMPLE) if (channels > outMaxChannels) channels = outMaxChannels; if (!resampleContext && (inCodecContext->sample_fmt != outSampleFmt || inCodecContext->channels != channels)) { int64_t inChannelLayout = (inCodecContext->channel_layout && inCodecContext->channels == av_get_channel_layout_nb_channels(inCodecContext->channel_layout)) ? inCodecContext->channel_layout : av_get_default_channel_layout(inCodecContext->channels); int64_t outChannelLayout = av_get_default_channel_layout(channels); resampleContext = avresample_alloc_context(); if ( ! resampleContext ) { cerr << "Cannot allocate AVAudioResampleContext" << endl; av_free_packet(&packet); break; } av_opt_set_int(resampleContext, "in_channel_layout", inChannelLayout, 0); av_opt_set_int(resampleContext, "in_sample_fmt", inCodecContext->sample_fmt, 0); av_opt_set_int(resampleContext, "in_sample_rate", inCodecContext->sample_rate, 0); av_opt_set_int(resampleContext, "out_channel_layout", outChannelLayout, 0); av_opt_set_int(resampleContext, "out_sample_fmt", outSampleFmt, 0); av_opt_set_int(resampleContext, "out_sample_rate", inCodecContext->sample_rate, 0); // If both the input and output formats are s16 or u8, use s16 // as the internal sample format if (av_get_bytes_per_sample(inCodecContext->sample_fmt) <= 2 && av_get_bytes_per_sample(outSampleFmt) <= 2) { av_opt_set_int(resampleContext, "internal_sample_fmt", AV_SAMPLE_FMT_S16P, 0); } ret = avresample_open(resampleContext); if (ret < 0) { cerr << "Error opening libavresample" << endl; av_free_packet(&packet); break; } } if (resampleContext) { int outLinesize; int maxOutSamples = outBufferSize / channels / outSampleSize; av_samples_get_buffer_size(&outLinesize, channels, decodedFrame->nb_samples, outSampleFmt, 0); int nSamplesOut = avresample_convert(resampleContext, &outBuffer, outLinesize, maxOutSamples, decodedFrame->extended_data, decodedFrame->linesize[0], decodedFrame->nb_samples); if (nSamplesOut < 0) { cerr << "avresample_convert failed" << endl; av_free_packet(&packet); break; } nSamples = nSamplesOut; dataSize = nSamplesOut * channels * outSampleSize; } else #endif { nSamples = decodedFrame->nb_samples; memcpy(outBuffer, decodedFrame->data[0], dataSize); } } if ( packet.pts != AV_NOPTS_VALUE ) timestamp = av_q2d(inFormatContext->streams[streamIndex]->time_base)*packet.pts; av_free_packet(&packet); } timestamp += (double)nSamples / decodedFrame->sample_rate; av_frame_free(&decodedFrame); return outBuffer; } LAV_Source::LAV_Source() : d(new LAV_SourcePrivate()) { av_register_all(); avformat_network_init(); } LAV_Source::~LAV_Source() { release(); avformat_network_deinit(); delete d; } bool LAV_Source::eof() const { return (d->eof || d->inFormatContext->pb->eof_reached); } void LAV_Source::init(const QString& fileName) { // Assume that we want to start fresh if ( d->inFormatContext || d->inCodecContext ) release(); if ( avformat_open_input(&d->inFormatContext, QFile::encodeName(fileName), NULL, NULL ) < 0 ) { throw std::runtime_error ("Cannot open the media file!"); } if( avformat_find_stream_info(d->inFormatContext, NULL) < 0) { release(); throw std::runtime_error ("Cannot find stream info in the file!"); } // If there's a default video stream, we want the audio associated with it. // Otherwise just get the default audio stream. AVCodec* codec; int videoIndex = av_find_best_stream(d->inFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if ( videoIndex < 0 ) videoIndex = -1; d->streamIndex = av_find_best_stream(d->inFormatContext, AVMEDIA_TYPE_AUDIO, -1, videoIndex, &codec, 0); if ( d->streamIndex < 0 ) { release(); throw std::runtime_error ("No audio streams found in the file!"); } int64_t duration = d->inFormatContext->streams[d->streamIndex]->duration; // If the stream duration isn't available, get the duration from // AVFormatContext. This should give us the right duration for 99 point // whatever percent of cases. if ( duration < 1 ) { d->duration = d->inFormatContext->duration / AV_TIME_BASE; } else { AVRational timeBase = d->inFormatContext->streams[d->streamIndex]->time_base; d->duration = duration * timeBase.num / timeBase.den; } d->inCodecContext = d->inFormatContext->streams[d->streamIndex]->codec; // MP3 decodes to S16P, but we always want S16, so request it d->inCodecContext->request_sample_fmt = outSampleFmt; if ( !avcodec_open2(d->inCodecContext, codec, NULL) < 0 ) { release(); throw std::runtime_error ("Unable to open a compatible audio codec for the file!"); } if ( d->inCodecContext->bit_rate > 0 ) { d->bitrate = d->inCodecContext->bit_rate; } #if !defined(HAVE_SWRESAMPLE) && !defined(HAVE_AVRESAMPLE) if (d->inCodecContext->sample_fmt != outSampleFmt || d->inCodecContext->channels < 1 || d->inCodecContext->channels > 2) { release(); throw std::runtime_error ("The file has an incompatible sample format!"); } #endif } void LAV_Source::getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) { lengthSecs = d->duration; samplerate = d->inCodecContext->sample_rate; bitrate = d->bitrate; nchannels = d->inCodecContext->channels; #if defined(HAVE_SWRESAMPLE) || defined(HAVE_AVRESAMPLE) // What we promise to have if we need to convert the audio if (nchannels > outMaxChannels) nchannels = outMaxChannels; #endif } void LAV_Source::release() { if ( d->inCodecContext && d->inCodecContext->codec_id != AV_CODEC_ID_NONE ) { avcodec_close(d->inCodecContext); } if ( d->inFormatContext) { avformat_close_input(&d->inFormatContext); } #if defined(HAVE_SWRESAMPLE) || defined(HAVE_AVRESAMPLE) if ( d->resampleContext) { #if defined(HAVE_SWRESAMPLE) swr_free(&d->resampleContext); #elif defined(HAVE_AVRESAMPLE) avresample_free(&d->resampleContext); #endif } #endif d->inCodecContext = NULL; d->inFormatContext = NULL; d->streamIndex = -1; d->duration = 0; d->bitrate = 0; d->eof = false; d->overflowSize = 0; } void LAV_Source::skipSilence(double silenceThreshold /* = 0.0001 */) { silenceThreshold *= static_cast( numeric_limits::max() ); int dataSize, channels, nSamples; uint8_t *out = d->decodeOneFrame(dataSize, channels, nSamples); while(dataSize > 0) { double sum = 0; int16_t *buf = (int16_t*)out; switch ( channels ) { case 1: for (int j = 0; j < nSamples; j++) sum += abs( buf[j] ); break; case 2: for (int j = 0; j < nSamples; j+=2) sum += abs( (buf[j] >> 1) + (buf[j+1] >> 1) ); break; } if ( sum >= silenceThreshold * static_cast(nSamples) ) { break; } out = d->decodeOneFrame(dataSize, channels, nSamples); } avcodec_flush_buffers(d->inCodecContext); } void LAV_Source::skip(const int mSecs) { double targetTimestamp = d->timestamp + mSecs/1000.0; int dataSize, channels, nSamples; for (;;) { d->decodeOneFrame(dataSize, channels, nSamples); if ( d->timestamp > targetTimestamp || d->eof ) return; } } int LAV_Source::updateBuffer(signed short* pBuffer, size_t bufferSize) { size_t bufferFill = 0; int dataSize; if ( d->overflowSize ) { memcpy( pBuffer, d->overflow, d->overflowSize ); bufferFill = d->overflowSize/outSampleSize; d->overflowSize = 0; } uint8_t * out; while(bufferFill < bufferSize) { int channels, nb_samples; out = d->decodeOneFrame(dataSize, channels, nb_samples); if (!dataSize) break; // Only put as many bytes in pBuffer as will fit; save the rest for the // next call to updateBuffer int bytesToBuffer = (bufferSize - bufferFill)*outSampleSize; if ( bytesToBuffer < dataSize ) { d->overflowSize = dataSize - bytesToBuffer; memcpy( d->overflow, out + bytesToBuffer, d->overflowSize); } else { bytesToBuffer = dataSize; } memcpy( pBuffer + bufferFill, out, bytesToBuffer ); bufferFill += bytesToBuffer/outSampleSize; } return bufferFill; } ================================================ FILE: app/fingerprinter/LAV_Source.h ================================================ /* * LAV_Source: use FFmpeg/Libav to extract audio for Last.fm fingerprinting * Copyright (C) 2012 John Stamp * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General 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 LAV_SOURCE_H #define LAV_SOURCE_H #include using namespace std; class LAV_Source : public lastfm::FingerprintableSource { public: LAV_Source(); ~LAV_Source(); void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels); void init(const QString& fileName); void release(); int updateBuffer(signed short* pBuffer, size_t bufferSize); void skip(const int mSecs); void skipSilence(double silenceThreshold = 0.0001); bool eof() const; private: class LAV_SourcePrivate * const d; }; #endif ================================================ FILE: app/fingerprinter/fingerprinter.pro ================================================ TARGET = fingerprinter QT = core network xml sql CONFIG += lastfm unicorn logger fingerprint ffmpeg CONFIG -= app_bundle include( ../../admin/include.qmake ) # TODO: FIX THIS: I think this means that we can only build bundles mac { DESTDIR = "../../_bin/Last.fm Scrobbler.app/Contents/Helpers" QMAKE_POST_LINK += ../../admin/dist/mac/bundleFrameworks.sh \"$$DESTDIR/$$TARGET\" } SOURCES += main.cpp \ Fingerprinter.cpp \ LAV_Source.cpp HEADERS += LAV_Source.h \ Fingerprinter.h DEFINES += LASTFM_COLLAPSE_NAMESPACE LASTFM_FINGERPRINTER ================================================ FILE: app/fingerprinter/main.cpp ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of liblastfm. liblastfm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liblastfm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ #include "Fingerprinter.h" #include "lib/unicorn/UnicornCoreApplication.h" #include #include #include #include // ./fingerprinter --username --filename --title --album <album> --artist <artist> int main(int argc, char *argv[]) { int exitCode = -1; QtSingleCoreApplication::setApplicationName( "Last.fm Fingerprinter" ); QtSingleCoreApplication::setOrganizationName( "Last.fm" ); unicorn::CoreApplication a(argc, argv); qDebug() << a.arguments(); int usernameIndex = a.arguments().indexOf( "--username" ); int filenameIndex = a.arguments().indexOf( "--filename" ); if ( usernameIndex != -1 && filenameIndex != -1 ) { // username and filename are required fields lastfm::ws::Username = a.arguments().at( usernameIndex + 1 ); // create the track from the command line arguments MutableTrack track; track.setUrl( QUrl::fromLocalFile( a.arguments().at( filenameIndex + 1 ) ) ); int titleIndex = a.arguments().indexOf( "--title" ); int albumIndex = a.arguments().indexOf( "--album" ); int artistIndex = a.arguments().indexOf( "--artist" ); if ( titleIndex != -1 ) track.setTitle( a.arguments().at( titleIndex + 1 ) ); if ( albumIndex != -1 ) track.setAlbum( a.arguments().at( albumIndex + 1 ) ); if ( artistIndex != -1 ) track.setTitle( a.arguments().at( artistIndex + 1 ) ); Fingerprinter* fingerprinter = new Fingerprinter( track ); exitCode = a.exec(); delete fingerprinter; } else { qWarning() << "Usage: fingerprinter --username <username> --filename <filename> --title <title> --album <album> --artist <artist>"; } return exitCode; } ================================================ FILE: app/twiddly/IPod.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "lib/unicorn/mac/AppleScript.h" #include "IPod.h" #include "IPodScrobble.h" #include "ITunesLibrary.h" #include "PlayCountsDatabase.h" #include "common/qt/msleep.cpp" #include "plugins/iTunes/ITunesExceptions.h" #include <lastfm/misc.h> #include <lastfm/Track.h> #include <QtCore> #include <QtXml> #include <iostream> IPod* //static IPod::fromCommandLineArguments( const QStringList& args ) { #if 0 // handy for debug QStringList myArgs = QString( "--device ipod --connection usb --pid 4611 --vid 1452 --serial 000000C8B035" ).split( ' ' ); //QStringList myArgs = QString( "--bootstrap" ).split( ' ' ); #define args myArgs #endif QMap<QString, QString> map; QListIterator<QString> i( args ); while (i.hasNext()) { QString arg = i.next(); if (!arg.startsWith( "--" ) || !i.hasNext() || i.peekNext().startsWith( "--" )) continue; arg = arg.mid( 2 ); // get all the strings up until the end or the next -- QString value = i.next(); while ( i.hasNext() && !i.peekNext().startsWith("--") ) value.append( " " + i.next() ); map[arg] = value; } IPod* ipod; if (args.contains( "--manual" )) ipod = new ManualIPod; else ipod = new AutomaticIPod; #define THROW_IF_EMPTY( x ) ipod->x = map[#x]; if (ipod->x.isEmpty()) throw "Could not resolve argument: --" #x; THROW_IF_EMPTY( device ); THROW_IF_EMPTY( vid ); THROW_IF_EMPTY( pid ); THROW_IF_EMPTY( serial ); // use device for name if name wasn't sent ipod->name = map["name"]; if (ipod->name.isEmpty()) ipod->name = ipod->serial; #undef THROW_IF_EMPTY return ipod; } QString IPod::scrobbleId() const { #ifdef Q_OS_MAC const char* os = "Macintosh"; #elif defined Q_OS_WIN const char* os = "Windows"; #else #error What kind of operating system are you?! \ I am a home-made one. #endif return vid + '-' + pid + '-' + device + '-' + os; } QDir IPod::saveDir() const { QDir d = lastfm::dir::runtimeData().filePath( "devices/" + uid() ); d.mkpath( "." ); return d; } QDomDocument IPod::ScrobbleList::xml() const { QDomDocument xml; QDomElement root = xml.createElement( "submissions" ); root.setAttribute( "product", "Twiddly" ); QListIterator<Track> i( *this ); while (i.hasNext()) root.appendChild( i.next().toDomElement( xml ) ); xml.appendChild( root ); return xml; } /** iPod plays are determined using our iTunes Plays sqlite database and * comparing that with the actual state of the iTunes Library database after * an iPod is synced with it */ void IPod::twiddle() { PlayCountsDatabase& db = *playCountsDatabase(); ITunesLibrary& library = *iTunesLibrary(); QList<ITunesLibrary::Track> tracksToUpdate; QList<ITunesLibrary::Track> tracksToInsert; QList<ITunesLibrary::Track> tracksToScrobble; int nullTrackCount = 0; // If creation of the library class failed due to a dialog showing in iTunes // or COM not responding for some other reason, hasTracks will just return false // and we will quit this run. while ( library.hasTracks() ) { try { ITunesLibrary::Track track = library.nextTrack(); if ( track.isNull() ) { // Failed to read current iTunes track. Either something went wrong, // or the track was not found on the disk despite being in the iTunes library. // Don't log every error as this could be a library full of iTunes Match tracks // just count how many failed and say at the end ++nullTrackCount; continue; } QString id = track.uniqueId(); // We don't know about this track yet, this means either:- // 1. The track was added to iTunes since the last sync. thus it is // impossible for it to have been played on the iPod // 2. On Windows, the path of the track changed since the last sync. // Since we don't have persistent IDs on Windows we have no way of // matching up this track up with its previous incarnation. Thus // we don't scrobble it as we have no idea if it was played or not // chances are, it wasn't PlayCountsDatabase::Track dbTrack = db[id]; if ( dbTrack.isNull() ) { tracksToInsert << track; continue; } const int diff = track.playCount() - dbTrack.playCount(); // can throw if ( diff > 0 ) tracksToScrobble << track; // a worthwhile optimisation since updatePlayCount() is really slow // NOTE negative diffs *are* possible if ( diff < 0 ) tracksToUpdate << track; // can throw } catch ( ITunesException& ) { // Carry on... } } qDebug() << "There were " << nullTrackCount << " null tracks"; if ( tracksToUpdate.count() + tracksToInsert.count() + tracksToScrobble.count() > 0 ) { // We've got some updates and inserts to do so lock the database and do them db.beginTransaction(); foreach ( const ITunesLibrary::Track& track, tracksToScrobble ) { try { ::Track t = track.lastfmTrack(); // can throw // Because we take a snapshot of our playcounts db there is a possible race condition. // use db.track here to fetch the latest playcount now that we've locked the database const int diff = track.playCount() - db.track( track.uniqueId() ).playCount(); if ( !t.isNull() ) { if ( t.timestamp().secsTo( QDateTime::currentDateTime() ) > 30 ) { // we only scrobble tracks with a timestamp older than 30 seconds // to give the iTunes plugin time so update the playcount db // after a track change - bit of a hack, but it stops spurious iPod scrobbles // update the playcount db to the current playcount for this track // this means that we won't try to scrobble the track again db.update( track ); IPodScrobble t2( t ); t2.setPlayCount( diff ); t2.setMediaDeviceId( scrobbleId() ); t2.setUniqueId( track.uniqueId() ); m_scrobbles += t2; qDebug() << diff << "scrobbles found for" << t; } else { qDebug() << "Timestamp less than 30 seconds. Don't scrobble yet."; } } else { qWarning() << "Couldn't get Track for" << track.uniqueId(); // We get here if COM fails to populate the Track for whatever reason. // Therefore we continue and don't let the local db update. That way we // maintain the diff and we should be picking up on it next time twiddly // runs. continue; } } catch ( ITunesException& ) { // Carry on... } } // this should just be tracks with negative playcount diffs foreach ( const ITunesLibrary::Track& track, tracksToUpdate ) db.update( track ); // insert all the new tracks we've found foreach ( const ITunesLibrary::Track& track, tracksToInsert ) db.insert( track ); db.endTransaction(); } delete &db; delete &library; } #ifdef Q_OS_MAC ManualIPod::ManualIPod() : m_pid( firstPid() ) {} QString //static ManualIPod::firstPid() { AppleScript script; script << "tell application 'iTunes' to " "return persistent ID of some source whose kind is iPod"; // wait for iPod to exist, since we're launched early by the plugin // and it may not be in iTunes yet, 10 seconds is more than enough // without being excessive QString pid; for( int x = 0; x < 20 && (pid = script.exec()).isEmpty(); ++x ) { Qt::msleep( 500 ); } return pid; } #else ManualIPod::ManualIPod() {} #endif ================================================ FILE: app/twiddly/IPod.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "ITunesLibrary.h" #include "IPodScrobble.h" #include "IPodSettings.h" #include "PlayCountsDatabase.h" #include <QDir> #include <QDomDocument> #include <QStringList> /** @author <max@last.fm> */ class IPod { public: virtual ~IPod() {} /** you own the memory */ static IPod* fromCommandLineArguments( const QStringList& ); /** figures out the iPod scrobbles */ void twiddle(); /** allows us to encapsulate the real scrobble count() */ class ScrobbleList : private QList<Track> { int m_realCount; public: ScrobbleList() : m_realCount( 0 ) {} using QList<Track>::isEmpty; QDomDocument xml() const; int count() const { return m_realCount; } ScrobbleList& operator+=( const Track& t ) { m_realCount += IPodScrobble(t).playCount(); append( t ); return *this; } #ifdef WIN32 /** verbose, but named to make it clear you're expected to set uniqueIDs * on tracks added to the ScrobbleList on Windows, at least if you want * to remove them */ void removeAllWithUniqueId( const QString& uniqueId ); #endif void clear() { m_realCount = 0; QList<Track>::clear(); } }; /** this must be writable by Qt as a path on all platforms */ QString uid() const { return device + '/' + serial; } ScrobbleList scrobbles() const { return m_scrobbles; } ScrobbleList& scrobbles() { return m_scrobbles; } //so we can clear() IPodSettings settings() const { return IPodSettings( device + '/' + serial ); } /** assigned to the mediaDeviceId on TrackInfo objects */ QString scrobbleId() const; /** every device has its own directory for storing stuff */ QDir saveDir() const; QString device; QString vid; QString pid; //product id, not persistent id QString serial; QString name; protected: ScrobbleList m_scrobbles; /** heap allocate and return those relevent to your iPod type */ virtual class PlayCountsDatabase* playCountsDatabase() = 0; virtual class ITunesLibrary* iTunesLibrary() = 0; }; class AutomaticIPod : public IPod { public: class PlayCountsDatabase : public ::PlayCountsDatabase { public: PlayCountsDatabase(); /** duplicates the contents of the iTunes Library into our database */ void bootstrap(); bool isBootstrapNeeded() const; }; private: virtual PlayCountsDatabase* playCountsDatabase() { return new PlayCountsDatabase; } virtual ITunesLibrary* iTunesLibrary() { return new ITunesLibrary; } }; class ManualIPod : public IPod { public: ManualIPod(); class PlayCountsDatabase : public ::PlayCountsDatabase { public: PlayCountsDatabase( class IPod const * const ipod ) : ::PlayCountsDatabase( ipod->saveDir().filePath( "playcounts.db" ) ) {} }; class Library : public ITunesLibrary { public: Library( const QString& pid ) : ITunesLibrary( pid, true ) {} }; private: virtual PlayCountsDatabase* playCountsDatabase() { return new PlayCountsDatabase( this ); } virtual ITunesLibrary* iTunesLibrary() { return new Library( m_pid ); } /** persistent ID of the iPod source, mac only */ QString const m_pid; #ifdef Q_OS_MAC static QString firstPid(); #endif }; ================================================ FILE: app/twiddly/IPodScrobble.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef IPOD_SCROBBLE_H #define IPOD_SCROBBLE_H #include <lastfm/Track.h> struct IPodScrobble : public MutableTrack { IPodScrobble() {} IPodScrobble( const Track& that ) : MutableTrack( that ) {} QString uniqueId() const { return extra( "uniqueId" ); } int playCount() const { return extra( "playCount" ).toInt(); } void setPlayCount( int const i ) { setExtra( "playCount", QString::number( i ) ); } void setMediaDeviceId( const QString& id ) { setExtra( "deviceId", id ); } void setUniqueId( const QString& id ) { setExtra( "uniqueId", id ); } }; #endif ================================================ FILE: app/twiddly/IPodSettings.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "lib/unicorn/UnicornSettings.h" enum IPodType { IPodUnknownType, IPodAutomaticType, IPodManualType }; /** Used by app/client so don't add anything specific to Twiddly * <max@last.fm> */ class IPodSettings { class Settings : public unicorn::AppSettings { public: Settings( IPodSettings const * const s ) { beginGroup( "device/" + s->m_uid ); } }; friend class IPod; friend class IPodScrobbleCache; friend class Settings; QString m_uid; IPodSettings( const QString& uid ) : m_uid( uid ) {} public: IPodType type() const { return (IPodType)Settings( this ).value( "type" ).toInt(); } void setType( IPodType t ) { Settings( this ).setValue( "type", (int)t ); } QDateTime lastSync() const { return Settings( this ).value( "LastSync" ).toDateTime(); } void setLastSync( const QDateTime& time ) { Settings( this ).setValue( "LastSync", time ); } }; ================================================ FILE: app/twiddly/ITunesLibrary.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef ITUNES_LIBRARY_H #define ITUNES_LIBRARY_H #include "ITunesLibraryTrack.h" #include <lastfm/Track.h> #include <QList> /** @author Max Howell <max@last.fm> - Mac * @author <erik@last.fm> - Win * * This class offers easy access to the iTunes Library database * It uses AppleScript and COM to access and query iTunes */ class ITunesLibrary { public: /** the isIPod bool is for Windows only, the source, mac :( */ ITunesLibrary( const QString& source = "", bool isIPod = false ); // throws ~ITunesLibrary(); typedef ITunesLibraryTrack Track; bool hasTracks() const; Track nextTrack(); int trackCount() const; private: uint m_currentIndex; #ifdef WIN32 class ITunesComWrapper* m_com; long m_trackCount; bool const m_isIPod; #else QList<Track> m_tracks; #endif private: Q_DISABLE_COPY( ITunesLibrary ); }; #endif ================================================ FILE: app/twiddly/ITunesLibraryTrack.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef ITUNES_LIBRARY_TRACK_H #define ITUNES_LIBRARY_TRACK_H #ifdef WIN32 #include <lastfm/Track.h> #include "plugins/iTunes/ITunesTrack.h" #include <QSharedData> #include <QSharedDataPointer> namespace COM { using ::ITunesTrack; } /** @author <max@last.fm> * * We are QSharedData to use polymorphism but still allow * ITunesLibraryTracks to be passed by value. * * It would be nicer to derive ITunesTrack, but ITunesComWrapper is not * designed for it, and ITunesTrack is not virtual enough, so we have this * mess instead */ struct ITunesLibraryTrackData : public QSharedData { ITunesLibraryTrackData( const ITunesTrack& t ) : i( t ) {} virtual ~ITunesLibraryTrackData() {} /** the path is the uniqueId for iTunes library tracks on Windows */ virtual QString uniqueId() const { return QString::fromStdWString( i.persistentId() ); } /** @returns false if the track doesn't exist in the iTunes Library */ virtual bool isNull() const { return i.isNull(); } /** @returns -1 if COM failure or isNull() */ int playCount() const { return i.playCount(); } COM::ITunesTrack i; }; /** @author <max@last.fm> * @brief represents a track from an iPod source * * NOTE only used for manual iPod scrobbling, since iPods have no path, this * is the only unique ID we have available :( */ struct IPodLibraryTrackData : public ITunesLibraryTrackData { IPodLibraryTrackData( const ITunesTrack& t ) : ITunesLibraryTrackData( t ) {} /** isNull from COM::ITunesTrack returns true if m_path is null too * * so we can't rely on that at all :( */ virtual bool isNull() const { return uniqueId().remove( '\t' ).isEmpty(); } /** iTunes returns empty paths for tracks stored on the iPod */ virtual QString uniqueId() const { return QString::fromStdWString( i.artist() + L'\t' + i.track() + L'\t' + i.album() ); } }; /** @author <max@last.fm> */ class ITunesLibraryTrack { friend class ITunesLibrary; ITunesLibraryTrack() {} QSharedDataPointer<ITunesLibraryTrackData> d; public: bool isNull() const { return !d || d->isNull(); } QString uniqueId() const { Q_ASSERT( d ); return d->uniqueId(); } QString persistentId() const { return uniqueId(); } int playCount() const { Q_ASSERT( d ); return d->playCount(); } /** @returns a TrackInfo object filled out with minimal information, * check to see if what you want is assigned before assuming so! * It gets the data from iTunes using AppleScript/COM * * @defined ITunesLibrary.cpp */ lastfm::Track lastfmTrack() const; }; #else //MAC #include <lastfm/Track.h> #include "PlayCountsDatabase.h" template <typename T> class QList; /** @author <max@last.fm> * private because there is no "isA" relationship */ class ITunesLibraryTrack : private PlayCountsDatabase::Track { friend class ITunesLibrary; friend class QList<ITunesLibraryTrack>; ITunesLibraryTrack() // for QList only {} public: ITunesLibraryTrack( const QString& uid, int c ) : PlayCountsDatabase::Track( uid, c ) {} lastfm::Track lastfmTrack() const; using PlayCountsDatabase::Track::isNull; using PlayCountsDatabase::Track::uniqueId; using PlayCountsDatabase::Track::playCount; QString persistentId() const { return uniqueId(); } private: /** the persistent ID of the source for this track, if empty, we use * the default iTunes library */ QString m_sourcePersistentId; }; #endif #endif ================================================ FILE: app/twiddly/ITunesLibrary_mac.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "ITunesLibrary.h" #include "IPodScrobble.h" #include "lib/unicorn/mac/AppleScript.h" #include <QDateTime> #include <QFileInfo> #include <QStringList> #include <QDebug> ITunesLibrary::ITunesLibrary( const QString& pid, bool ) : m_currentIndex( 0 ) { QString source; if (!pid.isEmpty()) source = "tell first source whose persistent ID is \"" + pid + "\" to "; AppleScript script( "tell application \"iTunes\" to " + source + "get {persistent ID, played count } of every track in library playlist 1" ); QString out = script.exec(); if (out.isEmpty()) throw "Failed to get iTunes library contents"; out.chop( 2 ); //remove encapsulating }} out.remove( 0, 2 ); //remove encapsulating {{ QStringList parts = out.split( ", " ); const int N = parts.count() / 2; parts[N-1].chop( 1 ); //remove encapsulating } parts[N].remove( 0, 1 ); //remove encapsulating { for (int i = 0; i < N; ++i) { QString const uid = parts[i].remove( '"' ); QString const plays = parts[N+i]; ITunesLibrary::Track t( uid, plays.toInt() ); t.m_sourcePersistentId = pid; m_tracks += t; } qDebug() << "Found" << m_tracks.count() << "tracks"; } ITunesLibrary::~ITunesLibrary() {} bool ITunesLibrary::hasTracks() const { return m_currentIndex < (uint)m_tracks.count(); } ITunesLibrary::Track ITunesLibrary::nextTrack() { return m_tracks.value( m_currentIndex++ ); } int ITunesLibrary::trackCount() const { return m_tracks.count(); } static QDateTime qDateTimeFromScriptString( const QString& s ) { QList<int> parts; foreach (const QString& part, s.split( ':' )) parts += part.toInt(); if (parts.count() < 4) return QDateTime(); QDate d( parts[0], parts[1], parts[2] ); QTime t; t = t.addSecs( parts[3] ); return QDateTime( d, t ); } ::Track ITunesLibrary::Track::lastfmTrack() const { // NOTE we only what data we require for scrobbling, though we could fill in // more of the Track object IPodScrobble t; t.setSource( ::Track::MediaDevice ); QString source; if (!m_sourcePersistentId.isEmpty()) source = "tell first source whose persistent ID is '" + m_sourcePersistentId + "' to "; // TODO compile once and pass pid as argument // NOTE trust me, the code is ugly, but doesn't work any other way, don't clean it up! AppleScript script; script << "tell application 'iTunes'" << source + "set lib to library playlist 1" << "set t to first track of lib whose persistent ID is '" + persistentId() + "'" << "set d to played date of t" << "end tell" << "try" << "set d to (year of d) & ':' & " "((month of d) as integer) & ':' & " "(day of d) & ':' & " "(time of d)" << "end try" << "tell application 'iTunes' to tell t" << "set l to \"\"" << "try" << "set l to location" << "set l to POSIX path of l" << "end try" << "return artist & '\n' & album artist & '\n' & name & '\n' & (duration as integer) & '\n' & album & '\n' & played count & '\n' & d & '\n' & l & '\n' & podcast & '\n' & video kind" << "end tell"; QString out = script.exec(); QTextStream s( &out, QIODevice::ReadOnly ); t.setArtist( s.readLine() ); t.setAlbumArtist( s.readLine() ); t.setTitle( s.readLine() ); t.setDuration( (uint) s.readLine().toFloat() ); t.setAlbum( s.readLine() ); t.setPlayCount( s.readLine().toInt() ); t.setTimeStamp( qDateTimeFromScriptString( s.readLine() ) ); QFileInfo fileinfo( s.readLine() ); t.setUrl( QUrl::fromLocalFile( fileinfo.absolutePath() ) ); t.setPodcast( s.readLine() == "true" ); QString videoKind = s.readLine(); t.setVideo( videoKind != "none" && videoKind != "music video" ); return t; } ================================================ FILE: app/twiddly/ITunesLibrary_win.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "ITunesLibrary.h" #include "IPodScrobble.h" #include "common/qt/msleep.cpp" #include "plugins/iTunes/ITunesComWrapper.h" #include <cassert> #include <QDateTime> #include <QFileInfo> #include <QDebug> using namespace std; ITunesLibrary::ITunesLibrary( const QString&, bool const iPod ) : m_currentIndex( 0 ), m_isIPod( iPod ), m_trackCount( -1 ) { m_com = new ITunesComWrapper(); // This will throw if it fails if ( !iPod ) m_trackCount = m_com->libraryTrackCount(); else { // loop 20 twenty times as sometimes the iPod isn't in iTunes yet and // the plugin has called us prematurely for( int x = 0; x < 20 && m_trackCount == -1; x++) { if (x) Qt::msleep( 500 ); // the first ipod is used, which isn't perfect, but oh well m_trackCount = m_com->iPodLibraryTrackCount(); } } qDebug() << "Found " << m_trackCount << " tracks in iTunes library"; } ITunesLibrary::~ITunesLibrary() { delete m_com; } bool ITunesLibrary::hasTracks() const { return (int)m_currentIndex < m_trackCount; } int ITunesLibrary::trackCount() const { return m_trackCount; } ITunesLibrary::Track ITunesLibrary::nextTrack() { // Weird increment is because if this call throws we still want to // have increased m_currentIndex. Otherwise we can get stuck in an // infinite loop calling this function over and over. COM::ITunesTrack it = m_com->track( (++m_currentIndex) - 1 ); ITunesLibrary::Track t; t.d = (m_isIPod) ? new IPodLibraryTrackData( it ) : new ITunesLibraryTrackData( it ); return t; } Track ITunesLibrary::Track::lastfmTrack() const { // TODO: why are we doing this? It will return true for manual tracks as it checks the path. if ( isNull() ) return Track(); COM::ITunesTrack i = d->i; // These will throw if something goes wrong IPodScrobble t; t.setArtist( QString::fromStdWString( i.artist() ) ); t.setAlbumArtist( QString::fromStdWString( i.albumArtist() ) ); t.setTitle( QString::fromStdWString( i.track() ) ); t.setDuration( i.duration() ); t.setAlbum( QString::fromStdWString( i.album() ) ); t.setPlayCount( i.playCount() ); t.setPodcast( i.podcast() ); t.setVideo( i.video() ); QDateTime stamp = QDateTime::fromString( QString::fromStdWString( i.lastPlayed() ), "yyyy-MM-dd hh:mm:ss" ); if ( stamp.isValid() ) { uint unixTime = stamp.toTime_t(); // This is to prevent the spurious scrobble bug that can happen if iTunes is // shut during twiddling. The timestamp returned in that case was FFFFFFFF // so let's check for that. if ( unixTime == 0xFFFFFFFF ) { qWarning() << "Caught a 0xFFFFFFFF timestamp, assume it's the scrobble of spury:" << QString::fromStdWString( i.toString() ); return Track(); } t.setTimeStamp( stamp ); } else { // If we don't have a valid timestamp, set to current time. Should work. We hope. qWarning() << "Invalid timestamp, set to 60 seconds ago:" << QString::fromStdWString( i.toString() ); t.setTimeStamp( QDateTime::currentDateTime().addSecs( -60 ) ); } const QString path = QString::fromStdWString( i.path() ); t.setUrl( QUrl::fromLocalFile( QFileInfo( path ).absoluteFilePath() ) ); t.setSource( Track::MediaDevice ); return t; } ================================================ FILE: app/twiddly/PlayCountsDatabase.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "PlayCountsDatabase.h" #include "TwiddlyApplication.h" #include "IPod.h" #include "ITunesLibrary.h" #include "common/qt/msleep.cpp" #include "common/c++/fileCreationTime.cpp" #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/mac/AppleScript.h" #include <lastfm/misc.h> #include <QSqlDatabase> #include <QSqlError> #include <QSqlQuery> #include <QTemporaryFile> #include <iostream> #include <QDebug> #define TABLE_NAME_OLD "itunes_db" #define TABLE_NAME "playcounts" #define SCHEMA "persistent_id VARCHAR( 32 ) PRIMARY KEY," \ "play_count INTEGER" #define INDEX "persistent_id" /** @author Max Howell <max@last.fm> * @brief automatically log sql errors */ namespace QtOverrides { class SqlQuery : public ::QSqlQuery { // this is called arse because both check and verify wouldn't compile! bool arse( bool success ) { if (!success) qWarning() << lastError().text() << "in query:\n" << lastQuery(); return success; } public: SqlQuery( QSqlDatabase db ) : QSqlQuery( db ) {} bool exec() { return arse( QSqlQuery::exec() ); } bool exec( const QString& sql ) { return arse( QSqlQuery::exec( sql ) ); } }; } #define QSqlQuery QtOverrides::SqlQuery PlayCountsDatabase::PlayCountsDatabase( const QString& path ) : m_path( path ) { qDebug() << path; m_db = QSqlDatabase::addDatabase( "QSQLITE", path /*connection-name*/ ); m_db.setDatabaseName( path ); m_db.open(); if ( !m_db.isValid() ) throw "Could not open " + path; // rename the old table name if it exists if ( m_db.tables().contains(TABLE_NAME_OLD) ) { QSqlQuery deleteQuery( m_db ); deleteQuery.exec( QString( "ALTER TABLE itunes_db RENAME TO itunes_db_%1" ).arg( QDateTime::currentDateTimeUtc().toString( "yyyyMMddhhmmsszzz" ) ) ); } // create the playcounts table if it doesn't already exist if ( !m_db.tables().contains( TABLE_NAME ) ) { QSqlQuery query( m_db ); query.exec( "CREATE TABLE " TABLE_NAME " ( " SCHEMA " );" ); query.exec( "CREATE INDEX " INDEX "_idx ON " TABLE_NAME " ( " INDEX " );" ); } m_query = new QSqlQuery( m_db ); m_query->prepare( "SELECT play_count FROM " TABLE_NAME " WHERE persistent_id = :pid LIMIT 1" ); QSqlQuery snapshotQuery( m_db ); snapshotQuery.exec( "SELECT persistent_id, play_count FROM " TABLE_NAME " ORDER BY persistent_id ASC" ); if ( snapshotQuery.first() ) { do { bool ok; int count = snapshotQuery.value( 1 ).toInt( &ok ); if ( ok ) m_snapshot[snapshotQuery.value(0).toString()] = count; } while ( snapshotQuery.next() ); } } PlayCountsDatabase::~PlayCountsDatabase() { // NOTE don't do this! It closes any copies too, but if we let Qt handle it // it only closes the connection for the last db instance //m_db.close(); delete m_query; } PlayCountsDatabase::Track PlayCountsDatabase::operator[]( const QString& uid ) { if ( m_snapshot.contains( uid ) ) return Track( uid, m_snapshot[uid] ); return Track(); } PlayCountsDatabase::Track PlayCountsDatabase::track( const QString& uid ) { m_query->bindValue( ":pid", uid ); m_query->exec(); Q_ASSERT( m_query->size() < 2 ); if ( m_query->first() ) { bool ok; int count = m_query->value( 0 ).toInt( &ok ); if ( ok ) { return Track( uid, count ); } } return Track(); } void PlayCountsDatabase::beginTransaction() { qDebug() << "beginTransaction"; // we try until blue in the face to begin the transaction, as we really, // really want db lock QSqlQuery q( m_db ); q.exec( "BEGIN TRANSACTION" ); for (int i = 5; q.lastError().type() == QSqlError::ConnectionError && i; i--) { // we only try 5 times since SQLITE_BUSY is just one of the // possible things that ConnectionError might mean LOG( 3, "SQLite might be busy trying again in 25ms..." ); Qt::msleep( 25 ); q.exec(); } } void PlayCountsDatabase::endTransaction() { qDebug() << "endTransaction"; // FIXME: what happens if exec returns false? QSqlQuery( m_db ).exec( "END TRANSACTION;" ); } bool PlayCountsDatabase::insert( const ITunesLibrary::Track& track ) { QString playCount = QString::number( track.playCount() ); QString sql = "INSERT OR ROLLBACK INTO " TABLE_NAME " ( persistent_id, play_count ) " "VALUES ( '" + track.persistentId() + "', '" + playCount + "' )"; return QSqlQuery( m_db ).exec( sql ); } bool PlayCountsDatabase::update( const ITunesLibrary::Track& track ) { QString playCount = QString::number( track.playCount() ); QString sql = "UPDATE OR ROLLBACK " TABLE_NAME " " "SET play_count='" + playCount + "' " "WHERE persistent_id='" + track.persistentId() + "'"; return QSqlQuery( m_db ).exec( sql ); } AutomaticIPod::PlayCountsDatabase::PlayCountsDatabase() #ifdef Q_OS_MAC : ::PlayCountsDatabase( lastfm::dir::runtimeData().filePath( "iTunesPlays.db" ) ) #else : ::PlayCountsDatabase( lastfm::dir::runtimeData().filePath( "Client/iTunesPlays.db" ) ) #endif {} bool AutomaticIPod::PlayCountsDatabase::isBootstrapNeeded() const { QSqlQuery q( m_db ); q.exec( "SELECT value FROM metadata WHERE key='bootstrap_complete'" ); if (q.next() && q.value( 0 ).toString() == "true") return false; return true; } static QString pluginPath() { #ifdef Q_OS_MAC QString path = std::getenv( "HOME" ); path += "/Library/iTunes/iTunes Plug-ins/AudioScrobbler.bundle/Contents/MacOS/AudioScrobbler"; return path; #else QSettings settings( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Last.fm\\Client\\Plugins\\", QSettings::NativeFormat ); QString path = settings.value( "itw/Path" ).toString(); if (path.isEmpty()) throw "Unknown iTunes plugin path"; return path; #endif } void AutomaticIPod::PlayCountsDatabase::bootstrap() { qDebug() << "Starting bootstrapping..."; static_cast<TwiddlyApplication*>(qApp)->sendBusMessage( "container://Notification/Twiddly/Bootstrap/Started" ); beginTransaction(); QSqlQuery query( m_db ); // this will fail if the metadata table doesn't exist, which is fine query.exec( "DELETE FROM metadata WHERE key='bootstrap_complete'" ); query.exec( "DELETE FROM metadata WHERE key='plugin_ctime'" ); query.exec( "DELETE FROM " TABLE_NAME_OLD ); query.exec( "DELETE FROM " TABLE_NAME ); ITunesLibrary lib; // for wizard progress screen std::cout << lib.trackCount() << std::endl; int i = 0; while (lib.hasTracks()) { try { ITunesLibrary::Track const t = lib.nextTrack(); QString const plays = QString::number( t.playCount() ); query.exec( "INSERT OR IGNORE INTO " TABLE_NAME " ( persistent_id, play_count ) " "VALUES ( '" + t.uniqueId() + "', '" + plays + "' )" ); } catch ( ... ) { // Move on... } std::cout << ++i << std::endl; } // if either INSERTS fail we'll rebootstrap next time query.exec( "CREATE TABLE metadata (key VARCHAR( 32 ), value VARCHAR( 32 ))" ); query.exec( "INSERT INTO metadata (key, value) VALUES ('bootstrap_complete', 'true')" ); QString const t = QString::number( common::fileCreationTime( pluginPath() ) ); query.exec( "INSERT INTO metadata (key, value) VALUES ('plugin_ctime', '"+t+"')" ); endTransaction(); static_cast<TwiddlyApplication*>(qApp)->sendBusMessage( "container://Notification/Twiddly/Bootstrap/Finished" ); } ================================================ FILE: app/twiddly/PlayCountsDatabase.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PLAY_COUNT_DATABASE_H #define PLAY_COUNT_DATABASE_H #include <QString> #include <QSqlDatabase> #include <QHash> class QSqlQuery; class ITunesLibraryTrack; /** @author Jono Cole <jono@last.fm> * @refactor Max Howell <max@last.fm> * @contributor Christian Muehlhaeuser <chris@last.fm> * * Derived by ManualIPodDatabase and AutomaticIPodDatabase */ class PlayCountsDatabase { protected: /** pass the path for the db, you need to make sure the directory exists */ PlayCountsDatabase( const QString& path ); public: virtual ~PlayCountsDatabase(); class Track { public: Track() : m_playCount( 0 ) {} Track( const QString& uid, int c ) { m_playCount = c; m_id = uid; } bool isNull() const { return m_id.isEmpty(); } QString uniqueId() const { return m_id; } int playCount() const { return m_playCount; } private: QString m_id; int m_playCount; }; /** the uid is path on Windows, persistentId on mac */ Track operator[]( const QString& uid ); // gets the snapshot value Track track( const QString& uid ); // this actually fetchs the current value // NOTE never put these in the ctor/dtor, as if exception is thrown we // mustn't commit the transaction! void beginTransaction(); void endTransaction(); /** the justification for INSERT is manual ipod scrobbling, since we have * no bootstrap step, which is unavoidable, the first diff effectively * bootstraps the device */ bool insert( const ITunesLibraryTrack& track ); bool remove( const ITunesLibraryTrack& track ); bool update( const ITunesLibraryTrack& track ); QString path() const { return m_path; } protected: #ifdef Q_OS_WIN32 bool exec( const QString& sql, const ITunesLibraryTrack& ); #endif protected: QSqlDatabase m_db; QSqlQuery* m_query; QHash<QString,int> m_snapshot; private: Q_DISABLE_COPY( PlayCountsDatabase ) QString m_path; }; #endif //PLAY_COUNT_DATABASE_H ================================================ FILE: app/twiddly/README ================================================ Twiddly is coupled to the iPod handling component of the iTunes plugin and the Last.fm Client software. It is launched when an iPod has finished syncing with iTunes and compares the iTunesPlaysDatabase with the iTunes Library Database, and thus determines what has been played by an iPod. It is a separate process for the purpose of writing simpler code, and the use-case where the client is not running. ================================================ FILE: app/twiddly/Twiddly.rc ================================================ IDI_ICON1 ICON DISCARDABLE "..\\..\\admin\\dist\\win\\installer.ico" // The first entry ('1') on the below line is normally written as the define // VS_VERSION_INFO, but we can't always be sure that value has been included // in a Qt build so we just hardcode 1 in. 1 VERSIONINFO FILEVERSION 0,0,0,0 PRODUCTVERSION 0,0,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "Comments", "http://www.last.fm" VALUE "CompanyName", "Last.fm" VALUE "FileDescription", "Last.fm iPodScrobbler" VALUE "FileVersion", "0.0.0.0\0" VALUE "InternalName", "Last.fm iPodScrobbler" VALUE "LegalCopyright", "Copyright (C) 2008" VALUE "OriginalFilename", "iPodScrobbler.exe" VALUE "ProductName", "Last.fm iPodScrobbler" VALUE "ProductVersion", "0.0.0.0\0" END END END ================================================ FILE: app/twiddly/TwiddlyApplication.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "TwiddlyApplication.h" TwiddlyApplication::TwiddlyApplication( int& argc, char** argv ) :unicorn::CoreApplication( "fm.last.Twiddly", argc, argv ) { } void TwiddlyApplication::sendBusMessage( const QByteArray& /*msg*/ ) { //m_bus.sendMessage( msg ); } ================================================ FILE: app/twiddly/TwiddlyApplication.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "lib/unicorn/UnicornCoreApplication.h" class TwiddlyApplication : public unicorn::CoreApplication { Q_OBJECT public: TwiddlyApplication(int& argc, char** argv); void sendBusMessage( const QByteArray& msg ); }; ================================================ FILE: app/twiddly/Utils.cpp ================================================ /* Copyright 2005-2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QProcess> #include <QSettings> #include "lib/unicorn/UnicornSettings.h" #include <lastfm/misc.h> #include "Utils.h" #ifndef Q_OS_MAC void Utils::startAudioscrobbler( QStringList& vargs ) { QString path = unicorn::AppSettings( "Client" ).value( "Path" ).toString(); if ( path.size() == 0 ) { path = unicorn::AppSettings( "Last.fm" ).value( "Path" ).toString(); if ( path.size() == 0 ) { path = lastfm::dir::programFiles().filePath( "Last.fm/Last.fm.exe" ); } } QProcess::startDetached( path, vargs ); } #endif // Q_OS_MAC ================================================ FILE: app/twiddly/Utils.h ================================================ /* Copyright 2005-2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UTILS_H #define UTILS_H #include <QStringList> class Utils { public: static void startAudioscrobbler( QStringList& vargs ); }; #endif // UTILS_H ================================================ FILE: app/twiddly/Utils_mac.mm ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "Utils.h" #define AUDIOSCROBBLER_BUNDLEID "fm.last.Scrobbler" #include <ApplicationServices/ApplicationServices.h> //This macro clashes with Qt headers #undef check void Utils::startAudioscrobbler( QStringList& vargs ) { FSRef appRef; LSFindApplicationForInfo( kLSUnknownCreator, CFSTR( AUDIOSCROBBLER_BUNDLEID ), NULL, &appRef, NULL ); const void* arg[vargs.size()]; int index(0); AEDescList argAEList; AECreateList( NULL, 0, FALSE, &argAEList ); foreach( QString i, vargs ) { arg[index++] = CFStringCreateWithCString( NULL, i.toUtf8().data(), kCFStringEncodingUTF8 ); AEPutPtr( &argAEList, 0, typeChar, i.toUtf8().data(), i.toUtf8().length()); } LSApplicationParameters params; params.version = 0; params.flags = kLSLaunchAndHide | kLSLaunchDontSwitch | kLSLaunchAsync; params.application = &appRef; params.asyncLaunchRefCon = NULL; params.environment = NULL; CFArrayRef args = CFArrayCreate( NULL, ((const void**)arg), vargs.size(), NULL); params.argv = args; AEAddressDesc target; AECreateDesc( typeApplicationBundleID, CFSTR( AUDIOSCROBBLER_BUNDLEID ), 16, &target); AppleEvent event; AECreateAppleEvent ( kCoreEventClass, kAEReopenApplication , &target, kAutoGenerateReturnID, kAnyTransactionID, &event ); AEPutParamDesc( &event, keyAEPropData, &argAEList ); params.initialEvent = &event; LSOpenApplication( ¶ms, NULL ); //AEDisposeDesc( &argAEList ); //AEDisposeDesc( &target ); } ================================================ FILE: app/twiddly/main.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "IPod.h" #include "TwiddlyApplication.h" #include "Utils.h" #include "plugins/iTunes/ITunesExceptions.h" #include "common/c++/Logger.h" #include "lib/unicorn/UnicornCoreApplication.h" #ifdef Q_OS_MAC #include "lib/unicorn/plugins/Version.h" #include "lib/unicorn/plugins/ITunesPluginInstaller.h" #else #include "lib/unicorn/plugins/ITunesPluginInfo.h" #endif #include <lastfm/misc.h> #include <QtCore> #include <QtXml> #include <iostream> void writeXml( const QDomDocument&, const QString& path ); void logException( QString ); /** * * Fake params: --device ipod --connection usb --pid 4611 --vid 1452 --serial 000000C8B035 */ int main( int argc, char** argv ) { // Make sure the logger exists in this binary #ifdef Q_OS_WIN QString bytes = TwiddlyApplication::log( TwiddlyApplication::applicationName() ).absoluteFilePath(); const wchar_t* path = bytes.utf16(); #else QByteArray bytes = TwiddlyApplication::log( TwiddlyApplication::applicationName() ).absoluteFilePath().toLocal8Bit(); const char* path = bytes.data(); #endif new Logger( path ); TwiddlyApplication::setApplicationName( "iPodScrobbler" ); TwiddlyApplication::setApplicationVersion( "2" ); TwiddlyApplication app( argc, argv ); if ( app.isRunning() ) { qDebug() << "The iPod Scrobbler is already running. Shutting down" << app.arguments(); return 1; } // check we're using a compatible version of the plugin unicorn::Version compatibleVersion( 6, 0, 5, 4 ); unicorn::Version installedVersion; #ifdef Q_OS_WIN unicorn::ITunesPluginInfo* iTunesPluginInfo = new unicorn::ITunesPluginInfo; installedVersion = iTunesPluginInfo->installedVersion(); delete iTunesPluginInfo; #else // TODO: get the actual installed version installedVersion = unicorn::ITunesPluginInstaller::installedVersion(); #endif if ( installedVersion < compatibleVersion ) { // tell the app that the plugin is incompatible QStringList args; args << "--tray"; args << "--twiddly"; args << "incompatible-plugin"; Utils::startAudioscrobbler( args ); qDebug() << "The iTunes pluggin is old and incompatible. Please update. Shutting down" << app.arguments(); return 1; } try { if ( app.arguments().contains( "--bootstrap-needed?" ) ) { return AutomaticIPod::PlayCountsDatabase().isBootstrapNeeded(); } QDateTime start_time = QDateTime::currentDateTime(); QTime time; time.start(); if ( app.arguments().contains( "--bootstrap" ) ) { AutomaticIPod::PlayCountsDatabase().bootstrap(); } else // twiddle! { app.sendBusMessage( "--twiddling" ); IPod* ipod = IPod::fromCommandLineArguments( app.arguments() ); { QStringList args; args << "--tray"; args << "--twiddly"; args << "starting"; args << "--deviceId"; args << ipod->uid(); args << "--deviceName"; args << ipod->name; Utils::startAudioscrobbler( args ); } qDebug() << "Twiddling device: " << ipod->serial; ipod->twiddle(); //------------------------------------------------------------------ IPodType previousType = ipod->settings().type(); IPodType currentType = app.arguments().contains( "--manual" ) ? IPodManualType : IPodAutomaticType; if ( previousType == IPodManualType && currentType == IPodAutomaticType ) { qDebug() << "iPod switched from manual to automatic - deleting manual db and ignoring scrobbles"; // The iPod was manual, but is now automatic, we must: // 1. remove the manual db, to avoid misscrobbles if it ever becomes // manual again QString path = ManualIPod::PlayCountsDatabase( ipod ).path(); QFile( path ).remove(); // 2. not scrobble this time :( because any tracks that were on the // the iPod and are also in the iTunes library will be merged // and if they ever played on the iPod, will increase the iTunes // library playcounts. We need to sync the Automatic playcountsdb // but not scrobble anything. ipod->scrobbles().clear(); } ipod->settings().setType( currentType ); //------------------------------------------------------------------ if (ipod->scrobbles().count()) { // create a unique storage location for the XML QDir dir = ipod->saveDir().filePath( "scrobbles" ); QString filename = QDateTime::currentDateTime().toString( "yyyyMMddhhmmss" ) + ".xml"; QString path = dir.filePath( filename ); dir.mkpath( "." ); QDomDocument xml = ipod->scrobbles().xml(); xml.documentElement().setAttribute( "uid", ipod->uid() ); writeXml( xml, path ); QStringList args; args << "--tray"; args << "--twiddly"; args << "complete"; args << "--ipod-path"; args << path; args << "--deviceId"; args << ipod->uid(); args << "--deviceName"; args << ipod->name; Utils::startAudioscrobbler( args ); } else { QStringList args; args << "--tray"; args << "--twiddly"; args << "no-tracks-found"; args << "--deviceId"; args << ipod->uid(); args << "--deviceName"; args << ipod->name; Utils::startAudioscrobbler( args ); } // do last so we don't record a sync if we threw and thus it "didn't" happen ipod->settings().setLastSync( start_time ); delete ipod; } qDebug() << "Procedure took:" << time.elapsed() << "milliseconds"; } catch( QString& s ) { logException( s ); } catch( std::string& s ) { logException( QString::fromStdString( s ) ); } catch( const char* s ) { logException( s ); } catch( ITunesException& e ) { logException( e.what() ); } return 0; } void writeXml( const QDomDocument& xml, const QString& path ) { // we write to a temporary file, and then do an atomic move // this prevents the client from potentially reading a corrupt XML file QTemporaryFile f; f.setAutoRemove( false ); if (!f.open()) throw "Couldn't write XML"; QTextStream s( &f ); xml.save( s, 2 ); if ( !f.rename( path ) ) throw QString("Couldn't move to ") + path; } void logException( QString message ) { qCritical() << "FATAL ERROR:" << message; // we do this because LfmApp splits on spaces in parseMessage() message.replace( ' ', '_' ); static_cast<TwiddlyApplication*>(qApp)->sendBusMessage( QString( "container://Notification/Twiddly/Error/" + message ).toAscii() ); } ================================================ FILE: app/twiddly/twiddly.pro ================================================ TARGET = iPodScrobbler LIBS += -lunicorn -llastfm QT = core xml sql CONFIG += lastfm logger CONFIG -= app_bundle include( ../../admin/include.qmake ) # TODO: FIX THIS: I think this means that we can only build bundles mac { DESTDIR = "../../_bin/Last.fm Scrobbler.app/Contents/Helpers" QMAKE_POST_LINK += ../../admin/dist/mac/bundleFrameworks.sh \"$$DESTDIR/$$TARGET\" } DEFINES += LASTFM_COLLAPSE_NAMESPACE SOURCES = main.cpp \ TwiddlyApplication.cpp \ PlayCountsDatabase.cpp \ IPod.cpp \ Utils.cpp HEADERS = TwiddlyApplication.h \ PlayCountsDatabase.h \ IPod.h \ Utils.h mac { SOURCES += ITunesLibrary_mac.cpp OBJECTIVE_SOURCES += Utils_mac.mm } win32 { # Would prefer to refer to ITunesTrack.cpp and ITunesComWrapper.cpp in-situ # in the ../../plugins/iTunes/ directory, but that triggers bugs in nmake # causing it to compile the wrong main.cpp and IPod.cpp! # So here we are copying them and their dependencies. # Oh, and for some reason, cygwin mutilates their permissions. system( xcopy ..\\..\\plugins\\iTunes\\ITunesTrack.cpp . /Y /Q ) system( xcopy ..\\..\\plugins\\iTunes\\ITunesTrack.h . /Y /Q ) system( xcopy ..\\..\\plugins\\iTunes\\ITunesExceptions.h . /Y /Q ) system( xcopy ..\\..\\plugins\\iTunes\\ITunesComWrapper.cpp . /Y /Q ) system( xcopy ..\\..\\plugins\\iTunes\\ITunesComWrapper.h . /Y /Q ) system( xcopy ..\\..\\plugins\\iTunes\\ITunesEventInterface.h . /Y /Q ) SOURCES += ITunesLibrary_win.cpp \ ITunesTrack.cpp \ ITunesComWrapper.cpp \ $$ROOT_DIR/plugins/scrobsub/EncodingUtils.cpp \ $$ROOT_DIR/lib/3rdparty/iTunesCOMAPI/iTunesCOMInterface_i.c HEADERS += ITunesTrack.h \ ITunesComWrapper.h \ ITunesEventInterface.h \ ITunesExceptions.h \ $$ROOT_DIR/plugins/scrobsub/EncodingUtils.h LIBS += -lcomsuppw DEFINES += _WIN32_DCOM RC_FILE = Twiddly.rc } ================================================ FILE: common/HideStupidWarnings.h ================================================ #ifdef Q_CC_MSVC //prevent gcc issuing warning about the pragma! lol ;) // ms admits its lousy compiler doesn't care about throw declarations #pragma warning( disable : 4290 ) #endif ================================================ FILE: common/c++/Logger.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "Logger.h" #include <ctime> #include <iostream> #include <iomanip> #include <cstdlib> #ifndef WIN32 #include <sys/stat.h> #include <pthread.h> #endif Logger* instance = 0; Logger::Logger( const COMMON_CHAR* path, Severity severity ) : mLevel( severity ) { using namespace std; instance = this; #ifdef WIN32 InitializeCriticalSection( &mMutex ); #else pthread_mutexattr_t attr; pthread_mutexattr_init( &attr ); pthread_mutex_init( &mMutex, &attr ); #endif #ifdef WIN32 HANDLE hFile = CreateFileW( path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); long const fileSize = GetFileSize( hFile, NULL ); CloseHandle( hFile ); #else struct stat st; long const fileSize = stat( path, &st ) == 0 ? st.st_size : 0; #endif if ( fileSize > 500000 ) truncate( path ); ios::openmode flags = ios::out | ios::app; mFileOut.open( path, flags ); if (!mFileOut) { #ifdef WIN32 OutputDebugStringA( "Could not open log file:" ); OutputDebugStringW( path ); #else cerr << "Could not open log file" << path; #endif return; } mFileOut << endl << endl; mFileOut << "==========================================================================lastfm" << endl; } Logger::~Logger() { mFileOut.close(); #ifdef WIN32 DeleteCriticalSection( &mMutex ); #else pthread_mutex_destroy( &mMutex ); #endif } static inline std::string time() { time_t now; time( &now ); char s[128]; strftime( s, 127, "%y%m%d %H:%M:%S", gmtime( &now ) ); return std::string( s); } void Logger::log( const char* message ) { if (!mFileOut.is_open()) return; #ifdef WIN32 EnterCriticalSection( &mMutex ); #else pthread_mutex_lock( &mMutex ); #endif mFileOut << "[" << time() << "] " << message << std::endl; #ifdef WIN32 LeaveCriticalSection( &mMutex ); #else pthread_mutex_unlock( &mMutex ); #endif } void Logger::log( Severity level, const std::string& message, const char* function, int line ) { if (level > mLevel) return; std::ostringstream s; s << function << "()"; if (level < mLevel) s << line << ": L" << level; s << std::endl; s << message << std::endl; log( s.str().c_str() ); } #ifdef WIN32 void Logger::log( Severity level, const std::wstring& in, const char* function, int line ) { // first call works out required buffer length int recLen = WideCharToMultiByte( CP_ACP, 0, in.c_str(), (int)in.size(), NULL, NULL, NULL, NULL ); char* buffer = new char[recLen + 1]; memset(buffer,0,recLen+1); // second call actually converts WideCharToMultiByte( CP_ACP, 0, in.c_str(), (int)in.size(), buffer, recLen, NULL, NULL ); std::string message = buffer; delete[] buffer; log( level, message, function, line ); } #endif void Logger::truncate( const COMMON_CHAR* path ) //static { using namespace std; ifstream inFile( path ); inFile.seekg( -400000, std::ios_base::end ); istreambuf_iterator<char> bufReader( inFile ), end; string sFile; sFile.reserve( 400005 ); sFile.assign( bufReader, end ); inFile.close(); ofstream outFile( path ); outFile << sFile << flush; outFile.close(); } Logger& //static Logger::the() { return *instance; } ================================================ FILE: common/c++/Logger.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef LOGGER_H #define LOGGER_H #include "lib/DllExportMacro.h" #include "common/c++/string.h" #include <fstream> #include <sstream> #ifdef WIN32 #include <windows.h> //for CRITICAL_SECTION #pragma warning(disable: 4251) #endif class LOGGER_DLLEXPORT Logger { public: enum Severity { Critical = 1, Warning, Info, Debug }; /** Sets the Logger instance to this, so only call once, and make it exist * as long as the application does */ explicit Logger( const COMMON_CHAR* filename, Severity severity = Info ); ~Logger(); static Logger& the(); void log( Severity level, const std::string& message, const char* function, int line ); void log( Severity level, const std::wstring& message, const char* function, int line ); /** plain write + flush, we suggest utf8 */ void log( const char* message ); static void truncate( const COMMON_CHAR* path ); private: const Severity mLevel; #ifdef WIN32 CRITICAL_SECTION mMutex; #else pthread_mutex_t mMutex; #endif std::ofstream mFileOut; }; /** use these to log */ #define LOG( level, msg ) { \ std::ostringstream ss; \ ss << msg; \ Logger::the().log( (Logger::Severity) level, ss.str(), __FUNCTION__, __LINE__ ); } #define LOGL LOG #define LOGW( level, msg ) { \ std::wostringstream ss; \ ss << msg; \ Logger::the().log( (Logger::Severity) level, ss.str(), __FUNCTION__, __LINE__ ); } #define LOGWL LOGW #endif ================================================ FILE: common/c++/fileCreationTime.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "Logger.h" #include <sys/stat.h> /** @author Max Howell * @brief Used by Twiddly and iTunesPlugin */ namespace common { #if WIN32 #define STRING wstring #define STAT _stat #define stat _wstat #define COMMON_LOG LOGW #else #define STAT stat #define STRING string #define COMMON_LOG LOG #endif static time_t fileCreationTime( const std::STRING& path ) { struct STAT st; if (stat( path.c_str(), &st ) != 0) { //COMMON_LOG( 3, "Couldn\'t stat" << path ); return 0; } else return st.st_ctime ? st.st_ctime : st.st_mtime; } #undef STAT #undef stat #undef STRING #undef COMMON_LOG #ifdef QT_CORE_LIB static inline time_t fileCreationTime( const QString& path ) { #ifdef Q_OS_WIN32 return fileCreationTime( path.toStdWString() ); #else return fileCreationTime( path.toStdString() ); #endif } #endif } ================================================ FILE: common/c++/mac/getBsdProcessList.c ================================================ // public domain // http://developer.apple.com/qa/qa2001/qa1123.html #include <sys/sysctl.h> #include <cerrno> /** Returns a list of all BSD processes on the system. This routine * allocates the list and puts it in *procList and a count of the * number of entries in *procCount. You are responsible for freeing * this list (use "free" from System framework). * On success, the function returns 0. * On error, the function returns a BSD errno value. */ static int getBsdProcessList( kinfo_proc **procList, size_t *procCount ) { int err; kinfo_proc * result; bool done; static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; // Declaring name as const requires us to cast it when passing it to // sysctl because the prototype doesn't include the const modifier. size_t length; // We start by calling sysctl with result == NULL and length == 0. // That will succeed, and set length to the appropriate length. // We then allocate a buffer of that size and call sysctl again // with that buffer. If that succeeds, we're done. If that fails // with ENOMEM, we have to throw away our buffer and loop. Note // that the loop causes use to call sysctl with NULL again; this // is necessary because the ENOMEM failure case sets length to // the amount of data returned, not the amount of data that // could have been returned. result = NULL; done = false; *procCount = 0; do { // Call sysctl with a NULL buffer. length = 0; err = sysctl( (int *) name, ( sizeof( name ) / sizeof( *name ) ) - 1, NULL, &length, NULL, 0 ); if (err == -1) { err = errno; } // Allocate an appropriately sized buffer based on the results // from the previous call. if ( err == 0 ) { result = (kinfo_proc*)malloc( length ); if ( result == NULL ) { err = ENOMEM; } } // Call sysctl again with the new buffer. If we get an ENOMEM // error, toss away our buffer and start again. if ( err == 0 ) { err = sysctl( (int *) name, ( sizeof( name ) / sizeof( *name ) ) - 1, result, &length, NULL, 0 ); if ( err == -1 ) { err = errno; } if (err == 0) { done = true; } else if ( err == ENOMEM ) { free( result ); result = NULL; err = 0; } } } while ( err == 0 && !done ); // Clean up and establish post conditions. if ( err != 0 && result != NULL ) { free( result ); result = NULL; } *procList = result; if ( err == 0 ) { *procCount = length / sizeof( kinfo_proc ); } return err; } ================================================ FILE: common/c++/string.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifdef WIN32 #define COMMON_STD_STRING std::wstring #define COMMON_CHAR wchar_t #else #define COMMON_STD_STRING std::string #define COMMON_CHAR char #endif #include <string> ================================================ FILE: common/c++/win/scrobSubPipeName.cpp ================================================ /* Copyright 2005-2009, Last.fm Ltd. <client@last.fm> * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the <organization> nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY <copyright holder> ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "windows.h" #include "Sddl.h" // for ConvertSidToString (_WIN32_WINNT = 0x0500) #include <string> #include <sstream> #include <iomanip> using namespace std; string formatWin32Error(DWORD error) { LPSTR buffer; if (FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, // ignored error, 0, // default langid behaviour (LPSTR) &buffer, // yay win32! 0, // min chars alloc'd in buffer 0 // no arguments! )) { // got message string result; result.assign(buffer); LocalFree(buffer); return result; } ostringstream os; os << "0x" << setfill('0') << setw(8) << setbase(16) << error; return os.str(); } // create a name (for the scrobsub pipe) based on the SID of the user running this process // return win32 error code (0 == success) DWORD scrobSubPipeName(string* pipeName) { #define PIPE_PREFIX "\\\\.\\pipe\\lastfm_scrobsub_" bool ok = false; HANDLE hToken; if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { DWORD outLen; #define BUFFER_SIZE 256 char buffer[BUFFER_SIZE] = { 0 } ; // init just to suppress warning if (GetTokenInformation(hToken, TokenOwner, buffer, BUFFER_SIZE, &outLen)) { LPSTR sidstring; TOKEN_OWNER* tokenOwner = (TOKEN_OWNER*) buffer; if (ConvertSidToStringSidA(tokenOwner->Owner, &sidstring)) { ostringstream os; os << PIPE_PREFIX << sidstring; *pipeName = os.str(); LocalFree(sidstring); ok = true; } } CloseHandle(hToken); } return ok ? 0 : GetLastError(); } ================================================ FILE: common/precompiled.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QtCore> #include <QtGui> #include <QtNetwork> #include <QtXml> ================================================ FILE: common/qt/README ================================================ These functions are small utility functions that only depend on Qt. ================================================ FILE: common/qt/msleep.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QThread> namespace Qt { static inline void msleep( uint ms ) { struct Thread : QThread { using QThread::msleep; }; Thread::msleep( ms ); } } ================================================ FILE: common/qt/override/QDebug ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef QT_OVERRIDE_QDEBUG #define QT_OVERRIDE_QDEBUG #include <QtCore/QDebug> #include <QTime> #ifdef WIN32 #define __PRETTY_FUNCTION__ __FUNCTION__ #endif struct StampedDebug : QDebug { StampedDebug( QtMsgType type, const QByteArray& file, uint line, QByteArray function ) : QDebug( type ) { Q_UNUSED( line ); Q_UNUSED( file ); QByteArray out; #if __GNUG__ int const k = function.indexOf( '(' ); int const m = function.mid( 0, k ).lastIndexOf( "::" ); // work with ctors and dtors int const n = m == -1 ? 0 : function.lastIndexOf( ' ', m ) + 1; function = function.mid( n, k - n ); #endif out += function + "()"; *this << out.data(); } }; #define qDebug() StampedDebug( QtDebugMsg, __FILE__, __LINE__, __PRETTY_FUNCTION__ ) #define qWarning() StampedDebug( QtWarningMsg, __FILE__, __LINE__, __PRETTY_FUNCTION__ ) #define qCritical() StampedDebug( QtCriticalMsg, __FILE__, __LINE__, __PRETTY_FUNCTION__ ) #endif ================================================ FILE: common/qt/override/QHttp ================================================ #error Use QNetworkManager instead. ================================================ FILE: common/qt/override/QMessageBox ================================================ #error Use lib/unicorn/QMessageBoxBuilder.h instead. ================================================ FILE: common/qt/override/QNetworkAccessManager ================================================ #error Use lib/ws/WsNetworkManager instead. ================================================ FILE: common/qt/override/README ================================================ Brief ===== Force this directory first in the -I g++ directives to use these overrides. With QMake: CXX = $$CXX -IlibMoose/QtOverrides There is danger with this system. But it beats the system where developers could accidentally use the Qt version of a class or function and for some reason it is absolutely essential our override is used. NOTE, the cpp files are named without the Q as this seems to break qmake, heh :) NOTE, new classes should be as minimal as possible, even Qt will use your versions of the classes, but only if they are used in inline functions in headers, so you should be mostly safe. Still be careful. If you change more than a tiny amount of the class do '#error include "blah.h" instead' and write a separate class. NOTE Beware overriding non virtual functions if the base class may use that function. Implementations =============== QSystemTrayIcon --------------- For sendMessage() on mac, to show a notification when Growl is not installed. ================================================ FILE: common/qt/reverse.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QList> namespace Qt { template <class T> static inline QList<T> reverse( QList<T> list ) { const int N = list.size(); const int n = N/2; for (int x = 0; x < n; ++x) list.swap( x, N-1-x ); return list; } //"specialise" for QStringList static inline QStringList reverse( QList<QString> strings ) { return (QStringList)reverse<QString>( strings ); } } ================================================ FILE: common/qt/sort.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QMap> #include <QStringList> namespace Qt { QStringList sort( QStringList input, Qt::CaseSensitivity s ) { if (sensitivity = Qt::CaseSensitive) return input.sort(); // This cumbersome bit of code here is how the Qt docs suggests you sort // a string list case-insensitively QMap<QString, QString> map; foreach (QString s, input) map.insert( s.toLower(), s ); QStringList output; QMapIterator<QString, QString> i( map ); while (i.hasNext()) output += i.next().value(); return output; } } ================================================ FILE: i18n/i18n.pro ================================================ CONFIG -= app_bundle include( ../admin/include.qmake ) TRANSLATIONS = lastfm_de.ts \ lastfm_en.ts \ lastfm_es.ts \ lastfm_fr.ts \ lastfm_it.ts \ lastfm_ja.ts \ lastfm_pl.ts \ lastfm_pt.ts \ lastfm_ru.ts \ lastfm_sv.ts \ lastfm_tr.ts \ lastfm_zh_CN.ts isEmpty(vcproj) { QMAKE_LINK = @: IGNORE THIS LINE OBJECTS_DIR = win32:CONFIG -= embed_manifest_exe } else { CONFIG += console PHONY_DEPS = . phony_src.input = PHONY_DEPS phony_src.output = phony.c phony_src.variable_out = GENERATED_SOURCES phony_src.commands = echo int main() { return 0; } > phony.c phony_src.name = CREATE phony.c phony_src.CONFIG += combine QMAKE_EXTRA_COMPILERS += phony_src } isEmpty(QMAKE_LRELEASE) { win32|os2:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]\\lrelease.exe else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease unix { !exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease-qt4 } } else { !exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease } } } updateqm.input = TRANSLATIONS updateqm.output = ${QMAKE_FILE_BASE}.qm updateqm.commands = $$QMAKE_LRELEASE -silent -removeidentical ${QMAKE_FILE_IN} -qm ${QMAKE_FILE_BASE}.qm updateqm.CONFIG += no_link target_predeps QMAKE_EXTRA_COMPILERS += updateqm unix:!mac { i18n.files += *.qm i18n.path = $$DATADIR/lastfm-scrobbler/i18n i18n.CONFIG += no_check_exist INSTALLS += i18n } ================================================ FILE: i18n/lastfm_de.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="de"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>Über Last.fm</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 (erstellt mit Qt %2)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>Klicke in deinem Webbrowser auf die Schaltfläche <strong>Ja, Zugang gestatten</strong>, um dein Last.fm-Konto mit der Last.fm-Desktop-App zu verbinden.</p><p>Versuche es bitte noch einmal, wenn keine Verbindung hergestellt werden konnte, weil du das Browserfenster geschlossen oder auf Abbrechen geklickt hast.</p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>Wir warten darauf, dass du dich mit Last.fm verbindest</translation> </message> <message> <source><< Back</source> <translation><< Zurück</translation> </message> <message> <source>Continue</source> <translation>Weiter</translation> </message> <message> <source>Try Again</source> <translation>Erneut versuchen</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>Wenn sich dein Webbrowser nicht geöffnet hat, kopiere den unten stehenden Link bitte in deine Adressleiste.</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>Tastaturkürzel:</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Last.fm ein-/ausblenden</translation> </message> <message> <source>Proxy:</source> <translation>Proxy:</translation> </message> <message> <source>Enable SSL</source> <translation>SSL aktivieren</translation> </message> <message> <source>Cache Size:</source> <translation>Cachegröße:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>Abonnent</translation> </message> <message> <source>Moderator</source> <translation>Moderator</translation> </message> <message> <source>Staff</source> <translation>Mitarbeiter</translation> </message> <message> <source>Alumna</source> <translation>Absolventin</translation> </message> <message> <source>Alumnus</source> <translation>Absolvent</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>Auf Tour</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>Um die bestmöglichen Empfehlungen auf Basis deines Musikgeschmacks zu erhalten, empfehlen wir, dass du deinen Hörverlauf aus deinem Mediaplayer importierst.</p><p>Wähle deinen bevorzugten Mediaplayer aus und klicke auf <strong>Import starten</strong></p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Deine Plugins wurden nicht installiert</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Du kannst sie später über das Dateimenü installieren</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>Lass uns nun deinen Hörverlauf importieren</translation> </message> <message> <source>Start Import</source> <translation>Import starten</translation> </message> <message> <source><< Back</source> <translation><< Zurück</translation> </message> <message> <source>Skip >></source> <translation>Überspringen >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>Keine Sorge! Das Hochladen dürfte, je nach Umfang deiner Musiksammlung, höchstens ein paar Minuten dauern.</p><p>Sieh dir doch einige der Hauptfunktionen der Last.fm-Desktop-App an, während wir daran arbeiten, deinen Hörverlauf in dein Last.fm-Profil einzutragen. Durch Klick auf <strong>Weiter</strong> gelangst du zur Tour.</p></translation> </message> <message> <source>Continue</source> <translation>Weiter</translation> </message> <message> <source><< Back</source> <translation><< Zurück</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>Apps schließen</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>Geräte-Scrobbeln deaktiviert - inkompatibles iTunes-Plugin - %1</translation> </message> <message> <source>please update</source> <translation>bitte aktualisieren</translation> </message> <message> <source>Scrobble iPod</source> <translation>iPod scrobbeln</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>Möchtest du das Gerät %1 mit deinem Audioscrobbler-Benutzerkonto verknüpfen?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>Das Gerät ist nun mit deinem Benutzerkonto verknüpft. Ab jetzt kannst du die auf diesem Gerät gehörten Titel scrobbeln.</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>%1 Titel gescrobbelt.</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>Keine zu scrobbelnden Titel seit deiner letzten Synchronisierung.</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>Die iPod-Datenbank konnte nicht geöffnet werden.</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>Beim Versuch, auf die iPod-Datenbank zuzugreifen, ist ein unbekannter Fehler aufgetreten.</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>Fehlerdiagnose</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbels</translation> </message> <message> <source>This is an easter egg!</source> <translation>Das ist ein Easter Egg!</translation> </message> <message> <source>Artist</source> <translation>Künstler</translation> </message> <message> <source>Track</source> <translation>Titel</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Fingerprinting</source> <translation>Fingerprinting</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>Titel, deren "Fingerabdrücke" kürzlich genommen wurden</translation> </message> <message> <source>iPod Scrobbling</source> <translation>iPod-Scrobbeln</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>iTunes verwaltet meinen iPod automatisch</translation> </message> <message> <source>I manually manage my iPod</source> <translation>Ich verwalte meinen iPod manuell</translation> </message> <message> <source>Scrobble iPod</source> <translation>iPod scrobbeln</translation> </message> <message> <source>Logs</source> <translation>Protokolle</translation> </message> <message> <source>&Close</source> <translation>&Schließen</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n lokal zwischengespeicherter Titel</numerusform> <numerusform>%n lokal zwischengespeicherte Titel</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>Last.fm-Desktop-App</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>Danke, <strong>%1</strong>! Dein Konto ist jetzt verbunden.</translation> </message> <message> <source>Importing...</source> <translation>Importvorgang läuft ...</translation> </message> <message> <source>Import complete!</source> <translation>Import wurde abgeschlossen!</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Finde deine Freunde auf Last.fm</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>Du hast noch keine Freundschaften auf Last.fm geschlossen.</h3><p>Mit dem Freundefinder kannst du schnell und einfach deine Facebook-Freunde und E-Mail-Kontakte auf Last.fm finden.</p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>Freunde nach Benutzer- oder echten Namen suchen</translation> </message> <message> <source>Refresh Friends</source> <translation>Freunde aktualisieren</translation> </message> <message> <source>Refreshing...</source> <translation>Aktualisierung läuft ...</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>Musiksammlungs-Radio von %1</translation> </message> <message> <source>Male</source> <translation>Männlich</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>Scrobbelt jetzt von %1</translation> </message> <message> <source>Female</source> <translation>Weiblich</translation> </message> <message> <source>Scrobbling now</source> <translation>Scrobbelt jetzt</translation> </message> <message> <source>Neuter</source> <translation>Neutral</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>Suche deine Freunde</translation> </message> <message> <source>Browse Friends</source> <translation>Freunde suchen</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>Sprache:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>Anwendungsicon in der Menüleiste anzeigen</translation> </message> <message> <source>Launch application with media players</source> <translation>Anwendung mit Mediaplayern starten</translation> </message> <message> <source>Show dock icon</source> <translation>Dock-Symbol anzeigen</translation> </message> <message> <source>Show desktop notifications</source> <translation>Desktop-Benachrichtungen anzeigen</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>Absturzberichte an Last.fm schicken</translation> </message> <message> <source>Check for updates automatically</source> <translation>Automatisch nach Updates suchen</translation> </message> <message> <source>Enable media keys</source> <translation>Medientasten aktivieren</translation> </message> <message> <source>System Language</source> <translation>Systemsprache</translation> </message> <message> <source>Restart now?</source> <translation>Jetzt neu starten?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>Damit die Änderungen übernommen werden können, muss die Anwendung neu gestartet werden. Willst du sie jetzt neu starten?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>Update zu Beta-Versionen – Warnung: nur für Mutige!</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>Die iPod-Datenbank konnte nicht geöffnet werden.</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>Die Nutzung einer Scrobbel-App mit iOS, z.B. %1, kann zu doppelten Scrobbels führen. Bitte aktiviere Scrobbeln nur in einer App.</p><p>iTunes Match synchronisiert Wiedergabezähler für mehrere Geräte, aber nicht die Zeiten der letzten Wiedergabe. Dies führt zu doppelten Scrobbels mit falschen Zeiten. Wir empfehlen Benutzern von iTunes Match vorläufig, das Geräte-Scrobbeln auf Desktop-Geräten zu deaktivieren und iPhones/iPods mithilfe einer iOS-Scrobbel-App zu scrobbeln, z.B. %1.</p></translation> </message> <message> <source>Setting not changed</source> <translation>Einstellung nicht geändert</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>Du hast iTunes nicht geschlossen, um diese Einstellung zu ändern</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>Geräte-Scrobbeln aktivieren</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>Geräte-Scrobbeln bestätigen</translation> </message> <message> <source>Please note</source> <translation>Hinweis</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>Lizenzen</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>Sind wir fertig?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>Klicke auf OK, sobald du diese App genehmigt hast.</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Last.fm benötigt zuerst deine Erlaubnis!</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>Diese Anwendung benötigt deine Erlaubnis, um sich mit deinem Last.fm-Profil zu verbinden. Klicke auf OK, um zur Last.fm-Website zu gehen und dies zu tun.</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>Du bist bereits Last.fm-Nutzer? Verbinde dein Konto mit der Last.fm-Desktop-App und sie aktualisiert dein Profil mit der von dir gehörten Musik.</p><p>Wenn du kein Konto hast, kannst du dich jetzt kostenlos registrieren.</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Fangen wir an, indem wir dein Last.fm-Konto verbinden</translation> </message> <message> <source>Connect Your Account</source> <translation>Verbinde dein Konto</translation> </message> <message> <source>Sign up</source> <translation>Registrieren</translation> </message> <message> <source>Proxy?</source> <translation>Proxy?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>Für deine Mediaplayer-Plugins stehen Updates bereit. Willst du diese jetzt installieren?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>Fehler bei der Installation des Plugins</numerusform> <numerusform>Fehler bei der Installation des Plugins</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>Beim Aktualisieren Ihres Plugins ist ein Fehler aufgetreten.</p><p>Bitte versuchen Sie es später erneut.</p></numerusform> <numerusform><p>Beim Aktualisieren Ihrer Plugins ist ein Fehler aufgetreten.</p><p>Bitte versuchen Sie es später erneut.</p> </numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>Plugin installiert!</numerusform> <numerusform>Plugins installiert!</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>Dein Plugin wurden installiert.</p><p>Du kannst jetzt mit deinem Mediaplayer scrobbeln</p></numerusform> <numerusform><p>Deine Plugins wurden installiert.</p><p>Du kannst jetzt mit deinen Mediaplayern scrobbeln</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Deine Plugins wurden nicht installiert</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Du kannst sie später über das Dateimenü installieren</translation> </message> <message> <source>File</source> <translation>Datei</translation> </message> <message> <source>Install plugins</source> <translation>Plugins installieren</translation> </message> <message> <source>&Quit</source> <translation>&Beenden</translation> </message> <message> <source>View</source> <translation>Ansehen</translation> </message> <message> <source>My Last.fm Profile</source> <translation>Mein Last.fm-Profil</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobbels</translation> </message> <message> <source>Refresh</source> <translation>Aktualisieren</translation> </message> <message> <source>Controls</source> <translation>Steuerung</translation> </message> <message> <source>Account</source> <translation>Benutzerkonto</translation> </message> <message> <source>Tools</source> <translation>Tools</translation> </message> <message> <source>Check for Updates</source> <translation>Nach Updates suchen</translation> </message> <message> <source>Options</source> <translation>Optionen</translation> </message> <message> <source>Window</source> <translation>Fenster</translation> </message> <message> <source>Minimize</source> <translation>Minimieren</translation> </message> <message> <source>Zoom</source> <translation>Zoom</translation> </message> <message> <source>Bring All to Front</source> <translation>Alle in den Vordergrund</translation> </message> <message> <source>Help</source> <translation>Hilfe</translation> </message> <message> <source>About</source> <translation>Über</translation> </message> <message> <source>FAQ</source> <translation>FAQ</translation> </message> <message> <source>Forums</source> <translation>Foren</translation> </message> <message> <source>Tour</source> <translation>Tour</translation> </message> <message> <source>Show Licenses</source> <translation>Lizenzen anzeigen</translation> </message> <message> <source>Diagnostics</source> <translation>Diagnose</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform><a href="tracks">%n Wiedergabe</a> wurde von einem Gerät gescrobbelt</numerusform> <numerusform><a href="tracks">%n Wiedergaben</a> wurden von einem Gerät gescrobbelt</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Zurück zu Scrobbels</translation> </message> <message> <source>Popular tags:</source> <translation>Beliebte Tags:</translation> </message> <message> <source>Your tags:</source> <translation>Deine Tags:</translation> </message> <message> <source>Similar Artists</source> <translation>Ähnliche Künstler</translation> </message> <message> <source>by %1</source> <translation>von %1</translation> </message> <message> <source>from %1</source> <translation>von %1</translation> </message> <message> <source>Play %1 Radio</source> <translation>%1-Radio abspielen</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>Wiedergabe</numerusform> <numerusform>Wiedergaben</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>Wiedergabe in deiner Musiksammlung</numerusform> <numerusform>Wiedergaben in deiner Musiksammlung</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>Hörer</numerusform> <numerusform>Hörer</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>Mit %1 und mehr.</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>Mit %1, %2 und mehr.</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>Bearbeitet am %1 | %2 Bearbeiten</translation> </message> <message> <source>Downloads</source> <translation>Downloads</translation> </message> <message> <source>Search on %1</source> <translation>Suche auf %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Auf %1 %2 kaufen</translation> </message> <message> <source>Physical</source> <translation>Physikalisch</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>Empfohlen, weil du %1 hörst.</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>Empfohlen, weil du %1 und %2 hörst.</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>Empfohlen, weil du %1, %2 und %3 hörst.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>Empfohlen, weil du %1, %2, %3 und %4 hörst.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>Empfohlen, weil du %1, %2, %3, %4 und %5 hörst.</translation> </message> <message> <source>From %1's library.</source> <translation>Aus der Musiksammlung von %1</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>Aus den Musiksammlungen von %1 und %2.</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 Mal</numerusform> <numerusform>%L1 Mal</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>Aus den Musiksammlungen von %1, %2 und %3.</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>Du hast %1 %2 und %3 %4 gehört.</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>Aus den Musiksammlungen von %1, %2, %3 und %4.</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>Du hast %1 %2 gehört, aber nicht diesen Titel.</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>Aus den Musiksammlungen von %1, %2, %3, %4 und %5.</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>Du hast zum ersten Mal %1 gehört.</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>Hallo!</translation> </message> <message> <source>Start a radio station</source> <translation>Eine Radiostation starten</translation> </message> <message> <source>Open iTunes</source> <translation>iTunes öffnen</translation> </message> <message> <source>Open Music</source> <translation>Music öffnen</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Windows Media Player öffnen</translation> </message> <message> <source>Open Winamp</source> <translation>Winamp öffnen</translation> </message> <message> <source>Open Foobar</source> <translation>Foobar öffnen</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>Von deinem Musikplayer scrobbeln</h2><p>Höre Musik auf deinem Mediaplayer. Im Register „Gerade läuft“ findest du weitere Informationen zu den gespielten Titeln.</p></translation> </message> <message> <source>Hello, %1!</source> <translation>Hallo %1!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>Eine Radiostation</translation> </message> <message> <source>Play %1</source> <translation>%1 wiedergeben</translation> </message> <message> <source>Multi-Library Radio</source> <translation>Multi-Musiksammlungs-Radio</translation> </message> <message> <source>Cue %1</source> <translation>%1 in die Warteschleife aufnehmen</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>Musiksammlungs-Radio %1 und %2 abspielen</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>Musiksammlungs-Radio %1 und %2 in die Warteschleife aufnehmen</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>Lieben</translation> </message> <message> <source>Ban</source> <translation>Bannen</translation> </message> <message> <source>Play</source> <translation>Wiedergeben</translation> </message> <message> <source>Skip</source> <translation>Überspringen</translation> </message> <message> <source>Pause</source> <translation>Pause</translation> </message> <message> <source>Resume</source> <translation>Fortsetzen</translation> </message> <message> <source>Unlove</source> <translation>Als Lieblingslied entfernen</translation> </message> <message> <source>Tuning</source> <translation>Tuning</translation> </message> <message> <source>A Radio Station</source> <translation>Eine Radiostation</translation> </message> <message> <source>Listening to</source> <translation>Gerade läuft</translation> </message> <message> <source>Scrobbling from</source> <translation>Scrobbelt von</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 Wiedergabe</numerusform> <numerusform>%L1 Wiedergaben</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>Last.fm hat deinen Mediaplayer-Hörverlauf importiert. Klicke OK, um fortzufahren.</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Import des Mediaplayer-Hörverlaufs</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>Bist du sicher, dass du den Import abbrechen möchtest?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Last.fm konnte keine Plays in deiner Mediensammlung finden. Klicke OK, um fortzufahren.</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm importiert gerade deine aktuelle Mediensammlung ...</translation> </message> <message> <source>Where is Winamp?</source> <translation>Wo ist Winamp?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Wo ist Windows Media Player?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>Import der Mediensammlung abgeschlossen</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>Last.fm hat deinen Hörverlauf an den Server übermittelt. Dein Profil wird in ein paar Minuten mit den neuen Titeln aktualisiert.</translation> </message> <message> <source>Library Import Failed</source> <translation>Import der Mediensammlung fehlgeschlagen</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Entschuldigung, Last.fm konnte deinen Hörverlauf leider nicht importieren. Du hast wahrscheinlich schon zu viele Titel gescrobbelt. Hörverläufe können nur zu ganz neuen Profilen hinzugefügt werden.</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>Befolge bitte die von deinem Betriebssystem angezeigten Anweisungen zur Installation der Plugins.</p><p>Klicke auf <strong>Weiter</strong>, sobald die Plugins auf deinem Computer installiert worden sind.</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>Deine Plugins werden jetzt installiert</translation> </message> <message> <source>Continue</source> <translation>Weiter</translation> </message> <message> <source><< Back</source> <translation><< Zurück</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Deine Plugins wurden nicht installiert</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Du kannst sie später über das Dateimenü installieren</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>Dein Mediaplayer benötigt ein spezielles Last.fm-Plugin, um die von dir gehörte Musik scrobbeln zu können.</p><p>Wähle bitte die Mediaplayer, von denen du deine Musik scrobbeln möchtest, und klicke auf <strong>Plugins installieren.</strong></p></translation> </message> <message> <source>(newer version)</source> <translation>(neuere Version)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(Plugin installiert. Zum Deinstallieren markieren.)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>Installiere als nächstes die Last.fm-Plugins, damit du deine gehörte Musik scrobbeln kannst.</translation> </message> <message> <source>Install Plugins</source> <translation>Plugins installieren</translation> </message> <message> <source>Continue</source> <translation>Weiter</translation> </message> <message> <source><< Back</source> <translation><< Zurück</translation> </message> <message> <source>Skip >></source> <translation>Überspringen >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>Allgemein</translation> </message> <message> <source>Accounts</source> <translation>Konten</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbels</translation> </message> <message> <source>Devices</source> <translation>Geräte</translation> </message> <message> <source>Advanced</source> <translation>Erweitert</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>1%-Radio</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 Wiedergabe</numerusform> <numerusform>%L1 Wiedergaben</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>Top-Künstler diese Woche</translation> </message> <message> <source>Top Artists Overall</source> <translation>Top-Künstler insgesamt</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>Scrobbel</numerusform> <numerusform>Scrobbels</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>Lieblingslied</numerusform> <numerusform>Lieblingslieder</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 Künstler</numerusform> <numerusform>%L1 Künstler</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 Titel</numerusform> <numerusform>%L1 Titel</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>Du hast %1 in deiner Musiksammlung und hörst durchschnittlich %2 pro Tag.</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>Scrobbel seit %1</numerusform> <numerusform>Scrobbels seit %1</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>Proxy-Einstellungen</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>Host:</translation> </message> <message> <source>Username:</source> <translation>Benutzername:</translation> </message> <message> <source>Port:</source> <translation>Port:</translation> </message> <message> <source>Password:</source> <translation>Passwort:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>unbekannter Mediaplayer</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>Wo ist dein iPod angeschlossen?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>Künstler/Tag eingeben und abspielen</translation> </message> <message> <source>Play</source> <translation>Wiedergabe</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>Probiere es doch einmal mit %1, %2, %3 oder %4.</translation> </message> <message> <source>Play next</source> <translation>Danach wiedergeben</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>Eine Radiostation</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>Du brauchst ein Abonnement, um Radio hören zu können</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>Letzte Station</translation> </message> <message> <source>A Radio Station</source> <translation>Eine Radiostation</translation> </message> <message> <source>Personal Stations</source> <translation>Persönliche Stationen</translation> </message> <message> <source>My Library Radio</source> <translation>Mein Musiksammlungs-Radio</translation> </message> <message> <source>Music you know and love</source> <translation>Musik, die du kennst und liebst</translation> </message> <message> <source>My Mix Radio</source> <translation>Mein Mix-Radio</translation> </message> <message> <source>Your library plus new music</source> <translation>Deine Musiksammlung plus neue Musik</translation> </message> <message> <source>My Recommended Radio</source> <translation>Mein Empfehlungs-Radio</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>Abo abschließen und Radio hören</translation> </message> <message> <source>New music from Last.fm</source> <translation>Neue Musik von Last.fm</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>Du brauchst ein Abonnement bei Last.fm, um in dieser App Radio hören zu können. Schließe ein Abo ab und komm in den Genuss von toller Musik und anderer toller Vorteile!</translation> </message> <message> <source>Network Stations</source> <translation>Netzwerkstationen</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Last.fm abonnieren</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>Kostenlos hören auf www.last.fm</translation> </message> <message> <source>My Friends' Radio</source> <translation>Radio meiner Freunde</translation> </message> <message> <source>Music your friends like</source> <translation>Musik, die deinen Freunden gefällt</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>Mein Nachbar-Radio</translation> </message> <message> <source>Music from listeners like you</source> <translation>Musik von Hörern wie dir</translation> </message> <message> <source>Recent Stations</source> <translation>Letzte Stationen</translation> </message> <message> <source>Now Playing</source> <translation>Gerade läuft</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>Werde Abonnent, um Radio hören zu können - für nur %1 im Monat</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>Geräte-Scrobbeln</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>Sieht so aus, als hättest du diese Titel abgespielt. Möchtest du sie scrobbeln?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>Geräte automatisch scrobbeln</translation> </message> <message> <source>Toggle selection</source> <translation>Auswahl umkehren</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>%n Wiedergabe wurde von einem Gerät gescrobbelt</numerusform> <numerusform>%n Wiedergaben wurden von einem Gerät gescrobbelt</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>Zu Lieblingsliedern hinzufügen</translation> </message> <message> <source>Add tags</source> <translation>Tags hinzufügen</translation> </message> <message> <source>Love</source> <translation>Lieben</translation> </message> <message> <source>Tag</source> <translation>Taggen</translation> </message> <message> <source>Share</source> <translation>Empfehlen</translation> </message> <message> <source>Share on Last.fm</source> <translation>Auf Last.fm empfehlen</translation> </message> <message> <source>Share on Twitter</source> <translation>Auf Twitter empfehlen</translation> </message> <message> <source>Share on Facebook</source> <translation>Auf Facebok empfehlen</translation> </message> <message> <source>Buy</source> <translation>Kaufen</translation> </message> <message> <source>Unlove track</source> <translation>Aus Lieblingsliedern entfernen</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Scrobbeln bei</translation> </message> <message> <source>percent of the track</source> <translation>Prozent des Titels</translation> </message> <message> <source>Enable scrobbling</source> <translation>Scrobbeln aktivieren</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>...oder bei 4 Minuten (je nachdem, was zuerst eintritt)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>Podcasts scrobbeln</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>Fingerprinting meiner Titel durch Last.fm zulassen</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>Ausgewählte Verzeichnisse werden nicht gescrobbelt</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>Mehr Scrobbels auf Last.fm</translation> </message> <message> <source>Refreshing...</source> <translation>Aktualisierung läuft ...</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Scrobbels aktualisieren</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>Künstler</translation> </message> <message> <source>Title</source> <translation>Titel</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Plays</source> <translation>Wiedergaben</translation> </message> <message> <source>Last Played</source> <translation>Zuletzt wiedergegeben</translation> </message> <message> <source>Loved</source> <translation>Lieblingslied</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>Dieser Titel ist weniger als 30 Sekunden lang</translation> </message> <message> <source>The artist name is missing</source> <translation>Der Künstlername fehlt</translation> </message> <message> <source>Invalid track title</source> <translation>Ungültiger Titelname</translation> </message> <message> <source>Invalid artist</source> <translation>Ungültiger Künstler</translation> </message> <message> <source>There is no timestamp</source> <translation>Es gibt keine Zeitmarkierung</translation> </message> <message> <source>This track is too far in the future</source> <translation>Dieser Titel liegt zu weit in der Zukunft</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>Dieser Titel wurde vor über zwei Wochen wiedergegeben</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>Du hast noch keine Musik zu Last.fm gescrobbelt.</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>Höre Musik in deinem Mediaplayer oder starte eine Radiostation:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>Freunden empfehlen</translation> </message> <message> <source>With:</source> <translation>An:</translation> </message> <message> <source>Message (optional):</source> <translation>Nachricht (optional):</translation> </message> <message> <source>include in my recent activity</source> <translation>in meine letzten Aktivitäten aufnehmen</translation> </message> <message> <source>A track by %1</source> <translation>Ein Titel von %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Ein Titel von %1 aus der Veröffentlichung %2</translation> </message> <message> <source>Check out %1</source> <translation>Schau dir mal %1 an</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>Gerade läuft</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobbels</translation> </message> <message> <source>Profile</source> <translation>Profil</translation> </message> <message> <source>Friends</source> <translation>Freunde</translation> </message> <message> <source>Radio</source> <translation>Radio</translation> </message> <message> <source>Next Section</source> <translation>Nächster Abschnitt</translation> </message> <message> <source>Previous Section</source> <translation>Vorheriger Abschnitt</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>Konnte Radio nicht starten: %1</translation> </message> <message> <source>no results for "%1"</source> <translation>keine Ergebnisse für "%1"</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>Scrobbeln ist deaktiviert</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>Online</translation> </message> <message> <source>Offline</source> <translation>Offline</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Choose something to tag:</source> <translation>Wähle etwas zum Taggen aus:</translation> </message> <message> <source>Track</source> <translation>Titel</translation> </message> <message> <source>Artist</source> <translation>Künstler</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>icon</source> <translation>Symbol</translation> </message> <message> <source>Add tags:</source> <translation>Tags hinzufügen:</translation> </message> <message> <source>A track by %1</source> <translation>Ein Titel von %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Ein Titel von %1 aus der Veröffentlichung %2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>Gib oben ein Tag ein oder wähle unten eines aus</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>Nach Beliebtheit sortieren</translation> </message> <message> <source>Sort Alphabetically</source> <translation>Alphabetisch sortieren</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>Last.fm-Seite für dieses Tag öffnen</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>Jetzt kannst du loslegen! Klicke einfach auf <strong>Fertigstellen</strong> und fange an, die App zu erkunden.</p><p>Wir haben auch deinen Hörverlauf importiert und in dein Last.fm-Profil eingetragen.</p><p>Vielen Dank, dass du die Last.fm-Desktop-App installiert hast! Wir wünschen dir viel Spaß damit.</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>Das wars, du kannst loslegen!</translation> </message> <message> <source>Finish</source> <translation>Fertigstellen</translation> </message> <message> <source><< Back</source> <translation><< Zurück</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>Der rote Pfeil auf deinem Bildschirm zeigt dir die Position der Last.fm-Desktop-App in deiner Taskleiste.</p><p>Klicke auf das Symbol, um Zugriff auf Radiosteuerungen zu erhalten, Titel zu empfehlen und zu taggen, deine Einstellungen zu bearbieten und dein Last.fm-Profil zu besuchen.</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>Die Last.fm-Desktop-App in deiner Menüleiste</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>Die Last.fm-Desktop-App in deiner Taskleiste</translation> </message> <message> <source>Continue</source> <translation>Weiter</translation> </message> <message> <source><< Back</source> <translation><< Zurück</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>Erfahre mehr über die Musik, die du hörst. Dazu gehören Biographien, Hörstatistiken, Fotos und ähnliche Künstler sowie die Tags, die andere Hörer zum Beschreiben vergeben haben.</p><p>Sieh dir unser Register <strong>Gerade läuft</strong> an oder klicke einfach auf einen Titel in deinem Register <strong>Scrobbels</strong>, um mehr zu erfahren.</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>Erfahre mehr über deine Lieblingskünstler</translation> </message> <message> <source>Continue</source> <translation>Weiter</translation> </message> <message> <source><< Back</source> <translation><< Zurück</translation> </message> <message> <source>Skip Tour >></source> <translation>Tour überspringen >></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>Höre ununterbrochenes, personalisiertes Radio</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Mit der Last.fm-Desktop-App kannst du personalisiertes Radio auf Basis deiner Lieblingsmusik hören.</p><p>Jede Wiedergabe in jeder Last.fm-Station ist komplett unterschiedlich – von Stationen, die auf Künstlern oder Tags basieren bis hin zu topaktuellen Empfehlungen anhand deines Musikgeschmacks.</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>Werde Abonnent und höre ununterbrochenes, personalisiertes Radio</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Werde Last.fm-Abonnent und nutze die Last.fm-Desktop-App, um personalisiertes Radio auf Basis deiner Lieblingsmusik zu hören.</p><p>Jede Wiedergabe in jeder Last.fm-Station ist komplett unterschiedlich – von Stationen, die auf Künstlern oder Tags basieren bis hin zu topaktuellen Empfehlungen anhand deines Musikgeschmacks.</p></translation> </message> <message> <source>Subscribe</source> <translation>Abonnieren</translation> </message> <message> <source>Continue</source> <translation>Weiter</translation> </message> <message> <source><< Back</source> <translation><< Zurück</translation> </message> <message> <source>Skip Tour >></source> <translation>Tour überspringen >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>Der Desktop-Client läuft im Hintergrund und aktualisiert dein Last.fm-Profil mit der Musik, die du abspielst. Dadurch kannst du Musikempfehlungen, Konzerttipps und mehr bekommen. </p><p>Du kannst die Last.fm-Desktop-App auch verwenden, um mehr über die Künstler zu erfahren, die du gerade hörst, und um personalisiertes Radio abzuspielen.</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>Herzlich willkommen bei der Last.fm-Desktop-App!</translation> </message> <message> <source>Continue</source> <translation>Weiter</translation> </message> <message> <source><< Back</source> <translation><< Zurück</translation> </message> <message> <source>Skip Tour >></source> <translation>Tour überspringen >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>Titel</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Artist</source> <translation>Künstler</translation> </message> <message> <source>Love</source> <translation>Lieben</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Empfehlen</translation> </message> <message> <source>Buy</source> <translation>Kaufen</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>Diesen Scrobbel aus deinem Profil entfernen</translation> </message> <message> <source>Play %1 Radio</source> <translation>%1-Radio abspielen</translation> </message> <message> <source>Cue %1 Radio</source> <translation>%1-Radio in die Warteschleife aufnehmen</translation> </message> <message> <source>%1 Radio</source> <translation>1%-Radio</translation> </message> <message> <source>Cached</source> <translation>Zwischengespeichert</translation> </message> <message> <source>Error: %1</source> <translation>Fehler: %1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Auf Last.fm empfehlen</translation> </message> <message> <source>Share on Twitter</source> <translation>Auf Twitter empfehlen</translation> </message> <message> <source>Share on Facebook</source> <translation>Auf Facebok empfehlen</translation> </message> <message> <source>Now listening</source> <translation>Aktuelle Hörer</translation> </message> <message> <source>Downloads</source> <translation>Downloads</translation> </message> <message> <source>Search on %1</source> <translation>Suche auf %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Auf %1 %2 kaufen</translation> </message> <message> <source>Physical</source> <translation>Physikalisch</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>Verbundene Benutzerkonten:</translation> </message> <message> <source>Add New User Account</source> <translation>Neues Benutzerkonto hinzufügen</translation> </message> <message> <source>Add User Error</source> <translation>Fehler beim Hinzufügen des Benutzers</translation> </message> <message> <source>This user has already been added.</source> <translation>Dieser Benutzer wurde bereits hinzugefügt.</translation> </message> <message> <source>Removing %1</source> <translation>%1 wird entfernt</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>Willst du diesen Benutzer wirklich entfernen? Dadurch gehen alle Benutzereinstellungen verloren und du musst eine erneute Authentifizierung durchführen, um künftig wieder scrobbeln zu können.</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>Abonnieren</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>Entfernen</translation> </message> <message> <source>(currently logged in)</source> <translation>(zurzeit angemeldet)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>Konten</translation> </message> <message> <source>Show Scrobbler</source> <translation>Scrobbler anzeigen</translation> </message> <message> <source>Love</source> <translation>Lieben</translation> </message> <message> <source>Play</source> <translation>Abspielen</translation> </message> <message> <source>Skip</source> <translation>Überspringen</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Empfehlen</translation> </message> <message> <source>Ban</source> <translation>Bannen</translation> </message> <message> <source>Mute</source> <translation>Stumm</translation> </message> <message> <source>Scrobble iPod...</source> <translation>iPod scrobbeln...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Last.fm-Profil besuchen</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Scrobbeln aktivieren</translation> </message> <message> <source>Quit %1</source> <translation>%1 wird beendet</translation> </message> <message> <source>from %1</source> <translation>von %1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>Du hast die maximale Anzahl an Titeln, die du überspringen kannst, für diese Station erreicht. In %n Minute kannst du wieder überspringen.</numerusform> <numerusform>Du hast die maximale Anzahl an Titeln, die du überspringen kannst, für diese Station erreicht. In %n Minuten kannst du wieder überspringen.</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>Du kannst in dieser Station noch %n Mal überspringen.</numerusform> <numerusform>Du kannst in dieser Station noch %n Mal überspringen.</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>Authentifizierung erforderlich</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>Das Benutzerkonto <strong>%1</strong> ist nicht mehr bei Last.fm authentifiziert.</p><p>Klicke auf OK, um das Setup zu starten und dieses Konto erneut zu authentifizieren.</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>Willst du %1 wirklich beenden?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>%1 wird gleich beendet. Wenn du fortfährst, werden wiedergegebene Titel nicht gescrobbelt.</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>Benutzer wird gewechselt</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 wird beim Scrobbler und bei Last.fm-Radio eingeloggt. Sämtliche Musik wird jetzt zu diesem Konto gescrobbelt. Willst du fortfahren?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>Schließe bitte folgende Apps, um fortzufahren.</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>Fehler bei der Installation des Plugins</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>Beim Update deines Plugins ist ein Fehler aufgetreten.</p><p>Versuche es bitte später noch einmal.</p></translation> </message> <message> <source>Plugin installed!</source> <translation>Plugin installiert!</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>Das %1-Plugin wurde installiert.</p><p>Du kannst jetzt mit %1 scrobbeln.</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>Das %1-Plugin wurde nicht installiert</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>Du hast %1 nicht geschlossen, deshalb wurde das zugehörige Plugin nicht installiert.</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>Schließe iTunes für ein Plugin-Update!</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>Dein iTunes-Plugin (%2) unterscheidet sich von demjenigen, das mit dieser Version der App (%1) ausgeliefert wurde.</p><p>Schließe iTunes bitte jetzt für das Update.</p></translation> </message> <message> <source>not installed</source> <translation>nicht installiert</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>Dein Plugin wurde nicht installiert</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>Beim Entfernen des alten Plugins ist ein Fehler aufgetreten</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>iTunes-Plugin installiert!</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>Dein iTunes-Plugin wurde installiert.</p><p>Du kannst jetzt mit dem Geräte-Scrobbeln beginnen.</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>Beim Kopieren des neuen Plugins ist ein Fehler aufgetreten</translation> </message> <message> <source>You didn't close iTunes</source> <translation>Du hast iTunes nicht geschlossen</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>Mit der Zeit stimmt etwas nicht</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>vor %n Minute</numerusform> <numerusform>vor %n Minuten</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>vor %n Stunde</numerusform> <numerusform>vor %n Stunden</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>Es gab einen Netzwerkfehler: %1</translation> </message> <message> <source>You have not authorised this application</source> <translation>Du hast diese Anwendung nicht autorisiert</translation> </message> <message> <source>Authentication Error</source> <translation>Authentifizierungsfehler</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>Stylesheet aktualisieren</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>Nicht mehr danach fragen</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>Autom. Erkennung</translation> </message> <message> <source>No-proxy</source> <translation>Kein Proxy</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_en.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="en"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>About</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 (built on Qt %2)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>We're waiting for you to connect to Last.fm</translation> </message> <message> <source><< Back</source> <translation><< Back</translation> </message> <message> <source>Continue</source> <translation>Continue</translation> </message> <message> <source>Try Again</source> <translation>Try Again</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>Keyboard Shortcuts:</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Raise/Hide Last.fm</translation> </message> <message> <source>Proxy:</source> <translation>Proxy:</translation> </message> <message> <source>Enable SSL</source> <translation>Enable SSL</translation> </message> <message> <source>Cache Size:</source> <translation>Cache Size:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>Subscriber</translation> </message> <message> <source>Moderator</source> <translation>Moderator</translation> </message> <message> <source>Staff</source> <translation>Staff</translation> </message> <message> <source>Alumna</source> <translation>Alumna</translation> </message> <message> <source>Alumnus</source> <translation>Alumnus</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>On Tour</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Your plugins haven't been installed</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>You can install them later through the file menu</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>Now let's import your listening history</translation> </message> <message> <source>Start Import</source> <translation>Start Import</translation> </message> <message> <source><< Back</source> <translation><< Back</translation> </message> <message> <source>Skip >></source> <translation>Skip >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></translation> </message> <message> <source>Continue</source> <translation>Continue</translation> </message> <message> <source><< Back</source> <translation><< Back</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>Close Apps</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>Device scrobbling disabled - incompatible iTunes plugin - %1</translation> </message> <message> <source>please update</source> <translation>please update</translation> </message> <message> <source>Scrobble iPod</source> <translation>Scrobble iPod</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>Do you want to associate the device %1 to your audioscrobbler user account?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>%1 tracks scrobbled.</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>No tracks to scrobble since your last sync.</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>The iPod database could not be opened.</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>An unknown error occurred while trying to access the iPod database.</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>Diagnostics</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbling</translation> </message> <message> <source>This is an easter egg!</source> <translation>This is an easter egg!</translation> </message> <message> <source>Artist</source> <translation>Artist</translation> </message> <message> <source>Track</source> <translation>Track</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Fingerprinting</source> <translation>Fingerprinting</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>Recently Fingerprinted Tracks</translation> </message> <message> <source>iPod Scrobbling</source> <translation>iPod Scrobbling</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>iTunes automatically manages my iPod</translation> </message> <message> <source>I manually manage my iPod</source> <translation>I manually manage my iPod</translation> </message> <message> <source>Scrobble iPod</source> <translation>Scrobble iPod</translation> </message> <message> <source>Logs</source> <translation>Logs</translation> </message> <message> <source>&Close</source> <translation>&Close</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n locally cached track</numerusform> <numerusform>%n locally cached tracks</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>Last.fm Desktop App</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>Thanks <strong>%1</strong>, your account is now connected!</translation> </message> <message> <source>Importing...</source> <translation>Importing...</translation> </message> <message> <source>Import complete!</source> <translation>Import complete!</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Find your friends on Last.fm</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>Search for a friend by username or real name</translation> </message> <message> <source>Refresh Friends</source> <translation>Refresh Friends</translation> </message> <message> <source>Refreshing...</source> <translation>Refreshing...</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>%1's Library Radio</translation> </message> <message> <source>Male</source> <translation>Male</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>Scrobbling now from %1</translation> </message> <message> <source>Female</source> <translation>Female</translation> </message> <message> <source>Scrobbling now</source> <translation>Scrobbling now</translation> </message> <message> <source>Neuter</source> <translation>Neuter</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>Search your friends</translation> </message> <message> <source>Browse Friends</source> <translation>Browse Friends</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>Language:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>Show application icon in menu bar</translation> </message> <message> <source>Launch application with media players</source> <translation>Launch application with media players</translation> </message> <message> <source>Show dock icon</source> <translation>Show dock icon</translation> </message> <message> <source>Show desktop notifications</source> <translation>Show desktop notifications</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>Send crash reports to Last.fm</translation> </message> <message> <source>Check for updates automatically</source> <translation>Check for updates automatically</translation> </message> <message> <source>Enable media keys</source> <translation>Enable media keys</translation> </message> <message> <source>System Language</source> <translation>System Language</translation> </message> <message> <source>Restart now?</source> <translation>Restart now?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>An application restart is required for the change to take effect. Would you like to restart now?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>Update to beta versions - Warning: only for the brave!</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>The iPod database could not be opened.</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></translation> </message> <message> <source>Setting not changed</source> <translation>Setting not changed</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>You did not close iTunes for this setting to change</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>Enable Device Scrobbling</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>Confirm Device Scrobbles</translation> </message> <message> <source>Please note</source> <translation>Please note</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>Licenses</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>Are we done?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>Click OK once you have approved this app.</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Last.fm needs your permission first!</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Let's get started by connecting your Last.fm account</translation> </message> <message> <source>Connect Your Account</source> <translation>Connect Your Account</translation> </message> <message> <source>Sign up</source> <translation>Sign up</translation> </message> <message> <source>Proxy?</source> <translation>Proxy?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>There are updates to your media player plugins. Would you like to install them now?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation type="unfinished"> <numerusform>Plugin install error</numerusform> <numerusform>Plugin install error</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation type="unfinished"> <numerusform><p>There was an error updating your plugin.</p><p>Please try again later.</p></numerusform> <numerusform><p>There was an error updating your plugins.</p><p>Please try again later.</p></numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>Plugin installed!</numerusform> <numerusform>Plugins installed!</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>Your plugin has been installed.</p><p>You're now ready to scrobble with your media player</p></numerusform> <numerusform><p>Your plugins have been installed.</p><p>You're now ready to scrobble with your media players</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Your plugins haven't been installed</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>You can install them later through the file menu</translation> </message> <message> <source>File</source> <translation>File</translation> </message> <message> <source>Install plugins</source> <translation>Install plugins</translation> </message> <message> <source>&Quit</source> <translation>&Quit</translation> </message> <message> <source>View</source> <translation>View</translation> </message> <message> <source>My Last.fm Profile</source> <translation>My Last.fm Profile</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobbles</translation> </message> <message> <source>Refresh</source> <translation>Refresh</translation> </message> <message> <source>Controls</source> <translation>Controls</translation> </message> <message> <source>Account</source> <translation>Account</translation> </message> <message> <source>Tools</source> <translation>Tools</translation> </message> <message> <source>Check for Updates</source> <translation>Check for Updates</translation> </message> <message> <source>Options</source> <translation>Options</translation> </message> <message> <source>Window</source> <translation>Window</translation> </message> <message> <source>Minimize</source> <translation>Minimize</translation> </message> <message> <source>Zoom</source> <translation>Zoom</translation> </message> <message> <source>Bring All to Front</source> <translation>Bring All to Front</translation> </message> <message> <source>Help</source> <translation>Help</translation> </message> <message> <source>About</source> <translation>About</translation> </message> <message> <source>FAQ</source> <translation>FAQ</translation> </message> <message> <source>Forums</source> <translation>Forums</translation> </message> <message> <source>Tour</source> <translation>Tour</translation> </message> <message> <source>Show Licenses</source> <translation>Show Licenses</translation> </message> <message> <source>Diagnostics</source> <translation>Diagnostics</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform><a href="tracks">%n play</a> has been scrobbled from a device</numerusform> <numerusform><a href="tracks">%n plays</a> have been scrobbled from a device</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Back to Scrobbles</translation> </message> <message> <source>Popular tags:</source> <translation>Popular tags:</translation> </message> <message> <source>Your tags:</source> <translation>Your tags:</translation> </message> <message> <source>Similar Artists</source> <translation>Similar Artists</translation> </message> <message> <source>by %1</source> <translation>by %1</translation> </message> <message> <source>from %1</source> <translation>from %1</translation> </message> <message> <source>Play %1 Radio</source> <translation>Play %1 Radio</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>Play</numerusform> <numerusform>Plays</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>Play in your library</numerusform> <numerusform>Plays in your library</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>Listener</numerusform> <numerusform>Listeners</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>With %1 and more.</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>With %1, %2 and more.</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>Edited on %1 | %2 Edit</translation> </message> <message> <source>Downloads</source> <translation>Downloads</translation> </message> <message> <source>Search on %1</source> <translation>Search on %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Buy on %1 %2</translation> </message> <message> <source>Physical</source> <translation>Physical</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>Recommended because you listen to %1.</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>Recommended because you listen to %1 and %2.</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>Recommended because you listen to %1, %2, and %3.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>Recommended because you listen to %1, %2, %3, and %4.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>Recommended because you listen to %1, %2, %3, %4, and %5.</translation> </message> <message> <source>From %1's library.</source> <translation>From %1's library.</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>From %1 and %2's libraries.</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 time</numerusform> <numerusform>%L1 times</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>From %1, %2, and %3's libraries.</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>You've listened to %1 %2 and %3 %4.</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>From %1, %2, %3, and %4's libraries.</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>You've listened to %1 %2, but not this track.</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>From %1, %2, %3, %4, and %5's libraries.</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>This is the first time you've listened to %1.</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>Hello!</translation> </message> <message> <source>Start a radio station</source> <translation>Start a radio station</translation> </message> <message> <source>Open iTunes</source> <translation>Open iTunes</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Open Windows Media Player</translation> </message> <message> <source>Open Winamp</source> <translation>Open Winamp</translation> </message> <message> <source>Open Foobar</source> <translation>Open Foobar</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></translation> </message> <message> <source>Hello, %1!</source> <translation>Hello, %1!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>A Radio Station</translation> </message> <message> <source>Play %1</source> <translation>Play %1</translation> </message> <message> <source>Multi-Library Radio</source> <translation>Multi-Library Radio</translation> </message> <message> <source>Cue %1</source> <translation>Cue %1</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>Play %1 and %2 Library Radio</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>Cue %1 and %2 Library Radio</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>Love</translation> </message> <message> <source>Ban</source> <translation>Ban</translation> </message> <message> <source>Play</source> <translation>Play</translation> </message> <message> <source>Skip</source> <translation>Skip</translation> </message> <message> <source>Pause</source> <translation>Pause</translation> </message> <message> <source>Resume</source> <translation>Resume</translation> </message> <message> <source>Unlove</source> <translation>Unlove</translation> </message> <message> <source>Tuning</source> <translation>Tuning</translation> </message> <message> <source>A Radio Station</source> <translation>A Radio Station</translation> </message> <message> <source>Listening to</source> <translation>Listening to</translation> </message> <message> <source>Scrobbling from</source> <translation>Scrobbling from</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 play</numerusform> <numerusform>%L1 plays</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>Last.fm has imported your media library. Click OK to continue.</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Last.fm Library Import</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>Are you sure you want to cancel the import?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm is importing your current media library...</translation> </message> <message> <source>Where is Winamp?</source> <translation>Where is Winamp?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Where is Windows Media Player?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>Media Library Import Complete</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</translation> </message> <message> <source>Library Import Failed</source> <translation>Library Import Failed</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>Your plugins are now being installed</translation> </message> <message> <source>Continue</source> <translation>Continue</translation> </message> <message> <source><< Back</source> <translation><< Back</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Your plugins haven't been installed</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>You can install them later through the file menu</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></translation> </message> <message> <source>(newer version)</source> <translation>(newer version)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(Plugin installed tick to reinstall)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</translation> </message> <message> <source>Install Plugins</source> <translation>Install Plugins</translation> </message> <message> <source>Continue</source> <translation>Continue</translation> </message> <message> <source><< Back</source> <translation><< Back</translation> </message> <message> <source>Skip >></source> <translation>Skip >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>General</translation> </message> <message> <source>Accounts</source> <translation>Accounts</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbling</translation> </message> <message> <source>Devices</source> <translation>Devices</translation> </message> <message> <source>Advanced</source> <translation>Advanced</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>%1 Radio</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 play</numerusform> <numerusform>%L1 plays</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>Top Artists This Week</translation> </message> <message> <source>Top Artists Overall</source> <translation>Top Artists Overall</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>Scrobble</numerusform> <numerusform>Scrobbles</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>Loved track</numerusform> <numerusform>Loved tracks</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 artist</numerusform> <numerusform>%L1 artists</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 track</numerusform> <numerusform>%L1 tracks</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>You have %1 in your library and on average listen to %2 per day.</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>Scrobble since %1</numerusform> <numerusform>Scrobbles since %1</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>Proxy Settings</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>Host:</translation> </message> <message> <source>Username:</source> <translation>Username:</translation> </message> <message> <source>Port:</source> <translation>Port:</translation> </message> <message> <source>Password:</source> <translation>Password:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>unknown media player</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>Where is your iPod mounted?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>Type an artist or tag and press play</translation> </message> <message> <source>Play</source> <translation>Play</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>Why not try %1, %2, %3 or %4?</translation> </message> <message> <source>Play next</source> <translation>Play next</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>A Radio Station</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>You need to be a subscriber to listen to radio</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>Last Station</translation> </message> <message> <source>A Radio Station</source> <translation>A Radio Station</translation> </message> <message> <source>Personal Stations</source> <translation>Personal Stations</translation> </message> <message> <source>My Library Radio</source> <translation>My Library Radio</translation> </message> <message> <source>Music you know and love</source> <translation>Music you know and love</translation> </message> <message> <source>My Mix Radio</source> <translation>My Mix Radio</translation> </message> <message> <source>Your library plus new music</source> <translation>Your library plus new music</translation> </message> <message> <source>My Recommended Radio</source> <translation>My Recommended Radio</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>Subscribe to listen to radio</translation> </message> <message> <source>New music from Last.fm</source> <translation>New music from Last.fm</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</translation> </message> <message> <source>Network Stations</source> <translation>Network Stations</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Subscribe to Last.fm</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>Listen free on www.last.fm</translation> </message> <message> <source>My Friends' Radio</source> <translation>My Friends' Radio</translation> </message> <message> <source>Music your friends like</source> <translation>Music your friends like</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>My Neighbourhood Radio</translation> </message> <message> <source>Music from listeners like you</source> <translation>Music from listeners like you</translation> </message> <message> <source>Recent Stations</source> <translation>Recent Stations</translation> </message> <message> <source>Now Playing</source> <translation>Now Playing</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>Subscribe to listen to radio, only %1 a month</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>Device Scrobbles</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>It looks like you've played these tracks. Would you like to scrobble them?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>Scrobble devices automatically</translation> </message> <message> <source>Toggle selection</source> <translation>Toggle selection</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>%n play has been scrobbled from a device</numerusform> <numerusform>%n plays have been scrobbled from a device</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>Love track</translation> </message> <message> <source>Add tags</source> <translation>Add tags</translation> </message> <message> <source>Love</source> <translation>Love</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Share</translation> </message> <message> <source>Share on Last.fm</source> <translation>Share on Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Share on Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Share on Facebook</translation> </message> <message> <source>Buy</source> <translation>Buy</translation> </message> <message> <source>Unlove track</source> <translation>Unlove track</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Scrobble at</translation> </message> <message> <source>percent of the track</source> <translation>percent of the track</translation> </message> <message> <source>Enable scrobbling</source> <translation>Enable scrobbling</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>...or at 4 minutes (whichever comes first)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>Scrobble podcasts</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>Allow Last.fm to fingerprint my tracks</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>Selected directories will not be scrobbled</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>More Scrobbles at Last.fm</translation> </message> <message> <source>Refreshing...</source> <translation>Refreshing...</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Refresh Scrobbles</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>Artist</translation> </message> <message> <source>Title</source> <translation>Title</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Plays</source> <translation>Plays</translation> </message> <message> <source>Last Played</source> <translation>Last Played</translation> </message> <message> <source>Loved</source> <translation>Loved</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>This track is under 30 seconds</translation> </message> <message> <source>The artist name is missing</source> <translation>The artist name is missing</translation> </message> <message> <source>Invalid track title</source> <translation>Invalid track title</translation> </message> <message> <source>Invalid artist</source> <translation>Invalid artist</translation> </message> <message> <source>There is no timestamp</source> <translation>There is no timestamp</translation> </message> <message> <source>This track is too far in the future</source> <translation>This track is too far in the future</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>This track was played over two weeks ago</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>You haven't scrobbled any music to Last.fm yet.</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>Start listening to some music in your media player or start a radio station:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>Share with Friends</translation> </message> <message> <source>With:</source> <translation>With:</translation> </message> <message> <source>Message (optional):</source> <translation>Message (optional):</translation> </message> <message> <source>include in my recent activity</source> <translation>include in my recent activity</translation> </message> <message> <source>A track by %1</source> <translation>A track by %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>A track by %1 from the release %2</translation> </message> <message> <source>Check out %1</source> <translation>Check out %1</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>Now Playing</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobbles</translation> </message> <message> <source>Profile</source> <translation>Profile</translation> </message> <message> <source>Friends</source> <translation>Friends</translation> </message> <message> <source>Radio</source> <translation>Radio</translation> </message> <message> <source>Next Section</source> <translation>Next Section</translation> </message> <message> <source>Previous Section</source> <translation>Previous Section</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>Could not start radio: %1</translation> </message> <message> <source>no results for "%1"</source> <translation>no results for "%1"</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>Scrobbling is off</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>Online</translation> </message> <message> <source>Offline</source> <translation>Offline</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Choose something to tag:</source> <translation>Choose something to tag:</translation> </message> <message> <source>Track</source> <translation>Track</translation> </message> <message> <source>Artist</source> <translation>Artist</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>icon</source> <translation>icon</translation> </message> <message> <source>Add tags:</source> <translation>Add tags:</translation> </message> <message> <source>A track by %1</source> <translation>A track by %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>A track by %1 from the release %2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>Type a tag above, or choose from below</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>Sort by Popularity</translation> </message> <message> <source>Sort Alphabetically</source> <translation>Sort Alphabetically</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>Open Last.fm Page for this Tag</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>That's it, you're good to go!</translation> </message> <message> <source>Finish</source> <translation>Finish</translation> </message> <message> <source><< Back</source> <translation><< Back</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>The Last.fm Desktop App in your menu bar</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>The Last.fm Desktop App in your system tray</translation> </message> <message> <source>Continue</source> <translation>Continue</translation> </message> <message> <source><< Back</source> <translation><< Back</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>Discover more about the artists you love</translation> </message> <message> <source>Continue</source> <translation>Continue</translation> </message> <message> <source><< Back</source> <translation><< Back</translation> </message> <message> <source>Skip Tour >></source> <translation>Skip Tour >></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>Listen to non-stop, personalised radio</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>Subscribe and listen to non-stop, personalised radio</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></translation> </message> <message> <source>Subscribe</source> <translation>Subscribe</translation> </message> <message> <source>Continue</source> <translation>Continue</translation> </message> <message> <source><< Back</source> <translation><< Back</translation> </message> <message> <source>Skip Tour >></source> <translation>Skip Tour >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>Welcome to the Last.fm Desktop App!</translation> </message> <message> <source>Continue</source> <translation>Continue</translation> </message> <message> <source><< Back</source> <translation><< Back</translation> </message> <message> <source>Skip Tour >></source> <translation>Skip Tour >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>Track</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Artist</source> <translation>Artist</translation> </message> <message> <source>Love</source> <translation>Love</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Share</translation> </message> <message> <source>Buy</source> <translation>Buy</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>Delete this scrobble from your profile</translation> </message> <message> <source>Play %1 Radio</source> <translation>Play %1 Radio</translation> </message> <message> <source>Cue %1 Radio</source> <translation>Cue %1 Radio</translation> </message> <message> <source>%1 Radio</source> <translation>%1 Radio</translation> </message> <message> <source>Cached</source> <translation>Cached</translation> </message> <message> <source>Error: %1</source> <translation>Error: %1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Share on Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Share on Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Share on Facebook</translation> </message> <message> <source>Now listening</source> <translation>Now listening</translation> </message> <message> <source>Downloads</source> <translation>Downloads</translation> </message> <message> <source>Search on %1</source> <translation>Search on %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Buy on %1 %2</translation> </message> <message> <source>Physical</source> <translation>Physical</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>Connected User Accounts:</translation> </message> <message> <source>Add New User Account</source> <translation>Add New User Account</translation> </message> <message> <source>Add User Error</source> <translation>Add User Error</translation> </message> <message> <source>This user has already been added.</source> <translation>This user has already been added.</translation> </message> <message> <source>Removing %1</source> <translation>Removing %1</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>Subscribe</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>Remove</translation> </message> <message> <source>(currently logged in)</source> <translation>(currently logged in)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>Accounts</translation> </message> <message> <source>Show Scrobbler</source> <translation>Show Scrobbler</translation> </message> <message> <source>Love</source> <translation>Love</translation> </message> <message> <source>Play</source> <translation>Play</translation> </message> <message> <source>Skip</source> <translation>Skip</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Share</translation> </message> <message> <source>Ban</source> <translation>Ban</translation> </message> <message> <source>Mute</source> <translation>Mute</translation> </message> <message> <source>Scrobble iPod...</source> <translation>Scrobble iPod...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Visit Last.fm profile</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Enable Scrobbling</translation> </message> <message> <source>Quit %1</source> <translation>Quit %1</translation> </message> <message> <source>from %1</source> <translation>from %1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>You've reached this station's skip limit. Skip again in %n minute.</numerusform> <numerusform>You've reached this station's skip limit. Skip again in %n minutes.</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>You have %n skip remaining on this station.</numerusform> <numerusform>You have %n skips remaining on this station.</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>Authentication Required</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>Are you sure you want to quit %1?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>%1 is about to quit. Tracks played will not be scrobbled if you continue.</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>Changing User</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>Please close the following apps to continue.</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>Plugin install error</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>There was an error updating your plugin.</p><p>Please try again later.</p></translation> </message> <message> <source>Plugin installed!</source> <translation>Plugin installed!</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>The %1 plugin hasn't been installed</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>You didn't close %1 so its plugin hasn't been installed.</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>Close iTunes for plugin update!</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></translation> </message> <message> <source>not installed</source> <translation>not installed</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>Your plugin hasn't been installed</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>There was an error while removing the old plugin</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>iTunes Plugin installed!</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>There was an error while copying the new plugin into place</translation> </message> <message> <source>You didn't close iTunes</source> <translation>You didn't close iTunes</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>Time is broken</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>%n minute ago</numerusform> <numerusform>%n minutes ago</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>%n hour ago</numerusform> <numerusform>%n hours ago</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>There was a network error: %1</translation> </message> <message> <source>You have not authorised this application</source> <translation>You have not authorised this application</translation> </message> <message> <source>Authentication Error</source> <translation>Authentication Error</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>Refresh Stylesheet</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>Don't ask this again</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>Auto-detect</translation> </message> <message> <source>No-proxy</source> <translation>No-proxy</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_es.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="es"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>Acerca de</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 (desarrollado en Qt %2)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>Haz clic en el botón <strong>Sí, permitir el acceso</strong> de tu explorador web para conectar tu cuenta de Last.fm con Last.fm Desktop App.</p><p>Si no la has conectado porque cerraste la ventana del explorador o hiciste clic en Cancelar, inténtalo de nuevo.</p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>Solo tienes que conectarte a Last.fm</translation> </message> <message> <source><< Back</source> <translation><< Atrás</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source>Try Again</source> <translation>Intentar de nuevo</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>Si tu explorador web no se ha abierto, copia y pega el vínculo en la barra de direcciones.</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>Atajos de teclado</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Mostrar/Ocultar Last.fm</translation> </message> <message> <source>Proxy:</source> <translation>Proxy:</translation> </message> <message> <source>Enable SSL</source> <translation>Activar SSL</translation> </message> <message> <source>Cache Size:</source> <translation>Tamaño de caché:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>Suscriptor</translation> </message> <message> <source>Moderator</source> <translation>Moderador</translation> </message> <message> <source>Staff</source> <translation>Personal Last.fm</translation> </message> <message> <source>Alumna</source> <translation>Veterana</translation> </message> <message> <source>Alumnus</source> <translation>Veterano</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>De gira</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>Para obtener las mejores recomendaciones posibles de acuerdo con la música que te gusta, te sugerimos que importes el historial de reproducción del reproductor de medios.</p><p>Selecciona tu reproductor de medios de preferencia y haz clic en <strong>Iniciar la importación</strong></p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>No se instalaron tus plugins</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Puedes instalarlos después a través del menú Archivo</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>Ahora importaremos tu historial de reproducción</translation> </message> <message> <source>Start Import</source> <translation>Iniciar la importación</translation> </message> <message> <source><< Back</source> <translation><< Atrás</translation> </message> <message> <source>Skip >></source> <translation>Omitir >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>No te preocupes, el proceso de carga tardará solo un par de minutos, en función del tamaño de tu colección de música.</p><p>Mientras trabajamos duro para agregar el historial de reproducción a tu perfil de Last.fm, ¿por qué no echas un vistazo a las características de Last.fm Desktop App? Haz clic en <strong>Continuar</strong> para realizar el tour.</p></translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Atrás</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>Cerrar aplicaciones</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>Scrobbling de dispositivo desactivado - Plugin no compatible - %1</translation> </message> <message> <source>please update</source> <translation>Actualizar</translation> </message> <message> <source>Scrobble iPod</source> <translation>Scrobbling del iPod</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>¿Quieres asociar el dispositivo %1 a tu cuenta de usuario de Audioscrobbler?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>El dispositivo se asoció correctamente a tu cuenta de usuario. A partir de ahora, puedes hacer el scrobbling de los temas que escuches en este dispositivo.</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>%1 scrobblings de temas.</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>No hay temas para el scrobbling desde la última sincronización.</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>No se pudo abrir la base de datos del iPod.</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>Se produjo un error desconocido al acceder a la base de datos del iPod.</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>Diagnóstico</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbling</translation> </message> <message> <source>This is an easter egg!</source> <translation>¡Esta es una pequeña sorpresa!</translation> </message> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Track</source> <translation>Tema</translation> </message> <message> <source>Album</source> <translation>Álbum</translation> </message> <message> <source>Fingerprinting</source> <translation>Asignar código (Fingerprinting)</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>Temas recientes con código</translation> </message> <message> <source>iPod Scrobbling</source> <translation>Scrobbling del iPod</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>iTunes administra mi iPod automáticamente</translation> </message> <message> <source>I manually manage my iPod</source> <translation>Administro mi iPod manualmente</translation> </message> <message> <source>Scrobble iPod</source> <translation>Scrobbling del iPod</translation> </message> <message> <source>Logs</source> <translation>Registros</translation> </message> <message> <source>&Close</source> <translation>&Cerrar</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n tema en caché local</numerusform> <numerusform>%n temas en caché local</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>Last.fm Desktop App</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>¡Gracias, <strong>%1</strong>! Tu cuenta ya está conectada.</translation> </message> <message> <source>Importing...</source> <translation>Importando...</translation> </message> <message> <source>Import complete!</source> <translation>Importación finalizada.</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Busca a tus amigos en Last.fm</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>Por ahora no tienes amigos en Last.fm.</h3><p>Usa el Buscador de amigos para buscar a tus contactos de correo electrónico o a tus amigos de Facebook en Last.fm.</p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>Busca a un amigo por su nombre de usuario o nombre real</translation> </message> <message> <source>Refresh Friends</source> <translation>Actualizar amigos</translation> </message> <message> <source>Refreshing...</source> <translation>Actualizando...</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>Radio de la colección de %1</translation> </message> <message> <source>Male</source> <translation>Hombre</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>Scrobbling en curso desde %1</translation> </message> <message> <source>Female</source> <translation>Mujer</translation> </message> <message> <source>Scrobbling now</source> <translation>Scrobbling en curso</translation> </message> <message> <source>Neuter</source> <translation>Neutral</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>Busca a tus amigos</translation> </message> <message> <source>Browse Friends</source> <translation>Explorar amigos</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>Idioma:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>Mostrar el icono de la aplicación en la barra de menús</translation> </message> <message> <source>Launch application with media players</source> <translation>Ejecutar la aplicación con los reproductores de medios</translation> </message> <message> <source>Show dock icon</source> <translation>Mostrar icono del dock</translation> </message> <message> <source>Show desktop notifications</source> <translation>Mostrar las notificaciones</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>Enviar los informes de errores a Last.fm</translation> </message> <message> <source>Check for updates automatically</source> <translation>Comprobar automáticamente las actualizaciones</translation> </message> <message> <source>Enable media keys</source> <translation>Activar claves de medios</translation> </message> <message> <source>System Language</source> <translation>Idioma del sistema</translation> </message> <message> <source>Restart now?</source> <translation>¿Reiniciar ahora?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>Para que los cambios surtan efecto, es necesario reiniciar la aplicación. ¿Quieres reiniciar ahora?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>Actualizar a versiones beta - Advertencia: ¡solo para valientes!</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>No se pudo abrir la base de datos del iPod.</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>El uso de una aplicación de scrobbling de iOS, como %1, puede generar la duplicación de los scrobblings. Activa el scrobbling solo en una aplicación.</p><p>iTunes Match puede sincronizar el recuento de las reproducciones en varios dispositivos, pero no la hora de las últimas reproducciones. Esto causará que se dupliquen los scrobblings, con horas incorrectas. Por ahora, recomendamos a los usuarios de iTunes Match que desactiven el scrobbling en los dispositivos de escritorio y hagan scrobbling desde iPhones/iPods con una aplicación de scrobbling de iOS, como %1.</p></translation> </message> <message> <source>Setting not changed</source> <translation>Configuración sin cambios</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>No cerraste iTunes para cambiar la configuración.</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>Activar el scrobbling del dispositivo</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>Confirmar el scrobbling del dispositivo</translation> </message> <message> <source>Please note</source> <translation>Recuerda:</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>Licencias</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>¿Listo?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>Cuando hayas aprobado esta aplicación, haz clic en Aceptar.</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Primero, Last.fm necesita tu permiso.</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>Esta aplicación necesita tu permiso para conectar con tu perfil de Last.fm. Haz clic en Aceptar para ir al sitio web de Last.fm y otorgar el permiso.</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>¿Ya eres usuario de Last.fm? Conecta tu cuenta con Last.fm Desktop App y tu perfil se actualizará con la música que escuches.</p><p>Si todavía no tienes una cuenta, puedes registrarte gratuitamente ahora mismo.</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Para empezar, conectaremos con tu cuenta de Last.fm</translation> </message> <message> <source>Connect Your Account</source> <translation>Conectar con tu cuenta</translation> </message> <message> <source>Sign up</source> <translation>Registrarse</translation> </message> <message> <source>Proxy?</source> <translation>¿Proxy?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>Hay actualizaciones de los plugins de tus reproductores de medios. ¿Quieres instalarlas ahora?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>Error en la instalación del plugin</numerusform> <numerusform>Error en la instalación de los plugins</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>Se produjo un error al actualizar el plugin.</p><p>Inténtalo de nuevo más tarde.</p></numerusform> <numerusform><p>Se produjo un error al actualizar los plugins.</p><p>Inténtalo de nuevo más tarde.</p> </numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>Plugin instalado.</numerusform> <numerusform>Plugins instalados.</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>Se instaló el plugin.</p><p>Ahora estás listo para hacer scrobbling con tu reproductor de medios</p></numerusform> <numerusform><p>Se instalaron los plugins.</p><p>Ahora estás listo para hacer scrobbling con tus reproductores de medios</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>No se instalaron tus plugins</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Puedes instalarlos después a través del menú Archivo</translation> </message> <message> <source>File</source> <translation>Archivo</translation> </message> <message> <source>Install plugins</source> <translation>Instalar plugins</translation> </message> <message> <source>&Quit</source> <translation>&Salir</translation> </message> <message> <source>View</source> <translation>Ver</translation> </message> <message> <source>My Last.fm Profile</source> <translation>Mi perfil de Last.fm</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobblings</translation> </message> <message> <source>Refresh</source> <translation>Actualizar</translation> </message> <message> <source>Controls</source> <translation>Controles</translation> </message> <message> <source>Account</source> <translation>Cuenta</translation> </message> <message> <source>Tools</source> <translation>Herramientas</translation> </message> <message> <source>Check for Updates</source> <translation>Comprobar actualizaciones</translation> </message> <message> <source>Options</source> <translation>Opciones</translation> </message> <message> <source>Window</source> <translation>Ventana</translation> </message> <message> <source>Minimize</source> <translation>Minimizar</translation> </message> <message> <source>Zoom</source> <translation>Zoom</translation> </message> <message> <source>Bring All to Front</source> <translation>Traer todo al frente</translation> </message> <message> <source>Help</source> <translation>Ayuda</translation> </message> <message> <source>About</source> <translation>Acerca de</translation> </message> <message> <source>FAQ</source> <translation>P+F</translation> </message> <message> <source>Forums</source> <translation>Foros</translation> </message> <message> <source>Tour</source> <translation>Tour</translation> </message> <message> <source>Show Licenses</source> <translation>Mostrar licencias</translation> </message> <message> <source>Diagnostics</source> <translation>Diagnóstico</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform><a href="tracks">%n scrobbling</a> desde un dispositivo</numerusform> <numerusform><a href="tracks">%n scrobblings</a> desde un dispositivo</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Volver a Scrobblings</translation> </message> <message> <source>Popular tags:</source> <translation>Tags populares:</translation> </message> <message> <source>Your tags:</source> <translation>Tus tags:</translation> </message> <message> <source>Similar Artists</source> <translation>Artistas similares</translation> </message> <message> <source>by %1</source> <translation>de %1</translation> </message> <message> <source>from %1</source> <translation>de %1</translation> </message> <message> <source>Play %1 Radio</source> <translation>Escuchar la radio de %1</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>scrobbling</numerusform> <numerusform>scrobblings</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>scrobbling en tu colección</numerusform> <numerusform>scrobblings en tu colección</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>oyente</numerusform> <numerusform>oyentes</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>Con %1 y otros más.</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>Con %1, %2 y otros más.</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>Editado el %1 | %2 Editar</translation> </message> <message> <source>Downloads</source> <translation>Descargas</translation> </message> <message> <source>Search on %1</source> <translation>Buscar en %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Comprar en %1 %2</translation> </message> <message> <source>Physical</source> <translation>CD</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>Recomendado porque escuchaste a %1.</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>Recomendado porque escuchaste a %1 y %2.</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>Recomendado porque escuchaste a %1, %2 y %3.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>Recomendado porque escuchaste a %1, %2, %3 y %4.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>Recomendado porque escuchaste a %1, %2, %3, %4 y %5.</translation> </message> <message> <source>From %1's library.</source> <translation>De la colección de %1.</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>De las colecciones de %1 y %2.</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 vez</numerusform> <numerusform>%L1 veces</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>De las colecciones de %1, %2 y %3.</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>Has escuchado a %1 %2 y %3 %4.</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>De las colecciones de %1, %2, %3 y %4.</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>Has escuchado a %1 %2, pero no este tema.</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>De las colecciones de %1, %2, %3, %4 y %5.</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>Es la primera vez que escuchas a %1.</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>¡Hola!</translation> </message> <message> <source>Start a radio station</source> <translation>Escuchar una emisora</translation> </message> <message> <source>Open Music</source> <translation>Abrir Music</translation> </message> <message> <source>Open iTunes</source> <translation>Abrir iTunes</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Abrir Windows Media Player</translation> </message> <message> <source>Open Winamp</source> <translation>Abrir Winamp</translation> </message> <message> <source>Open Foobar</source> <translation>Abrir Foobar</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>Haz scrobbling desde tu reproductor de música</h2><p>Solo tienes que escuchar algo de música en tu reproductor de medios. Encontrarás más información sobre los temas que escuchas en la pestaña Escuchando.</p></translation> </message> <message> <source>Hello, %1!</source> <translation>¡Hola, %1!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>Una emisora</translation> </message> <message> <source>Play %1</source> <translation>Escuchar %1</translation> </message> <message> <source>Multi-Library Radio</source> <translation>Radio multicolección</translation> </message> <message> <source>Cue %1</source> <translation>Seguir con %1</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>Escuchar la radio de la colección de %1 y %2</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>Seguir con la radio de las colecciones de %1 y %2</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>Favorito</translation> </message> <message> <source>Ban</source> <translation>Vetar</translation> </message> <message> <source>Play</source> <translation>Escuchar</translation> </message> <message> <source>Skip</source> <translation>Saltar</translation> </message> <message> <source>Pause</source> <translation>Pausa</translation> </message> <message> <source>Resume</source> <translation>Reanudar</translation> </message> <message> <source>Unlove</source> <translation>Quitar de favoritos</translation> </message> <message> <source>Tuning</source> <translation>Sintonizando</translation> </message> <message> <source>A Radio Station</source> <translation>Una emisora</translation> </message> <message> <source>Listening to</source> <translation>Escuchando</translation> </message> <message> <source>Scrobbling from</source> <translation>Scrobbling desde</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 scrobbling</numerusform> <numerusform>%L1 scrobblings</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>Last.fm ha importado tu biblioteca de medios. Haz clic en Aceptar para continuar.</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Importación de la colección de Last.fm</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>¿Realmente quieres cancelar la importación?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Last.fm no encontró temas escuchados en tu biblioteca de medios. Haz clic en Aceptar para continuar.</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm está importando tu biblioteca de medios...</translation> </message> <message> <source>Where is Winamp?</source> <translation>¿Dónde está Winamp?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>¿Dónde está Windows Media Player?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>Importación de la biblioteca de medios finalizada</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>Last.fm ha enviado tu historial de reproducción al servidor. Tu perfil se actualizará con los nuevos temas en unos minutos.</translation> </message> <message> <source>Library Import Failed</source> <translation>Error en la importación de la biblioteca</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Last.fm no pudo importar tu historial de reproducción. Esto se puede deber a que ya hayas hecho el scrobbling de un buen número de temas. El historial solo se puede importar a perfiles nuevos.</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>Sigue las instrucciones de tu sistema operativo para la instalación de los plugins.</p><p>Una vez que los plugins se hayan instalado en tu equipo, haz clic en <strong>Continuar</strong>.</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>Tus plugins se están instalando</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Atrás</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>No se instalaron tus plugins</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Puedes instalarlos después a través del menú Archivo</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>Tus reproductores de medios necesitan un plugin especial de Last.fm para hacer el scrobbling de la música que escuchas.</p><p>Selecciona los reproductores de medios para el scrobbling y haz clic en <strong>Instalar plugins</strong></p></translation> </message> <message> <source>(newer version)</source> <translation>(versión más reciente)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(Plugin instalado. Márcalo para volver a instalar.)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>Ahora instala los plugins de Last.fm para hacer scrobbling de la música que escuchas.</translation> </message> <message> <source>Install Plugins</source> <translation>Instalar plugins</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Atrás</translation> </message> <message> <source>Skip >></source> <translation>Omitir >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>General</translation> </message> <message> <source>Accounts</source> <translation>Cuentas</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbling</translation> </message> <message> <source>Devices</source> <translation>Dispositivos</translation> </message> <message> <source>Advanced</source> <translation>Avanzadas</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>Radio de %1</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 scrobbling</numerusform> <numerusform>%L1 scrobblings</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>Artistas más escuchados esta semana</translation> </message> <message> <source>Top Artists Overall</source> <translation>Artistas más escuchados de todos los tiempos</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>scrobbling</numerusform> <numerusform>scrobblings</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>tema favorito</numerusform> <numerusform>temas favoritos</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 artista</numerusform> <numerusform>%L1 artistas</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 tema</numerusform> <numerusform>%L1 temas</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>Tienes %1 en tu colección y escuchas una media de %2 al día.</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>scrobbling desde %1</numerusform> <numerusform>scrobblings desde %1</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>Ajustes del proxy</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>Host:</translation> </message> <message> <source>Username:</source> <translation>Nombre de usuario:</translation> </message> <message> <source>Port:</source> <translation>Puerto:</translation> </message> <message> <source>Password:</source> <translation>Contraseña:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>Reproductor de medios desconocido</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>¿Dónde está montado tu iPod?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>Pon un artista o tag y pulsa Escuchar</translation> </message> <message> <source>Play</source> <translation>Escuchar</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>¿Por qué no pruebas con %1, %2, %3 o %4?</translation> </message> <message> <source>Play next</source> <translation>Escuchar el siguiente</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>Una emisora</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>Para escuchar la radio, tienes que suscribirte.</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>Última emisora</translation> </message> <message> <source>A Radio Station</source> <translation>Una emisora</translation> </message> <message> <source>Personal Stations</source> <translation>Emisoras personales</translation> </message> <message> <source>My Library Radio</source> <translation>Radio de tu colección</translation> </message> <message> <source>Music you know and love</source> <translation>Música que conoces y te gusta</translation> </message> <message> <source>My Mix Radio</source> <translation>Tu emisora Mix</translation> </message> <message> <source>Your library plus new music</source> <translation>Tu colección + nueva música</translation> </message> <message> <source>My Recommended Radio</source> <translation>Tu radio recomendada</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>Suscríbete para escuchar la radio</translation> </message> <message> <source>New music from Last.fm</source> <translation>Nueva música de Last.fm</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>Para escuchar la radio en esta aplicación, tienes que suscribirte. ¡Suscríbete ahora para empezar a escuchar música y disfrutar de otras ventajas!</translation> </message> <message> <source>Network Stations</source> <translation>Emisoras de red</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Suscríbete a Last.fm</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>Escucha música gratis en www.last.fm</translation> </message> <message> <source>My Friends' Radio</source> <translation>Radio de tus amigos</translation> </message> <message> <source>Music your friends like</source> <translation>Música que escuchan tus amigos</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>Radio de tus vecinos</translation> </message> <message> <source>Music from listeners like you</source> <translation>Música de oyentes como tú</translation> </message> <message> <source>Recent Stations</source> <translation>Emisoras recientes</translation> </message> <message> <source>Now Playing</source> <translation>Escuchando</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>Suscríbete para escuchar la radio, ¡solo %1 al mes!</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>Scrobblings de dispositivos</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>Parece que has escuchados estos temas. ¿Quieres agregarlos como scrobblings?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>Scrobbling automático de dispositivos</translation> </message> <message> <source>Toggle selection</source> <translation>Alternar selección</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>Se hizo %n scrobbling desde un dispositivo</numerusform> <numerusform>Se hicieron %n scrobblings desde un dispositivo</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>Tema favorito</translation> </message> <message> <source>Add tags</source> <translation>Agregar tags</translation> </message> <message> <source>Love</source> <translation>Favorito</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Compartir</translation> </message> <message> <source>Share on Last.fm</source> <translation>Compartir en Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Compartir en Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Compartir en Facebook</translation> </message> <message> <source>Buy</source> <translation>Comprar</translation> </message> <message> <source>Unlove track</source> <translation>Quitar de mis favoritos</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Scrobbling al</translation> </message> <message> <source>percent of the track</source> <translation>% del tema</translation> </message> <message> <source>Enable scrobbling</source> <translation>Activar el scrobbling</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>...o a los 4 minutos (lo que suceda primero)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>Scrobbling de podcasts</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>Permitir a Last.fm que asigne códigos a mis temas</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>No se hará scrobbling en las carpetas seleccionadas.</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>Más scrobblings en Last.fm</translation> </message> <message> <source>Refreshing...</source> <translation>Actualizando...</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Actualizar scrobblings</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Title</source> <translation>Título</translation> </message> <message> <source>Album</source> <translation>Álbum</translation> </message> <message> <source>Plays</source> <translation>Reproducciones</translation> </message> <message> <source>Last Played</source> <translation>Última reproducción</translation> </message> <message> <source>Loved</source> <translation>Favorito</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>Este tema no llega a los 30 segundos</translation> </message> <message> <source>The artist name is missing</source> <translation>Falta el nombre del artista</translation> </message> <message> <source>Invalid track title</source> <translation>Título de tema no válido</translation> </message> <message> <source>Invalid artist</source> <translation>Artista no válido</translation> </message> <message> <source>There is no timestamp</source> <translation>No hay marca de tiempo</translation> </message> <message> <source>This track is too far in the future</source> <translation>Tema con marca de tiempo en el futuro</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>El tema se escuchó hace más de dos semanas</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>Todavía no has hecho scrobbling de música en Last.fm.</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>Para empezar, pon algo de música en tu reproductor de medios o escucha una emisora:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>Compartir con amigos</translation> </message> <message> <source>With:</source> <translation>Compartir con:</translation> </message> <message> <source>Message (optional):</source> <translation>Mensaje (opcional):</translation> </message> <message> <source>include in my recent activity</source> <translation>Incluir en mi actividad reciente</translation> </message> <message> <source>A track by %1</source> <translation>Un tema de %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Un tema de %1, del álbum %2</translation> </message> <message> <source>Check out %1</source> <translation>Echa un vistazo a %1</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>Escuchando</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobblings</translation> </message> <message> <source>Profile</source> <translation>Perfil</translation> </message> <message> <source>Friends</source> <translation>Amigos</translation> </message> <message> <source>Radio</source> <translation>Radio</translation> </message> <message> <source>Next Section</source> <translation>Sección siguiente</translation> </message> <message> <source>Previous Section</source> <translation>Sección anterior</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>No se pudo iniciar la radio: %1</translation> </message> <message> <source>no results for "%1"</source> <translation>No hay resultados para "%1"</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>El scrobbling está desactivado</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>Conectado</translation> </message> <message> <source>Offline</source> <translation>Desconectado</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Choose something to tag:</source> <translation>Poner tag a:</translation> </message> <message> <source>Track</source> <translation>Tema</translation> </message> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Album</source> <translation>Álbum</translation> </message> <message> <source>icon</source> <translation>Icono</translation> </message> <message> <source>Add tags:</source> <translation>Agregar tags:</translation> </message> <message> <source>A track by %1</source> <translation>Un tema de %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Un tema de %1, del álbum %2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>Escribe un tag arriba o selecciónalo abajo</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>Ordenar por popularidad</translation> </message> <message> <source>Sort Alphabetically</source> <translation>Ordenar alfabéticamente</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>Abrir la página de Last.fm de este tag</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>¡Ya estás listo para empezar! Haz clic en <strong>Finalizar</strong> y explora un poco.</p><p>También hemos terminado de importar tu historial de reproducción y lo hemos agregado a tu perfil de Last.fm.</p><p>Gracias por instalar Last.fm Desktop App, ¡esperamos que los disfrutes!</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>Es todo, ¡ya puedes empezar!</translation> </message> <message> <source>Finish</source> <translation>Finalizar</translation> </message> <message> <source><< Back</source> <translation><< Atrás</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>La flecha roja en la pantalla indica la ubicación de Last.fm Desktop App en la bandeja del sistema.</p><p>Haz clic en el icono para tener un acceso rápido a los controles de la radio, compartir un tema o ponerle un tag, editar tus preferencias y visitar tu perfil de Last.fm.</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>Last.fm Desktop App en la barra de menús</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>Last.fm Desktop App en la bandeja del sistema</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Atrás</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>Puedes obtener más información sobre la música que escuchas: biografías, estadísticas de reproducción, fotos y artistas similares, así como los tags que los oyentes aplican a esa música.</p><p>Solo tienes que echar un vistazo a la pestaña <strong>Escuchando</strong> o hacer clic en cualquier tema de la pestaña <strong>Scrobblings</strong>.</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>Descubre más sobre los artistas que te gustan</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Atrás</translation> </message> <message> <source>Skip Tour >></source> <translation>Omitir el tour >></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>Escucha una radio inagotable y personalizada</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Usa Last.fm Desktop App para escuchar una radio personalizada, basada en la música que te gusta.</p><p>Cada reproducción de la radio de Last.fm es única: desde emisoras basadas en artistas o tags, hasta recomendaciones inéditas a la medida de tus preferencias musicales.</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>Suscríbete y escucha una radio inagotable y personalizada</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Suscríbete a Last.fm y usa Last.fm Desktop App para escuchar una radio personalizada, basada en la música que te gusta.</p><p>Cada reproducción de la radio de Last.fm es única: desde emisoras basadas en artistas o tags, hasta recomendaciones inéditas a la medida de tus preferencias musicales.</p></translation> </message> <message> <source>Subscribe</source> <translation>Suscribirse</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Atrás</translation> </message> <message> <source>Skip Tour >></source> <translation>Omitir el tour >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>El software cliente se ejecutará en segundo plano y actualizará en silencio tu perfil de Last.fm con la música que escuchas. Después, utilizará esa información para hacerte recomendaciones, sugerirte conciertos, etc. </p><p>También puedes usar Last.fm Desktop App para obtener más información sobre los artistas que escuchas y reproducir una radio personalizada.</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>¡Bienvenido a Last.fm Desktop App!</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Atrás</translation> </message> <message> <source>Skip Tour >></source> <translation>Omitir el tour >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>Tema</translation> </message> <message> <source>Album</source> <translation>Álbum</translation> </message> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Love</source> <translation>Favorito</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Compartir</translation> </message> <message> <source>Buy</source> <translation>Comprar</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>Eliminar este scrobbling de tu perfil</translation> </message> <message> <source>Play %1 Radio</source> <translation>Escuchar la radio de %1</translation> </message> <message> <source>Cue %1 Radio</source> <translation>Seguir con la radio de %1</translation> </message> <message> <source>%1 Radio</source> <translation>Radio de %1</translation> </message> <message> <source>Cached</source> <translation>En caché</translation> </message> <message> <source>Error: %1</source> <translation>Error: %1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Compartir en Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Compartir en Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Compartir en Facebook</translation> </message> <message> <source>Now listening</source> <translation>En reproducción</translation> </message> <message> <source>Downloads</source> <translation>Descargas</translation> </message> <message> <source>Search on %1</source> <translation>Buscar en %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Comprar en %1 %2</translation> </message> <message> <source>Physical</source> <translation>CD</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>Cuentas de usuario conectadas:</translation> </message> <message> <source>Add New User Account</source> <translation>Agregar una nueva cuenta de usuario</translation> </message> <message> <source>Add User Error</source> <translation>Error al agregar un usuario</translation> </message> <message> <source>This user has already been added.</source> <translation>Este usuario ya está agregado.</translation> </message> <message> <source>Removing %1</source> <translation>Eliminando %1</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>¿Seguro que quieres eliminar este usuario? Se perderán todos los ajustes de usuario y tendrás que volver a autenticarte para hacer nuevos scrobblings.</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>Suscribirse</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>Eliminar</translation> </message> <message> <source>(currently logged in)</source> <translation>(conectado)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>Cuentas</translation> </message> <message> <source>Show Scrobbler</source> <translation>Mostrar Scrobbler</translation> </message> <message> <source>Love</source> <translation>Favorito</translation> </message> <message> <source>Play</source> <translation>Escuchar</translation> </message> <message> <source>Skip</source> <translation>Saltar</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Compartir</translation> </message> <message> <source>Ban</source> <translation>Vetar</translation> </message> <message> <source>Mute</source> <translation>Silencio</translation> </message> <message> <source>Scrobble iPod...</source> <translation>Scrobbling del iPod...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Ir al perfil de Last.fm</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Activar el scrobbling</translation> </message> <message> <source>Quit %1</source> <translation>Salir de %1</translation> </message> <message> <source>from %1</source> <translation>de %1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>Has llegado al límite de saltos de esta emisora. Podrás saltar de nuevo en %n minuto.</numerusform> <numerusform>Has llegado al límite de saltos de esta emisora. Podrás saltar de nuevo en %n minutos.</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>Te queda %n salto en esta emisora.</numerusform> <numerusform>Te quedan %n saltos en esta emisora.</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>Requiere autenticación</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>La cuenta de usuario <strong>%1</strong> ya no está validada en Last.fm.</p><p>Haz clic en Aceptar para iniciar el proceso de instalación y volver a autenticar esta cuenta.</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>¿Seguro que quieres salir de %1?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>%1 está a punto de cerrarse. Si continúas, los temas reproducidos no se agregarán como scrobblings.</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>Cambiando usuario</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 se conectará al Scrobbler y a la radio de Last.fm. Toda la música se agregará como scrobblings de esta cuenta. ¿Quieres continuar?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>Para continuar, cierra las siguientes aplicaciones.</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>Error en la instalación del plugin</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>Se produjo un error al actualizar el plugin.</p><p>Inténtalo de nuevo más tarde.</p></translation> </message> <message> <source>Plugin installed!</source> <translation>Plugin instalado.</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>Se instaló el plugin de %1.</p><p>Ahora estás listo para hacer scrobbling con %1.</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>No se instaló el plugin de %1.</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>El plugin no se instaló porque no cerraste %1.</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>Cierra iTunes para actualizar el plugin.</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>El plugin de iTunes (%2) es diferente al que lanzamos con esta versión de la aplicación (%1).</p><p>Cierra iTunes para actualizarlo.</p></translation> </message> <message> <source>not installed</source> <translation>no instalado</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>El plugin no se ha instalado.</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>Se produjo un error al eliminar el antiguo plugin.</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>Plugin de iTunes instalado.</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>Se instaló el plugin de iTunes.</p><p>Ahora estás listo para hacer scrobbling con tu dispositivo.</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>Se produjo un error al copiar el nuevo plugin en la ubicación correspondiente.</translation> </message> <message> <source>You didn't close iTunes</source> <translation>No cerraste iTunes.</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>Marca de tiempo errónea</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>Hace %n minuto</numerusform> <numerusform>Hace %n minutos</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>Hace %n hora</numerusform> <numerusform>Hace %n horas</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>Se produjo un error de red: %1</translation> </message> <message> <source>You have not authorised this application</source> <translation>No has autorizado a la aplicación.</translation> </message> <message> <source>Authentication Error</source> <translation>Error de autenticación</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>Actualizar hoja de estilos</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>No volver a preguntar</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>Detección automática</translation> </message> <message> <source>No-proxy</source> <translation>Sin proxy</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_fr.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="fr"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>À propos de</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 (réalisé avec Qt %2)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>Veuillez cliquer sur le bouton <strong>Oui, autoriser l'accès</strong> dans votre navigateur Web pour associer votre compte Last.fm à l'application de bureau Last.fm.</p><p>Si vous ne vous êtes pas connecté car vous avez fermé la fenêtre du navigateur ou cliqué sur Annuler, veuillez réessayer.<p /></p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>Nous attendons que vous vous connectiez à Last.fm</translation> </message> <message> <source><< Back</source> <translation><< Retour</translation> </message> <message> <source>Continue</source> <translation>Poursuivre </translation> </message> <message> <source>Try Again</source> <translation>Réessayer</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>Si votre navigateur ne s'ouvre pas, copiez et collez le lien ci-dessous dans votre barre d'adresse.</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>Raccourcis clavier :</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Afficher/Masquer Last.fm</translation> </message> <message> <source>Proxy:</source> <translation>Proxy :</translation> </message> <message> <source>Enable SSL</source> <translation>Activer SSL</translation> </message> <message> <source>Cache Size:</source> <translation>Taille du cache :</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>Abonné</translation> </message> <message> <source>Moderator</source> <translation>Modérateur</translation> </message> <message> <source>Staff</source> <translation>L'équipe</translation> </message> <message> <source>Alumna</source> <translation>Ancienne élève</translation> </message> <message> <source>Alumnus</source> <translation>Ancien élève</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>En tournée</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>Pour les meilleures recommandations sur base de vos goûts musicaux, nous vous conseillons d'importer votre historique d'écoute depuis votre lecteur média.</p><p>Veuillez sélectionner votre lecteur média préféré et cliquer sur <strong>Démarrer l'importation</strong></p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Vos plug-ins n'ont pas été installés</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Vous pourrez les installer ultérieurement depuis le menu Fichier</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>Maintenant, importons votre historique d'écoute</translation> </message> <message> <source>Start Import</source> <translation>Commencer l'importation</translation> </message> <message> <source><< Back</source> <translation><< Retour</translation> </message> <message> <source>Skip >></source> <translation>Passer >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>Ne vous inquiétez pas, le processus de téléchargement ne devrait pas prendre plus de quelques minutes, selon la taille de votre bibliothèque musicale.</p><p>Pendant que nous travaillons d'arrache-pied pour ajouter votre historique d'écoute à votre profil Last.fm, pourquoi ne jetteriez-vous pas un œil aux principales fonctionnalités de l'app de bureau Last.fm. Cliquez sur <strong>Continuer</strong> pour démarrer la présentation</p></translation> </message> <message> <source>Continue</source> <translation>Continuer</translation> </message> <message> <source><< Back</source> <translation><< Retour</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>Fermer les apps</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>Scrobbling depuis le périphérique désactivé - plug-in iTunes incompatible - %1</translation> </message> <message> <source>please update</source> <translation>veuillez procéder à la mise à jour</translation> </message> <message> <source>Scrobble iPod</source> <translation>Scrobbler l'iPod</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>Souhaitez-vous associer l'appareil %1 au scrobbler audio de votre compte d'utilisateur ?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>Appareil bien associé à votre compte d'utilisateur. Vous pouvez désormais scrobbler les titres que vous écoutez sur celui-ci.</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>%1 titres scrobblés.</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>Aucun titre à scrobbler depuis votre dernière synchronisation.</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>Impossible d'ouvrir la base de données de l'iPod.</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>Une erreur inconnue est survenue lors de la tentative d'accès à la base de données de l'iPod.</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>Diagnostics</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbling</translation> </message> <message> <source>This is an easter egg!</source> <translation>C'est un œuf de Pâques !</translation> </message> <message> <source>Artist</source> <translation>Artiste</translation> </message> <message> <source>Track</source> <translation>Titre</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Fingerprinting</source> <translation>Réaliser l'empreinte acoustique</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>Titres dont l'empreinte acoustique a été réalisée récemment</translation> </message> <message> <source>iPod Scrobbling</source> <translation>Scrobbling de l'iPod</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>iTunes gère mon iPod automatiquement</translation> </message> <message> <source>I manually manage my iPod</source> <translation>Je gère mon iPod manuellement</translation> </message> <message> <source>Scrobble iPod</source> <translation>Scrobbler l'iPod</translation> </message> <message> <source>Logs</source> <translation>Connexions</translation> </message> <message> <source>&Close</source> <translation>&Fermer</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n titre dans la mémoire cache locale</numerusform> <numerusform>%n titres dans la mémoire cache locale</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>App de bureau Last.fm</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>Merci <strong>%1</strong>, votre compte est désormais associé !</translation> </message> <message> <source>Importing...</source> <translation>Importation en cours</translation> </message> <message> <source>Import complete!</source> <translation>Importation terminée !</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Trouvez vos amis sur Last.fm</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>Vous ne vous êtes pas encore fait d'amis sur Last.fm.</h3><p>Trouvez vos amis Facebook et contactez-les par e-mail facilement et rapidement depuis Last.fm en utilisant l'outil de recherche d'amis.<p /></p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>Rechercher un ami par son nom ou son pseudo</translation> </message> <message> <source>Refresh Friends</source> <translation>Rafraîchir vos amis</translation> </message> <message> <source>Refreshing...</source> <translation>Rafraîchissement en cours</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>Radio de la bibliothèque de %1</translation> </message> <message> <source>Male</source> <translation>Homme</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>Scrobbling en cours depuis %1</translation> </message> <message> <source>Female</source> <translation>Femme</translation> </message> <message> <source>Scrobbling now</source> <translation>Scrobbling en cours</translation> </message> <message> <source>Neuter</source> <translation>Neutre</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>Chercher vos amis</translation> </message> <message> <source>Browse Friends</source> <translation>Parcourir vos amis</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>Langue :</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>Afficher l'icône de l'app dans la barre de menu</translation> </message> <message> <source>Launch application with media players</source> <translation>Lancer l'app depuis des lecteurs média</translation> </message> <message> <source>Show dock icon</source> <translation>Afficher l'icône du Dock</translation> </message> <message> <source>Show desktop notifications</source> <translation>Afficher les notifications de bureau</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>Envoyer les rapports d'incident à Last.fm</translation> </message> <message> <source>Check for updates automatically</source> <translation>Chercher automatiquement des mises à jour</translation> </message> <message> <source>Enable media keys</source> <translation>Activer les clés média</translation> </message> <message> <source>System Language</source> <translation>Langue du système</translation> </message> <message> <source>Restart now?</source> <translation>Redémarrer maintenant ?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>Un redémarrage de l'application est requis pour que les modifications soient appliquées. Aimeriez-vous la redémarrer maintenant ?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>Passer aux versions bêta. Attention, réservé aux intrépides !</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>Impossible d'ouvrir la base de données de l'iPod.</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>Utiliser une app de scrobbling sous iOS, comme %1, peut entraîner une duplication des scrobbles. Veuillez n'activer le scrobbling que sur l'une d'elles.</p><p>iTunes Match synchronise le nombre de lectures sur de multiples appareils mais pas les dernières lectures. Une duplication des scrobbles pourrait dès lors avoir lieu à des moments inopportuns. Nous recommandons, pour l'instant, aux utilisateurs d'iTunes Match de désactiver le scrobbling d'appareils sur les dispositifs de bureau et de ne scrobbler des iPhones/iPods qu'en utilisant une app de scrobbling sous iOS comme %1.</p></translation> </message> <message> <source>Setting not changed</source> <translation>Configuration non changée</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>Vous n'avez pas fermé iTunes pour que cette configuration soit modifiée</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>Activer le scrobbling sur cet appareil</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>Confirmation de scrobbling depuis cet appareil</translation> </message> <message> <source>Please note</source> <translation>Veuillez noter</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>Licences</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>C'est tout ?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>Cliquez sur OK une fois l'app approuvée.</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Last.fm a d'abord besoin de votre autorisation !</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>L'application a besoin de votre autorisation pour associer votre profil Last.fm. Cliquez sur OK pour vous rendre sur le site Web de Last.fm et le faire.</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>Vous êtes déjà utilisateur de Last.fm ? Associez votre compte à l'app de bureau Last.fm et mettez à jour votre profil en renseignant la musique que vous écoutez.</p><p>Si vous n'avez pas encore de compte, inscrivez-vous gratuitement maintenant.</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Commençons par associer votre compte Last.fm</translation> </message> <message> <source>Connect Your Account</source> <translation>Associer votre compte</translation> </message> <message> <source>Sign up</source> <translation>S'inscrire</translation> </message> <message> <source>Proxy?</source> <translation>Proxy ?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>Des mises à jour relatives aux plug-ins de votre lecteur média sont disponibles. Aimeriez-vous les installer maintenant ?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>Error d'installation du plug-in</numerusform> <numerusform>Error d'installation des plug-ins</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>Une erreur est survenue lors de la mise à jour de votre plug-in.</p><p>Veuillez réessayer ultérieurement.</p></numerusform> <numerusform><p>Une erreur est survenue lors de la mise à jour de vos plug-ins.</p><p>Veuillez réessayer ultérieurement.</p></numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>Plug-in installé !</numerusform> <numerusform>Plug-ins installés !</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>Votre plug-in a bien été installé.</p><p>Vous pouvez désormais scrobbler depuis votre lecteur média.</p></numerusform> <numerusform><p>Vos plug-ins ont bien été installés.</p><p>Vous pouvez désormais scrobbler depuis votre lecteur média.</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Vos plug-ins n'ont pas été installés</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Vous pourrez les installer ultérieurement depuis le menu Fichier</translation> </message> <message> <source>File</source> <translation>Fichier</translation> </message> <message> <source>Install plugins</source> <translation>Installer les plug-ins</translation> </message> <message> <source>&Quit</source> <translation>&Quitter</translation> </message> <message> <source>View</source> <translation>Voir</translation> </message> <message> <source>My Last.fm Profile</source> <translation>Mon profil Last.fm</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobbles</translation> </message> <message> <source>Refresh</source> <translation>Rafraîchir</translation> </message> <message> <source>Controls</source> <translation>Commandes</translation> </message> <message> <source>Account</source> <translation>Compte</translation> </message> <message> <source>Tools</source> <translation>Outils</translation> </message> <message> <source>Check for Updates</source> <translation>Chercher des mises à jour</translation> </message> <message> <source>Options</source> <translation>Options</translation> </message> <message> <source>Window</source> <translation>Fenêtre</translation> </message> <message> <source>Minimize</source> <translation>Réduire</translation> </message> <message> <source>Zoom</source> <translation>Zoom</translation> </message> <message> <source>Bring All to Front</source> <translation>Tout ramener au premier plan</translation> </message> <message> <source>Help</source> <translation>Aide</translation> </message> <message> <source>About</source> <translation>À propos</translation> </message> <message> <source>FAQ</source> <translation>FAQ</translation> </message> <message> <source>Forums</source> <translation>Forums</translation> </message> <message> <source>Tour</source> <translation>Présentation</translation> </message> <message> <source>Show Licenses</source> <translation>Afficher les licences</translation> </message> <message> <source>Diagnostics</source> <translation>Diagnostics</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1 : %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform><a href="titres">%n écoute</a> a été scrobblée depuis cet appareil</numerusform> <numerusform><a href="titres">%n écoutes</a> ont été scrobblées depuis cet appareil</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Revenir aux Scrobbles</translation> </message> <message> <source>Popular tags:</source> <translation>Tags populaires :</translation> </message> <message> <source>Your tags:</source> <translation>Vos tags :</translation> </message> <message> <source>Similar Artists</source> <translation>Artistes similaires</translation> </message> <message> <source>by %1</source> <translation>par %1</translation> </message> <message> <source>from %1</source> <translation>de %1</translation> </message> <message> <source>Play %1 Radio</source> <translation>Lecture de la radio %1</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>Écoute</numerusform> <numerusform>Écoutes</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>Écoute dans votre bibliothèque</numerusform> <numerusform>Écoutes dans votre bibliothèque</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>Auditeur</numerusform> <numerusform>Auditeurs</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>Avec %1 et plus.</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>Avec %1, %2 et plus.</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>Modifié le %1 | %2 Modifier</translation> </message> <message> <source>Downloads</source> <translation>Téléchargements</translation> </message> <message> <source>Search on %1</source> <translation>Chercher sur %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Acheter sur %1 %2</translation> </message> <message> <source>Physical</source> <translation>Physique</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>Recommandé car vous écoutez %1.</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>Recommandé car vous écoutez %1 et %2.</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>Recommandé car vous écoutez %1, %2 et %3.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>Recommandé car vous écoutez %1, %2, %3 et %4.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>Recommandé car vous écoutez %1, %2, %3, %4 et %5.</translation> </message> <message> <source>From %1's library.</source> <translation>Depuis la bibliothèque de %1.</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>Depuis les bibliothèques de %1 et %2.</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 fois</numerusform> <numerusform>%L1 fois</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>Depuis les bibliothèques de %1, %2 et %3.</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>Vous avez écouté %1, %2 et %3, %4.</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>Depuis les bibliothèques de %1, %2, %3 et %4.</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>Vous avez écouté %1, %2, mais pas ce titre.</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>Depuis les bibliothèques de %1, %2, %3, %4 et %5.</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>C'est la première fois que vous écoutez %1.</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>Salut !</translation> </message> <message> <source>Start a radio station</source> <translation>Lancer une station de radio</translation> </message> <message> <source>Open Music</source> <translation>Ouvrir Music</translation> </message> <message> <source>Open iTunes</source> <translation>Ouvrir iTunes</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Ouvrir Windows Media Player</translation> </message> <message> <source>Open Winamp</source> <translation>Ouvrir Winamp</translation> </message> <message> <source>Open Foobar</source> <translation>Ouvrir Foobar</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>Scrobbler depuis votre lecteur média</h2><p>Commencez à écouter de la musique depuis votre lecteur média. Vous pouvez découvrir plus d'informations sur les titres que vous écoutez en consultant l'onglet En écoute.</p></translation> </message> <message> <source>Hello, %1!</source> <translation>Salut, %1 !</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>Une station de radio</translation> </message> <message> <source>Play %1</source> <translation>Écouter %1</translation> </message> <message> <source>Multi-Library Radio</source> <translation>Radio multi-bibliothèque</translation> </message> <message> <source>Cue %1</source> <translation>Mettre %1 en file d'attente</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>Écouter les radios des bibliothèques %1 et %2</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>Mettre les radios des bibliothèques %1 et %2 en file d'attente</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>Aimer</translation> </message> <message> <source>Ban</source> <translation>Bannir</translation> </message> <message> <source>Play</source> <translation>Lire</translation> </message> <message> <source>Skip</source> <translation>Passer</translation> </message> <message> <source>Pause</source> <translation>Suspendre</translation> </message> <message> <source>Resume</source> <translation>Reprendre</translation> </message> <message> <source>Unlove</source> <translation>Ne plus aimer</translation> </message> <message> <source>Tuning</source> <translation>Réglage</translation> </message> <message> <source>A Radio Station</source> <translation>Une station de radio</translation> </message> <message> <source>Listening to</source> <translation>Écouter</translation> </message> <message> <source>Scrobbling from</source> <translation>Scrobbler depuis</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 écoute</numerusform> <numerusform>%L1 écoutes</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>Last.fm a importé votre bibiothèque media. Cliquez sur OK pour continuer.</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Importation de bibliothèque de Last.fm</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>Êtes-vous sûr de vouloir annuler cette importation ?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Last.fm n'a trouvé aucun titre diffusé dans votre bibliothèque média. Cliquez sur OK pour continuer.</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm est en train d'importer votre bibliothèque média actuelle.</translation> </message> <message> <source>Where is Winamp?</source> <translation>Où est Winamp ?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Où est le lecteur Windows Media ?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>Importation de la bibliothèque média terminée</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>Last.fm a envoyé votre historique d'écoute au serveur. Les nouveaux titres seront ajoutés à votre profil dans quelques minutes.</translation> </message> <message> <source>Library Import Failed</source> <translation>Échec de l'importation de la bibliothèque</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Désolé, Last.fm n'a pu importer votre historique d'écoute. Vous avez sans doute déjà scrobblé un trop grand nombre de titres. L'historique ne peut être importé que sur les nouveaux profils.</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>Veuillez suivre les instructions qui apparaissent sur votre système d'exploitation pour installer les plug-ins.</p><p>Une fois ceux-ci installés sur votre ordinateur, cliquez sur <strong>Continuer</strong>.</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>Vos plug-ins sont en cours d'installation</translation> </message> <message> <source>Continue</source> <translation>Continuer</translation> </message> <message> <source><< Back</source> <translation><< Retour</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Vos plug-ins n'ont pas été installés</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Vous pourrez les installer ultérieurement depuis le menu Fichier</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>Vos lecteurs média ont besoin d'un plug-in spécial Last.fm pour être en mesure de scrobbler la musique que vous écoutez.</p><p>Veuillez sélectionner les lecteurs média depuis lesquels vous aimeriez scrobbler et cliquez sur <strong>Installer les plug-ins</strong></p></translation> </message> <message> <source>(newer version)</source> <translation>(version plus récente)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(Plug-in installé, cochez pour réinstaller)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>Ensuite, installez les plug-ins Last.fm pour pouvoir scrobbler la musique que vous écoutez.</translation> </message> <message> <source>Install Plugins</source> <translation>Installer les plug-ins</translation> </message> <message> <source>Continue</source> <translation>Continuer</translation> </message> <message> <source><< Back</source> <translation><< Retour</translation> </message> <message> <source>Skip >></source> <translation>Passer >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>Général</translation> </message> <message> <source>Accounts</source> <translation>Comptes</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbling</translation> </message> <message> <source>Devices</source> <translation>Appareils</translation> </message> <message> <source>Advanced</source> <translation>Avancé</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>Radio %1</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 écoute</numerusform> <numerusform>%L1 écoutes</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>Les top artistes de la semaine</translation> </message> <message> <source>Top Artists Overall</source> <translation>Les top artistes de tous les temps</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>Scrobble</numerusform> <numerusform>Scrobbles</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>Coup de cœur</numerusform> <numerusform>Coups de cœur</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 artiste</numerusform> <numerusform>%L1 artistes </numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 titre</numerusform> <numerusform>%L1 titres</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>Vous avez %1 dans votre bibliothèque et écoutez %2 en moyenne par jour.</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>Scrobble depuis %1</numerusform> <numerusform>Scrobbles depuis %1</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>Paramètres du proxy</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>Hôte :</translation> </message> <message> <source>Username:</source> <translation>Nom d'utilisateur :</translation> </message> <message> <source>Port:</source> <translation>Port :</translation> </message> <message> <source>Password:</source> <translation>Mot de passe :</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>lecteur média inconnu</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>Où votre iPod est-il installé ?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>Saisir un artiste ou son tag puis I></translation> </message> <message> <source>Play</source> <translation>Lire</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>Pourquoi ne pas essayer %1, %2, %3 ou %4 ?</translation> </message> <message> <source>Play next</source> <translation>Écouter le suivant</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>Une station de radio</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>Vous devez être abonné pour pouvoir écouter la radio.</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>La dernière station</translation> </message> <message> <source>A Radio Station</source> <translation>Une station de radio</translation> </message> <message> <source>Personal Stations</source> <translation>Radios personnelles</translation> </message> <message> <source>My Library Radio</source> <translation>La radio de ma bibliothèque</translation> </message> <message> <source>Music you know and love</source> <translation>La musique que vous connaissez et que vous adorez</translation> </message> <message> <source>My Mix Radio</source> <translation>Ma Radio Mix</translation> </message> <message> <source>Your library plus new music</source> <translation>Votre bibliothèque et de la nouvelle musique</translation> </message> <message> <source>My Recommended Radio</source> <translation>Ma radio recommandée</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>Abonnez-vous pour pouvoir écouter la radio.</translation> </message> <message> <source>New music from Last.fm</source> <translation>De la nouvelle musique de Last.fm</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>Vous devez être abonné pour pouvoir écouter la radio avec cette app. Abonnez-vous maintenant pour commencer à écouter et bénéficiez d'une multitude d'autres avantages !</translation> </message> <message> <source>Network Stations</source> <translation>Stations du réseau</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Abonnez-vous à Last.fm</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>Écoutez gratuitement sur www.last.fm</translation> </message> <message> <source>My Friends' Radio</source> <translation>La radio de mes amis</translation> </message> <message> <source>Music your friends like</source> <translation>La musique que vos amis apprécient</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>Ma radio de proximité</translation> </message> <message> <source>Music from listeners like you</source> <translation>La musique d'auditeurs qui vous ressemblent</translation> </message> <message> <source>Recent Stations</source> <translation>Stations récentes</translation> </message> <message> <source>Now Playing</source> <translation>En écoute</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>Abonnez-vous pour écouter la radio pour seulement %1 par mois.</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>Les scrobbles depuis cet appareil</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>On dirait que vous avez écouté ces titres. Aimeriez-vous les scrobbler ?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>Scrobbler ces appareils automatiquement</translation> </message> <message> <source>Toggle selection</source> <translation>Basculer entre les sélections</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>%n écoute a été scrobblée depuis cet appareil</numerusform> <numerusform>%n écoutes ont été scrobblées depuis cet appareil</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>Aimer le titre</translation> </message> <message> <source>Add tags</source> <translation>Ajouter des tags</translation> </message> <message> <source>Love</source> <translation>Favori</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Partager</translation> </message> <message> <source>Share on Last.fm</source> <translation>Partager sur Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Partager sur Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Partager sur Facebook</translation> </message> <message> <source>Buy</source> <translation>Acheter</translation> </message> <message> <source>Unlove track</source> <translation>Ne plus aimer le titre</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Scrobblé à</translation> </message> <message> <source>percent of the track</source> <translation>pourcent du titre</translation> </message> <message> <source>Enable scrobbling</source> <translation>Activer le scrobbling</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>...ou à 4 minutes (selon ce qui se produit en premier)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>Scrobbler les podcasts</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>Permettre à Last.fm d'effectuer une empreinte acoustique de mes titres</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>Les répertoires sélectionnés ne seront pas scrobblés</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>Plus de scrobbles sur Last.fm</translation> </message> <message> <source>Refreshing...</source> <translation>Rafraîchissement en cours</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Rafraîchir les scrobbles</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>Artiste</translation> </message> <message> <source>Title</source> <translation>Titre</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Plays</source> <translation>En lecture</translation> </message> <message> <source>Last Played</source> <translation>Dernière lecture</translation> </message> <message> <source>Loved</source> <translation>Aimé</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>Ce titre fait moins de 30 secondes</translation> </message> <message> <source>The artist name is missing</source> <translation>Le nom de l'artiste est manquant</translation> </message> <message> <source>Invalid track title</source> <translation>Intitulé de titre non valide</translation> </message> <message> <source>Invalid artist</source> <translation>Artiste non valide</translation> </message> <message> <source>There is no timestamp</source> <translation>Il n'existe aucun horadatage</translation> </message> <message> <source>This track is too far in the future</source> <translation>Ce titre est trop éloigné dans le futur</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>Ce titre a été lu il y a plus de deux semaines</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>Vous n'avez scrobblé encore aucune musique de Last.fm.</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>Commencez à écouter de la musique de votre lecteur média ou lancez une station de radio :</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>Partager avec des amis</translation> </message> <message> <source>With:</source> <translation>Avec :</translation> </message> <message> <source>Message (optional):</source> <translation>Message (facultatif) :</translation> </message> <message> <source>include in my recent activity</source> <translation>à reprendre dans mes activités récentes</translation> </message> <message> <source>A track by %1</source> <translation>Un titre par %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Un titre par %1 de la sortie %2</translation> </message> <message> <source>Check out %1</source> <translation>Jetez un coup d'œil à %1</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>En écoute</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobbles</translation> </message> <message> <source>Profile</source> <translation>Profil</translation> </message> <message> <source>Friends</source> <translation>Amis </translation> </message> <message> <source>Radio</source> <translation>Radio</translation> </message> <message> <source>Next Section</source> <translation>Section suivante</translation> </message> <message> <source>Previous Section</source> <translation>Section précédente</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>Impossible de lancer la radio : %1</translation> </message> <message> <source>no results for "%1"</source> <translation>aucun résultat pour "%1"</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>Scrobbling désactivé</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>En ligne</translation> </message> <message> <source>Offline</source> <translation>Hors ligne</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Choose something to tag:</source> <translation>Choisissez quelque chose à taguer :</translation> </message> <message> <source>Track</source> <translation>Titre</translation> </message> <message> <source>Artist</source> <translation>Artiste</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>icon</source> <translation>Icône</translation> </message> <message> <source>Add tags:</source> <translation>Ajouter des tags :</translation> </message> <message> <source>A track by %1</source> <translation>Un titre par %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Un titre par %1 de la sortie %2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>Saisissez un tag ci-dessus, ou choisissez-en un ci-dessous</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>Classer par popularité</translation> </message> <message> <source>Sort Alphabetically</source> <translation>Classer par ordre alphabétique</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>Ouvrir la page Last.fm de ce tag</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>Vous voilà désormais fin prêt à commencer ! Cliquez simplement sur <strong>Terminer</strong> et commencez à explorer.</p><p>Nous avons également terminé l'importation de votre historique d'écoute et l'avons ajouté à votre profil Last.fm.</p><p>Merci d'avoir installé l'app de bureau Last.fm, nous espérons qu'elle vous plaira !</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>Vous voilà fin prêt !</translation> </message> <message> <source>Finish</source> <translation>Terminer</translation> </message> <message> <source><< Back</source> <translation><< Retour</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>La flèche rouge sur votre écran indique où se trouve l'app de bureau Last.fm dans la barre d'état de votre système.</p><p>Cliquez sur l'icône pour accéder rapidement aux commandes de lecture de la radio, pour partager et taguer des titres, pour modifier vos préférences et vous rendre sur votre profil Last.fm</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>L'app de bureau Last.fm dans votre barre de menu</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>L'app de bureau Last.fm dans la barre d'état de votre système</translation> </message> <message> <source>Continue</source> <translation>Continuer</translation> </message> <message> <source><< Back</source> <translation><< Retour</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>Découvrez-en plus sur la musique que vous écoutez, y compris les biographies, les stats d'écoute, les photos et les artistes similaires, ainsi que les tags que les auditeurs utilisent pour les décrire.</p><p>Découvrez l'onglet <strong>En écoute</strong> et cliquez simplement sur n'importe quel titre repris sous l'onglet <strong>Scrobbles</strong> pour en savoir plus.</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>Découvrez-en plus sur les artistes que vous aimez</translation> </message> <message> <source>Continue</source> <translation>Continuer</translation> </message> <message> <source><< Back</source> <translation><< Retour</translation> </message> <message> <source>Skip Tour >></source> <translation>Passer la présentation</translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>Écouter votre radio personnalisée sans interruption</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Utilisez l'app de bureau Last.fm pour écouter une radio personnalisée, basée sur la musique que vous préférez.</p><p>Chaque écoute de chaque station radio de Last.fm est complètement différente car les stations se basent sur des artistes, des tags voire des recommandations afin de répondre au mieux à vos goûts musicaux.</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>Abonnez-vous et écoutez votre radio personnalisée sans interruption</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Abonnez-vous à Last.fm et utilisez l'app de bureau Last.fm pour écouter une radio personnalisée, basée sur la musique que vous préférez.</p><p>Chaque écoute de chaque station radio de Last.fm est complètement différente car les stations se basent sur des artistes, des tags voire des recommandations afin de répondre au mieux à vos goûts musicaux</p></translation> </message> <message> <source>Subscribe</source> <translation>S'abonner</translation> </message> <message> <source>Continue</source> <translation>Continuer</translation> </message> <message> <source><< Back</source> <translation><< Retour</translation> </message> <message> <source>Skip Tour >></source> <translation>Passer la présentation >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>Le client de bureau fonctionne en arrière-plan, mettant à jour discrètement votre profil Last.fm et la musique que vous jouez. Vous pouvez l'utiliser pour découvrir des recommandations musicales, des actus concerts et bien plus encore. </p><p>Vous pouvez également utiliser l'app de bureau Last.fm pour en apprendre davantage sur l'artiste que vous écoutez et écouter votre radio personnalisée.</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>Bienvenue sur l'app de bureau Last.fm !</translation> </message> <message> <source>Continue</source> <translation>Continuer</translation> </message> <message> <source><< Back</source> <translation><< Retour</translation> </message> <message> <source>Skip Tour >></source> <translation>Passer la présentation >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>Titre</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Artist</source> <translation>Artiste</translation> </message> <message> <source>Love</source> <translation>Favori</translation> </message> <message> <source>Tag</source> <translation>Taguer</translation> </message> <message> <source>Share</source> <translation>Partager</translation> </message> <message> <source>Buy</source> <translation>Acheter</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>Supprimer ce scrobble de votre profil</translation> </message> <message> <source>Play %1 Radio</source> <translation>Lecture de la radio %1</translation> </message> <message> <source>Cue %1 Radio</source> <translation>Mettre la radio %1 en file d'attente</translation> </message> <message> <source>%1 Radio</source> <translation>Radio %1</translation> </message> <message> <source>Cached</source> <translation>Mis dans la mémoire cache</translation> </message> <message> <source>Error: %1</source> <translation>Erreur : %1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Partager sur Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Partager sur Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Partager sur Facebook</translation> </message> <message> <source>Now listening</source> <translation>En écoute</translation> </message> <message> <source>Downloads</source> <translation>Téléchargements</translation> </message> <message> <source>Search on %1</source> <translation>Chercher sur %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Acheter sur %1 %2</translation> </message> <message> <source>Physical</source> <translation>Physique</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>Comptes d'utilisateur associés :</translation> </message> <message> <source>Add New User Account</source> <translation>Ajouter un nouveau compte d'utilisateur</translation> </message> <message> <source>Add User Error</source> <translation>Ajouter une erreur d'utilisateur</translation> </message> <message> <source>This user has already been added.</source> <translation>L'utilisateur a déjà été ajouté</translation> </message> <message> <source>Removing %1</source> <translation>Suppression en cours de %1</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>Êtes-vous sûr de vouloir supprimer cet utilisateur ? Tous les paramètres de cet utilisateur seront perdus et vous devrez vous authentifier à nouveau pour scrobbler à l'avenir.</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>Actions</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>Supprimer</translation> </message> <message> <source>(currently logged in)</source> <translation>(connecté actuellement)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>Comptes</translation> </message> <message> <source>Show Scrobbler</source> <translation>Afficher le scrobbler</translation> </message> <message> <source>Love</source> <translation>Favori</translation> </message> <message> <source>Play</source> <translation>Lire</translation> </message> <message> <source>Skip</source> <translation>Passer</translation> </message> <message> <source>Tag</source> <translation>Taguer</translation> </message> <message> <source>Share</source> <translation>Partager</translation> </message> <message> <source>Ban</source> <translation>Bannir</translation> </message> <message> <source>Mute</source> <translation>Muet</translation> </message> <message> <source>Scrobble iPod...</source> <translation>Scrobbler l'iPod...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Visiter le profil Last.fm</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Activer le scrobbling</translation> </message> <message> <source>Quit %1</source> <translation>Quitter %1</translation> </message> <message> <source>from %1</source> <translation>de %1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform> Vous avez cliqué sur Suivant le maximum de fois autorisé pour le moment pour cette radio. Veuillez attendre %n minute avant de cliquer sur Suivant à nouveau.</numerusform> <numerusform> Vous avez cliqué sur Suivant le maximum de fois autorisé pour le moment pour cette radio. Veuillez attendre %n minutes avant de cliquer sur Suivant à nouveau.</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>Vous pouvez encore cliquer %n fois sur Suivant pour cette radio.</numerusform> <numerusform>Vous pouvez encore cliquer %n fois sur Suivant pour cette radio.</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>Authentification requise</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>Le compte d'utilisateur <strong>%1</strong> n'est plus authentifié avec Last.fm.</p><p>Cliquez sur OK pour commencer le processus d'authentification et authentifier à nouveau ce compte.</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>Êtes-vous sûr de vouloir quitter %1 ?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>Sur le point de quitter %1. Si vous continuez, les titres lus ne seront plus scrobblés.</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>Changement d'utilisateur en cours</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 sera connecté au scrobbler et à la radio Last.fm. Toute la musique sera désormais scrobblée sur ce compte. Voulez-vous continuer ?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>Veuillez fermer les apps suivantes pour continuer.</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>Erreur d'installation du plug-in</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>Une erreur est survenue lors de la mise à jour du plug-in.</p><p>Veuillez réessayer ultérieurement.</p></translation> </message> <message> <source>Plugin installed!</source> <translation>Plug-in installé !</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>Le plug-in %1 a bien été installé.</p><p>Vous êtes désormais prêt à scrobbler avec %1.</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>Le plug-in %1 n'a pas été installé</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>Vous n'avez pas fermé %1 donc son plug-in n'a pas été installé.</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>Fermez iTunes pour la mise à jour du plug-in !</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>Votre plug-in iTunes (%2) est différent de celui expédié avec cette version de l'app (%1).</p><p>Veuillez fermer iTunes maintenant pour le mettre à jour.</p></translation> </message> <message> <source>not installed</source> <translation>pas installé</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>Votre plug-in n'a pas été installé</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>Une erreur est survenue lors de la suppression de l'ancien plug-in</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>Plug-in iTunes installé !</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>Votre plug-in iTunes est bien installé.</p><p>Vous pouvez désormais scrobbler depuis votre périphérique.</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>Une erreur est survenue lors de la copie du nouveau plug-in à son nouvel emplacement</translation> </message> <message> <source>You didn't close iTunes</source> <translation>Vous n'avez pas fermé iTunes</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>Le temps s'est fait la malle</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>Il y a %n minute</numerusform> <numerusform>Il y a %n minutes</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>Il y a %n heure</numerusform> <numerusform>Il y a %n heures</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>Une erreur réseau est survenue : %1</translation> </message> <message> <source>You have not authorised this application</source> <translation>Vous n'avez pas autorisé cette application</translation> </message> <message> <source>Authentication Error</source> <translation>Erreur d’authentification</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>Rafraîchir la feuille de style</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>Ne plus me le demander</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>Détection automatique</translation> </message> <message> <source>No-proxy</source> <translation>Aucun proxy</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_it.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="it"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>Informazioni</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 (basato su Qt %2)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>Fai clic sul pulsante <strong>Sì, consenti l'accesso</strong> nel browser Web per collegare il tuo account di Last.fm all'app Last.fm per desktop.</p><p>Se non hai effettuato il collegamento perché hai chiuso la finestra del browser o hai fatto clic su "annulla", riprova.</p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>Stiamo aspettando che ti colleghi a Last.fm</translation> </message> <message> <source><< Back</source> <translation><< Indietro</translation> </message> <message> <source>Continue</source> <translation>Continua</translation> </message> <message> <source>Try Again</source> <translation>Riprova</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>Se il browser non si è aperto, copia e incolla il collegamento qui sotto nella barra degli indirizzi.</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>Scorciatoie da tastiera:</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Mostra/Nascondi Last.fm</translation> </message> <message> <source>Proxy:</source> <translation>Proxy:</translation> </message> <message> <source>Enable SSL</source> <translation>Attiva SSL</translation> </message> <message> <source>Cache Size:</source> <translation>Dimensioni cache:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>Abbonato</translation> </message> <message> <source>Moderator</source> <translation>Moderatore</translation> </message> <message> <source>Staff</source> <translation>Staff</translation> </message> <message> <source>Alumna</source> <translation>Ex dipendente</translation> </message> <message> <source>Alumnus</source> <translation>Ex dipendente</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>In tour</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>Per ottenere i migliori consigli possibili in base ai tuoi gusti musicali, importa la cronologia di ascolto dal tuo lettore multimediale.</p><p>Seleziona il lettore che preferisci e fai clic su <strong>Avvia importazione</strong></p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>I plug-in non sono stati installati</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Puoi installarli in seguito dal menu File</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>Ora importiamo la tua cronologia di ascolto</translation> </message> <message> <source>Start Import</source> <translation>Avvia importazione</translation> </message> <message> <source><< Back</source> <translation><< Indietro</translation> </message> <message> <source>Skip >></source> <translation>Ignora >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>Il processo di caricamento non dovrebbe richiedere più di qualche minuto, in base alle dimensioni della tua libreria musicale.</p><p>Mentre aggiungiamo la cronologia di ascolto al tuo profilo di Last.fm, dai un'occhiata alle principali funzioni dell'app Last.fm per desktop. Fai clic su <strong>Continua</strong> per iniziare il tour.</p></translation> </message> <message> <source>Continue</source> <translation>Continua</translation> </message> <message> <source><< Back</source> <translation><< Indietro</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>Chiudi applicazioni</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>Scrobbling dai dispositivi disattivato. Plug-in di iTunes non compatibile. %1</translation> </message> <message> <source>please update</source> <translation>Aggiorna</translation> </message> <message> <source>Scrobble iPod</source> <translation>Scrobbling dall'iPod</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>Vuoi associare il dispositivo %1 al tuo account utente di Audioscrobbler?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>Il dispositivo è stato associato al tuo account utente. Ora puoi eseguire lo scrobbling dei brani che ascolti sul dispositivo.</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>%1 scrobbling di brani.</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>Nessun brano di cui eseguire lo scrobbling dall'ultima sincronizzazione.</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>Impossibile accedere al database dell'iPod.</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>Si è verificato un errore sconosciuto durante il tentativo di accesso al database dell'iPod.</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>Diagnostica</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbling</translation> </message> <message> <source>This is an easter egg!</source> <translation>Sorpresa!</translation> </message> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Track</source> <translation>Brano</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Fingerprinting</source> <translation>Fingerprinting</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>Brani recenti di cui è stato eseguito il fingerprinting</translation> </message> <message> <source>iPod Scrobbling</source> <translation>Scrobbling dall'iPod</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>iPod gestito automaticamente da iTunes</translation> </message> <message> <source>I manually manage my iPod</source> <translation>iPod gestito manualmente</translation> </message> <message> <source>Scrobble iPod</source> <translation>Scrobbling dall'iPod</translation> </message> <message> <source>Logs</source> <translation>Log</translation> </message> <message> <source>&Close</source> <translation>&Chiudi</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n brano memorizzato nella cache locale</numerusform> <numerusform>%n brani memorizzati nella cache locale</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>App Last.fm per desktop</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>Grazie <strong>%1</strong>, il tuo account è collegato!</translation> </message> <message> <source>Importing...</source> <translation>Importazione in corso...</translation> </message> <message> <source>Import complete!</source> <translation>Importazione completata!</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Trova i tuoi amici su Last.fm</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>Non hai ancora amici su Last.fm.</h3><p>Trova i tuoi amici di Facebook e contatti e-mail su Last.fm in modo semplice e rapido con la funzione Trova amici.</p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>Cerca un amico per nome utente o nome reale</translation> </message> <message> <source>Refresh Friends</source> <translation>Aggiorna amici</translation> </message> <message> <source>Refreshing...</source> <translation>Aggiornamento in corso...</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>La libreria di %1</translation> </message> <message> <source>Male</source> <translation>Maschio</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>Scrobbling in corso da %1</translation> </message> <message> <source>Female</source> <translation>Femmina</translation> </message> <message> <source>Scrobbling now</source> <translation>Scrobbling in corso</translation> </message> <message> <source>Neuter</source> <translation>Sconosciuto</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>Cerca i tuoi amici</translation> </message> <message> <source>Browse Friends</source> <translation>Cerca amici</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>Lingua:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>Mostra l'icona dell'applicazione sulla barra dei menu</translation> </message> <message> <source>Launch application with media players</source> <translation>Avvia applicazione con lettori multimediali</translation> </message> <message> <source>Show dock icon</source> <translation>Mostra icona del Dock</translation> </message> <message> <source>Show desktop notifications</source> <translation>Mostra notifiche sul desktop</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>Invia segnalazioni di arresto anomalo a Last.fm</translation> </message> <message> <source>Check for updates automatically</source> <translation>Controlla aggiornamenti automaticamente</translation> </message> <message> <source>Enable media keys</source> <translation>Attiva tasti multimediali</translation> </message> <message> <source>System Language</source> <translation>Lingua del sistema</translation> </message> <message> <source>Restart now?</source> <translation>Riavviare ora?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>È necessario riavviare l'applicazione per rendere effettiva la modifica. Vuoi riavviare ora?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>Aggiorna le versioni beta</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>Impossibile accedere al database dell'iPod.</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>Se usi un'app di scrobbling per iOS come %1 potresti creare scrobbling duplicati. Attiva lo scrobbling in una sola app.</p><p>iTunes Match sincronizza tra più dispositivi il numero di ascolti, ma non la data dell'ultimo ascolto. Ciò comporta la creazione di scrobbling duplicati con date errate. Per ora, consigliamo agli utenti di iTunes Match di disattivare lo scrobbling dai dispositivi sui dispositivi per desktop e di eseguire lo scrobbling da iPhone/iPod con un'app di scrobbling per iOS come %1.</p></translation> </message> <message> <source>Setting not changed</source> <translation>Impostazione non modificata</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>Non hai chiuso iTunes, quindi l'impostazione non è stata modificata</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>Attiva scrobbling dai dispositivi</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>Conferma scrobbling dai dispositivi</translation> </message> <message> <source>Please note</source> <translation>Nota</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>Licenze</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>Fatto?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>Una volta autorizzata l'applicazione, fai clic su OK.</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Last.fm ha bisogno della tua autorizzazione!</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>Devi autorizzazione questa applicazione a collegarsi al tuo profilo di Last.fm. Fai clic su OK per accedere al sito di Last.fm e fornire l'autorizzazione.</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>Sei già un utente di Last.fm? Collega il tuo account all'app Last.fm per desktop e aggiorneremo il tuo profilo con la musica che ascolti.</p><p>Se non hai un account, puoi registrarti ora. È gratis!</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Per iniziare, collega il tuo account di Last.fm</translation> </message> <message> <source>Connect Your Account</source> <translation>Collega il tuo account</translation> </message> <message> <source>Sign up</source> <translation>Registrati</translation> </message> <message> <source>Proxy?</source> <translation>Proxy?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>Sono disponibili aggiornamenti per i plug-in dei tuoi lettori multimediali. Vuoi installarli ora?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>Errore di installazione del plug-in</numerusform> <numerusform>Errore di installazione dei plug-in</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>Si è verificato un errore durante l'aggiornamento del plug-in.</p><p>Riprova più tardi.</p></numerusform> <numerusform><p>Si è verificato un errore durante l'aggiornamento dei plug-in.</p><p>Riprova più tardi.</p></numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>Plug-in installato!</numerusform> <numerusform>Plug-in installati!</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>Il plug-in è stato installato.</p><p>Ora puoi eseguire lo scobbling con il tuo lettore multimediale</p></numerusform> <numerusform><p>I plug-in sono stati installati.</p><p>Ora puoi eseguire lo scobbling con i tuoi lettori multimediali</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>I plug-in non sono stati installati</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Puoi installarli in seguito dal menu File</translation> </message> <message> <source>File</source> <translation>File</translation> </message> <message> <source>Install plugins</source> <translation>Installa plug-in</translation> </message> <message> <source>&Quit</source> <translation>&Chiudi</translation> </message> <message> <source>View</source> <translation>Visualizza</translation> </message> <message> <source>My Last.fm Profile</source> <translation>Il mio profilo di Last.fm</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobbling</translation> </message> <message> <source>Refresh</source> <translation>Aggiorna</translation> </message> <message> <source>Controls</source> <translation>Controlli</translation> </message> <message> <source>Account</source> <translation>Account</translation> </message> <message> <source>Tools</source> <translation>Strumenti</translation> </message> <message> <source>Check for Updates</source> <translation>Controlla aggiornamenti</translation> </message> <message> <source>Options</source> <translation>Opzioni</translation> </message> <message> <source>Window</source> <translation>Finestra</translation> </message> <message> <source>Minimize</source> <translation>Riduci a icona</translation> </message> <message> <source>Zoom</source> <translation>Ingrandisci</translation> </message> <message> <source>Bring All to Front</source> <translation>Porta in primo piano</translation> </message> <message> <source>Help</source> <translation>Guida</translation> </message> <message> <source>About</source> <translation>Informazioni</translation> </message> <message> <source>FAQ</source> <translation>FAQ</translation> </message> <message> <source>Forums</source> <translation>Forum</translation> </message> <message> <source>Tour</source> <translation>Tour</translation> </message> <message> <source>Show Licenses</source> <translation>Visualizza licenze</translation> </message> <message> <source>Diagnostics</source> <translation>Diagnostica</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform><a href="tracks">%n scrobbling</a> da un dispositivo</numerusform> <numerusform><a href="tracks">%n scrobbling</a> da un dispositivo</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Torna agli scrobbling</translation> </message> <message> <source>Popular tags:</source> <translation>Tag più usati:</translation> </message> <message> <source>Your tags:</source> <translation>I tuoi tag:</translation> </message> <message> <source>Similar Artists</source> <translation>Artisti simili</translation> </message> <message> <source>by %1</source> <translation>di %1</translation> </message> <message> <source>from %1</source> <translation>da %1</translation> </message> <message> <source>Play %1 Radio</source> <translation>Ascolta radio di %1</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>Ascolto</numerusform> <numerusform>Ascolti</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>Ascolto nella tua libreria</numerusform> <numerusform>Ascolti nella tua libreria</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>Ascoltatore</numerusform> <numerusform>Ascoltatori</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>Con %1 e altri.</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>Con %1, %2 e altri.</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>Modificato il %1 | %2 Modifica</translation> </message> <message> <source>Downloads</source> <translation>Download</translation> </message> <message> <source>Search on %1</source> <translation>Cerca su %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Acquista su %1 %2</translation> </message> <message> <source>Physical</source> <translation>Supporto fisico</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>Consigliato perché ascolti %1.</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>Consigliato perché ascolti %1 e %2.</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>Consigliato perché ascolti %1, %2 e %3.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>Consigliato perché ascolti %1, %2, %3 e %4.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>Consigliato perché ascolti %1, %2, %3, %4 e %5.</translation> </message> <message> <source>From %1's library.</source> <translation>Dalla libreria di %1.</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>Dalle librerie di %1 e %2.</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 volta</numerusform> <numerusform>%L1 volte</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>Dalle librerie di %1, %2 e %3.</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>Hai ascoltato %1 %2 e %3 %4.</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>Dalle librerie di %1, %2, %3 e %4.</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>Hai ascoltato %1 %2, ma non questo brano.</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>Dalle librerie di %1, %2, %3, %4 e %5.</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>Questa è la prima volta che ascolti %1.</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>Ciao!</translation> </message> <message> <source>Start a radio station</source> <translation>Avvia una stazione radio</translation> </message> <message> <source>Open iTunes</source> <translation>Apri iTunes</translation> </message> <message> <source>Open Music</source> <translation>Apri Music</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Apri Windows Media Player</translation> </message> <message> <source>Open Winamp</source> <translation>Apri Winamp</translation> </message> <message> <source>Open Foobar</source> <translation>Apri Foobar</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>Esegui lo scrobbling dal tuo lettore musicale</h2><p>Inizia ad ascoltare musica nel lettore. Puoi vedere ulteriori informazioni sui brani che ascolti nella scheda In ascolto.</p></translation> </message> <message> <source>Hello, %1!</source> <translation>Ciao %1!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>Una stazione radio</translation> </message> <message> <source>Play %1</source> <translation>Ascolta %1</translation> </message> <message> <source>Multi-Library Radio</source> <translation>Radio di diverse librerie</translation> </message> <message> <source>Cue %1</source> <translation>Metti in coda %1</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>Ascolta libreria di %1 e %2</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>Metti in coda libreria di %1 e %2</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>Aggiungi ai preferiti</translation> </message> <message> <source>Ban</source> <translation>Escludi</translation> </message> <message> <source>Play</source> <translation>Ascolta</translation> </message> <message> <source>Skip</source> <translation>Brano successivo</translation> </message> <message> <source>Pause</source> <translation>Pausa</translation> </message> <message> <source>Resume</source> <translation>Riprendi</translation> </message> <message> <source>Unlove</source> <translation>Elimina dai preferiti</translation> </message> <message> <source>Tuning</source> <translation>Sintonizzazione</translation> </message> <message> <source>A Radio Station</source> <translation>Una stazione radio</translation> </message> <message> <source>Listening to</source> <translation>In ascolto di</translation> </message> <message> <source>Scrobbling from</source> <translation>Scrobbling in corso da</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 ascolto</numerusform> <numerusform>%L1 ascolti</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>La tua libreria multimediale è stata importata. Fai clic su OK per continuare.</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Importazione della libreria su Last.fm</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>Vuoi annullare l'importazione?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Impossibile trovare brani ascoltati nella libreria multimediale. Fai clic su OK per continuare.</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm sta importando la tua libreria multimediale...</translation> </message> <message> <source>Where is Winamp?</source> <translation>Dov'è Winamp?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Dov'è Windows Media Player?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>Importazione della libreria multimediale completata</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>La tua cronologia di ascolto è stata inviata al server. Il tuo profilo verrà aggiornato con i nuovi brani tra qualche minuto.</translation> </message> <message> <source>Library Import Failed</source> <translation>Importazione della libreria non riuscita</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Impossibile importare la tua cronologia di ascolto. Probabilmente hai già eseguito lo scrobbling di troppi brani. Le cronologie di ascolto possono essere importate solo nei profili appena creati.</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>Segui le istruzioni visualizzate nel tuo sistema operativo per installare i plug-in.</p><p>Una volta completata l'installazione, fai clic su <strong>Continua</strong>.</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>I plug-in verranno installati</translation> </message> <message> <source>Continue</source> <translation>Continua</translation> </message> <message> <source><< Back</source> <translation><< Indietro</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>I plug-in non sono stati installati</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Puoi installarli in seguito dal menu File</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>Per eseguire lo scrobbling con i tuoi lettori multimediali, sono necessari appositi plug-in di Last.fm.</p><p>Seleziona i lettori che vorresti utilizzare per lo scrobbling e fai clic su <strong>Installa plug-in</strong>.</p></translation> </message> <message> <source>(newer version)</source> <translation>(versione più recente)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(Plug-in installato. Seleziona per reinstallarlo)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>Ora installa i plug-in di Last.fm per eseguire lo scrobbling della musica che ascolti.</translation> </message> <message> <source>Install Plugins</source> <translation>Installa plug-in</translation> </message> <message> <source>Continue</source> <translation>Continua</translation> </message> <message> <source><< Back</source> <translation><< Indietro</translation> </message> <message> <source>Skip >></source> <translation>Ignora >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>Generale</translation> </message> <message> <source>Accounts</source> <translation>Account</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbling</translation> </message> <message> <source>Devices</source> <translation>Dispositivi</translation> </message> <message> <source>Advanced</source> <translation>Avanzate</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>Radio di %1</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 ascolto</numerusform> <numerusform>%L1 ascolti</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>Artisti più ascoltati questa settimana</translation> </message> <message> <source>Top Artists Overall</source> <translation>Artisti più ascoltati in generale</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>Scrobbling</numerusform> <numerusform>Scrobbling</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>Brano preferito</numerusform> <numerusform>Brani preferiti</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 artista</numerusform> <numerusform>%L1 artisti</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 brano</numerusform> <numerusform>%L1 brani</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>Hai %1 nella tua libreria e in media ascolti %2 al giorno.</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>Scrobbling dal %1</numerusform> <numerusform>Scrobbling dal %1</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>Impostazioni proxy</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>Host:</translation> </message> <message> <source>Username:</source> <translation>Nome utente:</translation> </message> <message> <source>Port:</source> <translation>Porta:</translation> </message> <message> <source>Password:</source> <translation>Password:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>lettore multimediale sconosciuto</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>Dove è connesso il tuo iPod?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>Digita artista/tag e fai clic su Ascolta</translation> </message> <message> <source>Play</source> <translation>Ascolta</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>Perché non provi %1, %2, %3 o %4?</translation> </message> <message> <source>Play next</source> <translation>Ascolta successivo</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>Una stazione radio</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>Devi essere abbonato per ascoltare la radio</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>Ultima stazione</translation> </message> <message> <source>A Radio Station</source> <translation>Una stazione radio</translation> </message> <message> <source>Personal Stations</source> <translation>Stazioni personali</translation> </message> <message> <source>My Library Radio</source> <translation>La mia libreria</translation> </message> <message> <source>Music you know and love</source> <translation>Musica che conosci e ami</translation> </message> <message> <source>My Mix Radio</source> <translation>La mia radio mixata</translation> </message> <message> <source>Your library plus new music</source> <translation>La tua libreria e nuova musica</translation> </message> <message> <source>My Recommended Radio</source> <translation>La radio dei miei consigli</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>Abbonati per ascoltare la radio</translation> </message> <message> <source>New music from Last.fm</source> <translation>Nuova musica da Last.fm</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>Devi essere abbonato a Last.fm per ascoltare la radio in questa app. Abbonati ora per iniziare ad ascoltare e approfittare di altri fantastici vantaggi!</translation> </message> <message> <source>Network Stations</source> <translation>Stazioni del network</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Abbonati a Last.fm</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>Ascolta gratis su www.last.fm</translation> </message> <message> <source>My Friends' Radio</source> <translation>La radio dei miei amici</translation> </message> <message> <source>Music your friends like</source> <translation>Musica che piace ai tuoi amici</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>La radio dei miei vicini</translation> </message> <message> <source>Music from listeners like you</source> <translation>Musica di ascoltatori come te</translation> </message> <message> <source>Recent Stations</source> <translation>Stazioni recenti</translation> </message> <message> <source>Now Playing</source> <translation>In ascolto</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>Abbonati per ascoltare la radio, solo %1 al mese</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>Scrobbling dai dispositivi</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>Hai ascoltato questi brani. Vuoi eseguirne lo scrobbling?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>Esegui automaticamente lo scrobbling dai dispositivi</translation> </message> <message> <source>Toggle selection</source> <translation>Attiva/Disattiva selezione</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>%n scrobbling da un dispositivo</numerusform> <numerusform>%n scrobbling da un dispositivo</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>Aggiungi brano ai preferiti</translation> </message> <message> <source>Add tags</source> <translation>Aggiungi tag</translation> </message> <message> <source>Love</source> <translation>Aggiungi ai preferiti</translation> </message> <message> <source>Tag</source> <translation>Aggiungi tag</translation> </message> <message> <source>Share</source> <translation>Condividi</translation> </message> <message> <source>Share on Last.fm</source> <translation>Condividi su Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Condividi su Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Condividi su Facebook</translation> </message> <message> <source>Buy</source> <translation>Acquista</translation> </message> <message> <source>Unlove track</source> <translation>Elimina dai preferiti</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Scrobbling al</translation> </message> <message> <source>percent of the track</source> <translation>percento del brano</translation> </message> <message> <source>Enable scrobbling</source> <translation>Attiva scrobbling</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>...o a 4 minuti (a seconda della condizione che si verifica prima)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>Esegui scrobbling dei podcast</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>Consenti a Last.fm di eseguire il fingerprinting dei brani</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>Lo scrobbling non verrà eseguito nelle directory selezionate.</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>Altri scrobbling su Last.fm</translation> </message> <message> <source>Refreshing...</source> <translation>Aggiornamento in corso...</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Aggiorna scrobbling</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Title</source> <translation>Titolo</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Plays</source> <translation>Ascolti</translation> </message> <message> <source>Last Played</source> <translation>Ultimo ascolto</translation> </message> <message> <source>Loved</source> <translation>Aggiunto ai preferiti</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>Questo brano dura meno di 30 secondi</translation> </message> <message> <source>The artist name is missing</source> <translation>Nome dell'artista mancante</translation> </message> <message> <source>Invalid track title</source> <translation>Titolo del brano non valido</translation> </message> <message> <source>Invalid artist</source> <translation>Artista non valido</translation> </message> <message> <source>There is no timestamp</source> <translation>Timestamp mancante</translation> </message> <message> <source>This track is too far in the future</source> <translation>Questo brano è in un futuro lontano</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>Questo brano è stato ascoltato più di due settimane fa</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>Non hai ancora eseguito scrobbling su Last.fm.</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>Inizia ad ascoltare musica nel tuo lettore o avvia una stazione radio:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>Condividi con amici</translation> </message> <message> <source>With:</source> <translation>Con:</translation> </message> <message> <source>Message (optional):</source> <translation>Messaggio (opzionale):</translation> </message> <message> <source>include in my recent activity</source> <translation>includi nelle mie attività recenti</translation> </message> <message> <source>A track by %1</source> <translation>Un brano di %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Un brano di %1 dall'album %2</translation> </message> <message> <source>Check out %1</source> <translation>Dai un'occhiata a %1</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>In ascolto</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobbling</translation> </message> <message> <source>Profile</source> <translation>Profilo</translation> </message> <message> <source>Friends</source> <translation>Amici</translation> </message> <message> <source>Radio</source> <translation>Radio</translation> </message> <message> <source>Next Section</source> <translation>Sezione successiva</translation> </message> <message> <source>Previous Section</source> <translation>Sezione precedente</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>Impossibile avviare la radio: %1</translation> </message> <message> <source>no results for "%1"</source> <translation>nessun risultato per "%1"</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>Scrobbling disattivato</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>Online</translation> </message> <message> <source>Offline</source> <translation>Offline</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>Aggiungi tag</translation> </message> <message> <source>Choose something to tag:</source> <translation>Scegli a cosa aggiungere i tag:</translation> </message> <message> <source>Track</source> <translation>Brano</translation> </message> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>icon</source> <translation>icona</translation> </message> <message> <source>Add tags:</source> <translation>Aggiungi tag:</translation> </message> <message> <source>A track by %1</source> <translation>Un brano di %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Un brano di %1 dall'album %2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>Digita un tag qui sopra, o scegli tra i seguenti</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>Ordina per popolarità</translation> </message> <message> <source>Sort Alphabetically</source> <translation>Ordina alfabeticamente</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>Apri la pagina Last.fm di questo tag</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>Sei pronto per iniziare! Fai clic su <strong>Fine</strong> e comincia a esplorare.</p><p>La tua cronologia di ascolto è già stata importata e aggiunta al tuo profilo di Last.fm.</p><p>Grazie per aver installato l'app Last.fm per desktop, buon divertimento!</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>Questo è tutto!</translation> </message> <message> <source>Finish</source> <translation>Fine</translation> </message> <message> <source><< Back</source> <translation><< Indietro</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>La freccia rossa sullo schermo indica la posizione dell'app Last.fm per desktop nell'area di notifica.</p><p>Fai clic sull'icona per accedere ai controlli della radio, condividere i brani e aggiungervi tag, modificare le tue preferenze e visitare il tuo profilo di Last.fm.</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>App Last.fm per desktop nella barra dei menu</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>App Last.fm per desktop nell'area di notifica</translation> </message> <message> <source>Continue</source> <translation>Continua</translation> </message> <message> <source><< Back</source> <translation><< Indietro</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>Puoi ottenere maggiori informazioni sulla musica che ascolti, tra cui biografie, statistiche di ascolto, foto, artisti simili e tag aggiunti dagli ascoltatori.</p><p>Dai un'occhiata alla scheda <strong>In ascolto</strong> o fai clic su qualsiasi brano nella scheda <strong>Scrobbling</strong> per saperne di più.</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>Ottieni informazioni sugli artisti che ami</translation> </message> <message> <source>Continue</source> <translation>Continua</translation> </message> <message> <source><< Back</source> <translation><< Indietro</translation> </message> <message> <source>Skip Tour >></source> <translation>Ignora tour >></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>Ascolta una radio personalizzata senza interruzioni</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Usa l'app Last.fm per desktop per ascoltare una radio personalizzata, basata sulla musica che preferisci.</p><p>Ogni riproduzione della radio di Last.fm è unica, dalle stazioni basate su artisti e tag ai nuovi consigli personalizzati in base ai tuoi gusti musicali.</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>Abbonati e ascolta una radio personalizzata senza interruzioni</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Abbonati a Last.fm e usa l'app Last.fm per desktop per ascoltare una radio personalizzata, basata sulla musica che preferisci.</p><p>Ogni riproduzione della radio di Last.fm è unica, dalle stazioni basate su artisti e tag ai nuovi consigli personalizzati in base ai tuoi gusti musicali.</p></translation> </message> <message> <source>Subscribe</source> <translation>Abbonati</translation> </message> <message> <source>Continue</source> <translation>Continua</translation> </message> <message> <source><< Back</source> <translation><< Indietro</translation> </message> <message> <source>Skip Tour >></source> <translation>Ignora tour >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>Il client desktop aggiorna in background il tuo profilo di Last.fm con la musica che ascolti, così puoi ricevere consigli musicali, suggerimenti sui concerti e molto altro. </p><p>Puoi usare l'app Last.fm per desktop anche per ottenere maggiori informazioni sull'artista che stai ascoltando e per riprodurre una radio personalizzata.</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>Benvenuto nell'app Last.fm per desktop!</translation> </message> <message> <source>Continue</source> <translation>Continua</translation> </message> <message> <source><< Back</source> <translation><< Indietro</translation> </message> <message> <source>Skip Tour >></source> <translation>Ignora tour >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>Brano</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Love</source> <translation>Aggiungi ai preferiti</translation> </message> <message> <source>Tag</source> <translation>Aggiungi tag</translation> </message> <message> <source>Share</source> <translation>Condividi</translation> </message> <message> <source>Buy</source> <translation>Acquista</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>Elimina questo scrobbling dal tuo profilo</translation> </message> <message> <source>Play %1 Radio</source> <translation>Ascolta radio di %1</translation> </message> <message> <source>Cue %1 Radio</source> <translation>Metti in coda radio di %1</translation> </message> <message> <source>%1 Radio</source> <translation>Radio di %1</translation> </message> <message> <source>Cached</source> <translation>Nella cache</translation> </message> <message> <source>Error: %1</source> <translation>Errore: %1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Condividi su Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Condividi su Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Condividi su Facebook</translation> </message> <message> <source>Now listening</source> <translation>In ascolto</translation> </message> <message> <source>Downloads</source> <translation>Download</translation> </message> <message> <source>Search on %1</source> <translation>Cerca su %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Acquista su %1 %2</translation> </message> <message> <source>Physical</source> <translation>Supporto fisico</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>Account utente collegati:</translation> </message> <message> <source>Add New User Account</source> <translation>Aggiungi nuovo account</translation> </message> <message> <source>Add User Error</source> <translation>Errore durante l'aggiunta dell'utente</translation> </message> <message> <source>This user has already been added.</source> <translation>Questo utente è già stato aggiunto.</translation> </message> <message> <source>Removing %1</source> <translation>Rimozione di %1</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>Vuoi davvero rimuovere questo utente? Tutte le impostazioni dell'utente andranno perse e dovrai effettuare nuovamente l'autenticazione per eseguire lo scrobbling in futuro.</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>Abbonati</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>Rimuovi</translation> </message> <message> <source>(currently logged in)</source> <translation>(attualmente connesso)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>Account</translation> </message> <message> <source>Show Scrobbler</source> <translation>Mostra Scrobbler</translation> </message> <message> <source>Love</source> <translation>Aggiungi ai preferiti</translation> </message> <message> <source>Play</source> <translation>Ascolta</translation> </message> <message> <source>Skip</source> <translation>Brano successivo</translation> </message> <message> <source>Tag</source> <translation>Aggiungi tag</translation> </message> <message> <source>Share</source> <translation>Condividi</translation> </message> <message> <source>Ban</source> <translation>Escludi</translation> </message> <message> <source>Mute</source> <translation>Disattiva audio</translation> </message> <message> <source>Scrobble iPod...</source> <translation>Scrobbling dei brani sull'iPod...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Visita profilo di Last.fm</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Attiva scrobbling</translation> </message> <message> <source>Quit %1</source> <translation>Chiudi %1</translation> </message> <message> <source>from %1</source> <translation>da %1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>Hai raggiunto il limite massimo di passaggi al brano successivo per questa stazione. Riprova tra %n minuto.</numerusform> <numerusform>Hai raggiunto il limite massimo di passaggi al brano successivo per questa stazione. Riprova tra %n minuti.</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>Hai ancora %n passaggio al brano successivo per questa stazione.</numerusform> <numerusform>Hai ancora %n passaggi al brano successivo per questa stazione.</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>È richiesta l'autenticazione</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>L'account utente <strong>%1</strong> non è più autenticato con Last.fm.</p><p>Fai clic su OK per avviare il processo di configurazione ed effettuare nuovamente l'autenticazione dell'account.</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>Vuoi davvero chiudere %1?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>%1 verrà chiuso. Se continui, non verrà eseguito lo scrobbling dei brani che ascolti.</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>Cambio utente</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 verrà connesso allo Scrobbler e alla radio di Last.fm. Lo scrobbling di tutta la musica verrà eseguito in questo account. Vuoi continuare?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>Devi chiudere le seguenti applicazioni per continuare.</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>Errore di installazione del plug-in</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>Si è verificato un errore durante l'aggiornamento del plug-in.</p><p>Riprova più tardi.</p></translation> </message> <message> <source>Plugin installed!</source> <translation>Plug-in installato!</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>Il plug-in di %1 è stato installato.</p><p>Ora puoi eseguire lo scobbling con %1.</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>Il plug-in di %1 non è stato installato</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>Non hai chiuso %1, quindi il plug-in corrispondente non è stato installato.</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>Chiudi iTunes per aggiornare il plug-in!</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>Il plug-in di iTunes (%2) è diverso da quello fornito con questa versione dell'app (%1).</p><p>Chiudi iTunes per aggiornarlo.</p></translation> </message> <message> <source>not installed</source> <translation>non installato</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>Il plug-in non è stato installato</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>Si è verificato un errore durante la rimozione del plug-in precedente</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>Plug-in di iTunes installato!</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>Il plug-in di iTunes è stato installato.</p><p>Ora puoi eseguire lo scrobbling dai dispositivi.</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>Si è verificato un errore durante la copia del nuovo plug-in</translation> </message> <message> <source>You didn't close iTunes</source> <translation>Non hai chiuso iTunes</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>Tempo errato</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>%n minuto fa</numerusform> <numerusform>%n minuti fa</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>%n ora fa</numerusform> <numerusform>%n ore fa</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>Si è verificato un errore di rete: %1</translation> </message> <message> <source>You have not authorised this application</source> <translation>Non hai autorizzato l'applicazione</translation> </message> <message> <source>Authentication Error</source> <translation>Errore di autenticazione</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>Aggiorna foglio di stile</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>Non visualizzare più questo messaggio</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>Rilevamento automatico</translation> </message> <message> <source>No-proxy</source> <translation>Nessun proxy</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_ja.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="ja"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>バージョン情報</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 (built on Qt %2)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>ウェブブラウザ上の<strong>「アクセスを許可する」</strong>ボタンをクリックし、Last.fm アカウントを Last.fm デスクトップ・アプリに接続してください。</p><p>ブラウザ・ウインドウを閉じたもしくはキャンセルをクリックしたため接続していない場合は、もう一度試してください。<p /></p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>Last.fm への接続をお待ちしています</translation> </message> <message> <source><< Back</source> <translation><< 戻る</translation> </message> <message> <source>Continue</source> <translation>続行</translation> </message> <message> <source>Try Again</source> <translation>再実行</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>ウェブ・ブラウザが開かなかった場合、以下のリンクをアドレスバーにコピー&ペーストしてください。</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>キーボードショートカット:</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Last.fm 表示/非表示</translation> </message> <message> <source>Proxy:</source> <translation>プロキシ:</translation> </message> <message> <source>Enable SSL</source> <translation>SSL を有効にする</translation> </message> <message> <source>Cache Size:</source> <translation>キャッシュサイズ:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>有料メンバー</translation> </message> <message> <source>Moderator</source> <translation>モデレータ</translation> </message> <message> <source>Staff</source> <translation>スタッフ</translation> </message> <message> <source>Alumna</source> <translation>OG</translation> </message> <message> <source>Alumnus</source> <translation>OB</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>ツアー中</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>あなたの音楽テイストに基づいた最高のおすすめ情報を入手するには、お使いのメディアプレイヤーから再生履歴をインポートすることをお勧めします。</p><p>優先するメディアプレイヤーを選択して<strong>インポート開始</strong>をクリックしてください</p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>マイ プラグインはインストールされませんでした</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>後でファイルメニューからインストール可能です</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>再生履歴をインポートしましょう</translation> </message> <message> <source>Start Import</source> <translation>インポート開始</translation> </message> <message> <source><< Back</source> <translation><< 戻る</translation> </message> <message> <source>Skip >></source> <translation>スキップ >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>ご安心ください。マイ ライブラリのサイズにもよりますが、アップロード処理にかかる時間は数分です。</p><p>再生履歴を Last.fm プロフィールに追加している間、Last.fm デスクトップ・アプリのメイン機能をチェックしてみませんか?<strong>続行</strong>をクリックしてサイトツアー開始。</p></translation> </message> <message> <source>Continue</source> <translation>続行</translation> </message> <message> <source><< Back</source> <translation><< 戻る</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>アプリを閉じる</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>デバイス Scrobble 無効 - iTunes プラグイン互換性なし - %1</translation> </message> <message> <source>please update</source> <translation>アップデートしてください</translation> </message> <message> <source>Scrobble iPod</source> <translation>iPod を Scrobble</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>デバイス %1 を audioscrobbler ユーザーアカウントに関連付けますか?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>デバイスをユーザーアカウントに関連付けるのに成功しました。今後はこのデバイスで再生したトラックを Scrobble できます。</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>%1 トラック Scrobble 済。</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>最終同期から Scrobble されたトラックはありません。</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>iPod データベースを開くことができませんでした。</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>iPod データベースにアクセスしようとした際、原因不明のエラーが発生しました。</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>診断</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobble 中</translation> </message> <message> <source>This is an easter egg!</source> <translation>これはイースターエッグ(隠しメッセージ)です!</translation> </message> <message> <source>Artist</source> <translation>アーティスト</translation> </message> <message> <source>Track</source> <translation>トラック</translation> </message> <message> <source>Album</source> <translation>アルバム</translation> </message> <message> <source>Fingerprinting</source> <translation>フィンガープリント中</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>最近フィンガープリントされたトラック</translation> </message> <message> <source>iPod Scrobbling</source> <translation>iPod Scrobble 中</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>iTunes で iPod を自動管理</translation> </message> <message> <source>I manually manage my iPod</source> <translation>iPod を手動管理</translation> </message> <message> <source>Scrobble iPod</source> <translation>iPod を Scrobble</translation> </message> <message> <source>Logs</source> <translation>ログ</translation> </message> <message> <source>&Close</source> <translation>閉じる(&C)</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n ローカルキャッシュ・トラック</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>Last.fm デスクトップ・アプリ</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>ありがとう、<strong>%1</strong> さん。現在アカウントは接続されています!</translation> </message> <message> <source>Importing...</source> <translation>インポート中…</translation> </message> <message> <source>Import complete!</source> <translation>インポート完了!</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Last.fm で友だち検索</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>まだ Last.fm 上に友だちはいません。</h3><p>友だち検索機能で素早く簡単に Facebook の友だちを探して Last.fm 上でメールしてみましょう。<p /></p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>ユーザー名もしくは本名で友だち検索</translation> </message> <message> <source>Refresh Friends</source> <translation>友だちを再読込</translation> </message> <message> <source>Refreshing...</source> <translation>再読込中…</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>%1 さんのライブラリ・ラジオ</translation> </message> <message> <source>Male</source> <translation>男性</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>%1 からただ今 Scrobble 中</translation> </message> <message> <source>Female</source> <translation>女性</translation> </message> <message> <source>Scrobbling now</source> <translation>ただ今 Scrobble 中</translation> </message> <message> <source>Neuter</source> <translation>中性</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>友だちを検索</translation> </message> <message> <source>Browse Friends</source> <translation>友だちをブラウズ</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>言語:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>メニューバーにアプリケーションアイコンを表示</translation> </message> <message> <source>Launch application with media players</source> <translation>メディアプレイヤーでアプリケーションを起動</translation> </message> <message> <source>Show dock icon</source> <translation>Dock アイコン表示</translation> </message> <message> <source>Show desktop notifications</source> <translation>デスクトップ通知を表示</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>クラッシュレポートを Last.fm に送信</translation> </message> <message> <source>Check for updates automatically</source> <translation>自動的にアップデートをチェック</translation> </message> <message> <source>Enable media keys</source> <translation>メディアキーを有効にする</translation> </message> <message> <source>System Language</source> <translation>システム言語</translation> </message> <message> <source>Restart now?</source> <translation>今すぐ再起動しますか?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>変更を有効にするにはアプリケーションの再起動が必要です。今すぐ再起動しますか?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>ベータ版にアップデート - 注:勇敢な人限定!</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>iPod データベースを開くことができませんでした。</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>%1 のような iOS Scrobble アプリを使用すると、二重 Scrobble の可能性があります。Scrobble アプリは一つだけ有効にしてご利用ください。</p><p>iTunes Match では再生回数は同期されますが、複数のデバイスにわたる最新の再生回数は同期されません。このため Scrobble が重複し、不正確な回数につながる場合があります。さしあたり iTunes Match ユーザーの皆さんには、デスクトップ・デバイス上での Scrobble を無効にし、%1 のような iOS Scrobble アプリで iPhones/iPods を Scrobble することをおすすめします。</p></translation> </message> <message> <source>Setting not changed</source> <translation>設定は変更されていません</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>この設定変更のために iTunes を閉じませんでした</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>デバイスでの Scrobble を有効にする</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>デバイス Scrobble を確認する</translation> </message> <message> <source>Please note</source> <translation>注意:</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>ライセンス</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>終わりましたか?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>このアプリ承認後、OK をクリック</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Last.fm はまずあなたの許可が必要です!</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>このアプリケーション使用には、Last.fm マイ プロフィールに接続する許可が必要です。OK をクリックして Last.fm ウェブサイトにアクセス後、実行してください。</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>すでに Last.fm ユーザーですか?アカウントを Last.fm デスクトップ・アプリに接続すると、再生した音楽でマイ プロフィールが更新されます。</p><p>アカウントをお持ちでな場合、今すぐ無料で登録できます。</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Last.fm アカウントと接続してスタート</translation> </message> <message> <source>Connect Your Account</source> <translation>マイ アカウントを接続</translation> </message> <message> <source>Sign up</source> <translation>登録</translation> </message> <message> <source>Proxy?</source> <translation>プロキシですか?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>メディア・プレイヤー プラグインのアップデートがあります。今すぐインストールしますか?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>プラグイン インストールエラー</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>プラグイン更新時にエラーが発生しました。</p><p>後でもう一度試してください。</p></numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>プラグインインストール完了!</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>プラグインのインストール完了です。</p><p>お使いのメディア・プレイヤーで Scrobble する準備ができました。</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>マイ プラグインはインストールされませんでした</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>後でファイルメニューからインストール可能です</translation> </message> <message> <source>File</source> <translation>ファイル</translation> </message> <message> <source>Install plugins</source> <translation>プラグインのインストール</translation> </message> <message> <source>&Quit</source> <translation>中止(&Q)</translation> </message> <message> <source>View</source> <translation>表示</translation> </message> <message> <source>My Last.fm Profile</source> <translation>Last.fm マイ プロフィール</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobble</translation> </message> <message> <source>Refresh</source> <translation>更新</translation> </message> <message> <source>Controls</source> <translation>コントロール</translation> </message> <message> <source>Account</source> <translation>アカウント</translation> </message> <message> <source>Tools</source> <translation>ツール</translation> </message> <message> <source>Check for Updates</source> <translation>更新のチェック</translation> </message> <message> <source>Options</source> <translation>オプション</translation> </message> <message> <source>Window</source> <translation>ウインドウ</translation> </message> <message> <source>Minimize</source> <translation>最小化</translation> </message> <message> <source>Zoom</source> <translation>拡大</translation> </message> <message> <source>Bring All to Front</source> <translation>すべて前面に移動</translation> </message> <message> <source>Help</source> <translation>ヘルプ</translation> </message> <message> <source>About</source> <translation>バージョン情報</translation> </message> <message> <source>FAQ</source> <translation>FAQ</translation> </message> <message> <source>Forums</source> <translation>フォーラム</translation> </message> <message> <source>Tour</source> <translation>サイトツアー</translation> </message> <message> <source>Show Licenses</source> <translation>ライセンス表示</translation> </message> <message> <source>Diagnostics</source> <translation>診断</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>デバイスから <a href="tracks">%n 回</a> Scrobble</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Scrobble に戻る</translation> </message> <message> <source>Popular tags:</source> <translation>人気のタグ:</translation> </message> <message> <source>Your tags:</source> <translation>マイ タグ:</translation> </message> <message> <source>Similar Artists</source> <translation>似てるアーティスト</translation> </message> <message> <source>by %1</source> <translation>by %1</translation> </message> <message> <source>from %1</source> <translation>from %1</translation> </message> <message> <source>Play %1 Radio</source> <translation>%1 ラジオを再生</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>再生回数</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>マイ ライブラリでの再生回数</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>リスナー</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>With %1 など</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>With %1, %2 など</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>編集日 %1 | %2 編集</translation> </message> <message> <source>Downloads</source> <translation>ダウンロード</translation> </message> <message> <source>Search on %1</source> <translation>%1 で検索</translation> </message> <message> <source>Buy on %1 %2</source> <translation>%1 %2 で購入</translation> </message> <message> <source>Physical</source> <translation>CD</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>%1 を再生しているのでオススメ。</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>%1, %2 を再生しているのでオススメ。</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>%1, %2, %3 を再生しているのオススメ。</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>%1, %2, %3, %4 を再生しているのオススメ。</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>%1, %2, %3, %4, %5 を再生しているのでオススメ。</translation> </message> <message> <source>From %1's library.</source> <translation>%1 さんのライブラリから。</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>%1 & %2 さんのライブラリから。</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 回</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>%1, %2, & %3 さんのライブラリから。</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>%1 %2 と %3 %4 を再生しました。</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>%1, %2, %3, & %4 さんのライブラリから。</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>%1 %2 は再生しましたが、このトラックは再生していません。</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>%1, %2, %3, %4, & %5 さんのライブラリから。</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>%1 の初回再生です。</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>こんにちは!</translation> </message> <message> <source>Start a radio station</source> <translation>ラジオステーションを開始</translation> </message> <message> <source>Open iTunes</source> <translation>iTunes を開く</translation> </message> <message> <source>Open Music</source> <translation>Music を開く</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Windows Media Player を開く</translation> </message> <message> <source>Open Winamp</source> <translation>Winamp を開く</translation> </message> <message> <source>Open Foobar</source> <translation>Foobar を開く</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>お使いの音楽プレイヤーから Scrobble</h2><p>メディアプレイヤーで音楽再生をスタートしましょう。「ただ今再生中」タブで再生トラックの詳細情報をチェックできます。</p></translation> </message> <message> <source>Hello, %1!</source> <translation>こんにちは %1 さん!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>ラジオステーション</translation> </message> <message> <source>Play %1</source> <translation>%1 を再生</translation> </message> <message> <source>Multi-Library Radio</source> <translation>マルチライブラリ・ラジオ</translation> </message> <message> <source>Cue %1</source> <translation>%1 を続けて再生</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>%1 & %2 ライブラリ・ラジオを再生</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>%1 & %2 ライブラリ・ラジオを続けて再生</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>Love</translation> </message> <message> <source>Ban</source> <translation>禁止</translation> </message> <message> <source>Play</source> <translation>再生</translation> </message> <message> <source>Skip</source> <translation>スキップ</translation> </message> <message> <source>Pause</source> <translation>一時停止</translation> </message> <message> <source>Resume</source> <translation>再開</translation> </message> <message> <source>Unlove</source> <translation>Love じゃない</translation> </message> <message> <source>Tuning</source> <translation>チューニング</translation> </message> <message> <source>A Radio Station</source> <translation>ラジオステーション</translation> </message> <message> <source>Listening to</source> <translation>再生:</translation> </message> <message> <source>Scrobbling from</source> <translation>Scrobble 中:</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 回再生</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>Last.fm はメディアプレイヤーをインポートしました。 OK をクリックして次に進んでください。</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Last.fm ライブラリ・インポート</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>本当にインポートをキャンセルしますか?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Last.fm はメディアプレイヤーで再生されたトラックを検出できませんでした。 OK をクリックして次に進んでください。</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm は現在ご利用のメディアライブラリをインポートしています...</translation> </message> <message> <source>Where is Winamp?</source> <translation>Winamp はどこ?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Windows Media Player はどこ?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>メディアライブラリ・インポート完了</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>Last.fm は再生履歴をサーバーに送信しました。 新しいトラック情報は数分でプロフィールページに更新されます。</translation> </message> <message> <source>Library Import Failed</source> <translation>ライブラリ・インポート失敗</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Sorry! Last.fm は再生履歴をインポートできませんでした。Scrobble 済みのトラック数が多すぎるようです。再生履歴は、新しいプロフィールページにのみインポート可能です。</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>プラグインをインストールするには、お使いの OS で表示される手順に従ってください。</p><p>コンピュータにプラグインがインストールされたら、<strong>続行</strong>をクリックしてください。</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>現在プラグインをインストール中です。</translation> </message> <message> <source>Continue</source> <translation>続行</translation> </message> <message> <source><< Back</source> <translation><< 戻る</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>マイ プラグインはインストールされませんでした</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>後でファイルメニューからインストール可能です</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>再生する音楽の Scrobble をお使いのメディアプレイヤーで可能にするには、Last.fm の特別なプラグインが必要です。</p><p>Scrobble に利用したいメディアプレイヤーを選択し、<strong>プラグインをインストール</strong>をクリックしてください</p></translation> </message> <message> <source>(newer version)</source> <translation>(新バージョン)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(プラグインはインストール済み。クリックして再インストール)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>次に Last.fm プラグインをインストールし、再生音楽の Scrobble を可能にします。</translation> </message> <message> <source>Install Plugins</source> <translation>プラグインをインストール</translation> </message> <message> <source>Continue</source> <translation>続行</translation> </message> <message> <source><< Back</source> <translation><< 戻る</translation> </message> <message> <source>Skip >></source> <translation>スキップ >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>全般</translation> </message> <message> <source>Accounts</source> <translation>アカウント</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobble 中</translation> </message> <message> <source>Devices</source> <translation>デバイス</translation> </message> <message> <source>Advanced</source> <translation>拡張</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>%1 ラジオ</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 回再生</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>今週の Top アーティスト</translation> </message> <message> <source>Top Artists Overall</source> <translation>総合 Top アーティスト</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>Scrobble</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>Love トラック</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 アーティスト</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 トラック</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>マイ ライブラリに %1 があります。1日平均 %2 再生。</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>%1 から Scrobble </numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>プロキシ設定</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>ホスト:</translation> </message> <message> <source>Username:</source> <translation>ユーザー名:</translation> </message> <message> <source>Port:</source> <translation>ポート:</translation> </message> <message> <source>Password:</source> <translation>パスワード:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>不明なメディアプレイヤー</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>iPod はどこに取り付けましたか?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>アーティスト名もしくはタグ名を入力して再生ボタンをプッシュ</translation> </message> <message> <source>Play</source> <translation>再生</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>%1, %2, %3 もしくは %4 を試してみませんか?</translation> </message> <message> <source>Play next</source> <translation>次を再生</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>ラジオステーション</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>ラジオ再生には有料メンバー登録が必要です。</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>最終ステーション</translation> </message> <message> <source>A Radio Station</source> <translation>ラジオステーション</translation> </message> <message> <source>Personal Stations</source> <translation>パーソナル・ステーション</translation> </message> <message> <source>My Library Radio</source> <translation>マイ ライブラリ・ラジオ</translation> </message> <message> <source>Music you know and love</source> <translation>知ってる & Love な音楽</translation> </message> <message> <source>My Mix Radio</source> <translation>マイ Mix ラジオ</translation> </message> <message> <source>Your library plus new music</source> <translation>マイ ライブラリ + 新しい音楽</translation> </message> <message> <source>My Recommended Radio</source> <translation>おすすめ マイ ラジオ</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>有料メンバー登録してラジオを再生</translation> </message> <message> <source>New music from Last.fm</source> <translation>Last.fm から新しい音楽</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>このアプリでラジオ再生するには Last.fm 有料メンバー登録が必要です。今すぐメンバー登録してラジオ再生をスタートしましょう。その他素敵な特典もご利用ください。</translation> </message> <message> <source>Network Stations</source> <translation>ネットワーク・ステーション</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Last.fm に有料メンバー登録</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>www.last.fm で無料再生</translation> </message> <message> <source>My Friends' Radio</source> <translation>マイ 友だちラジオ</translation> </message> <message> <source>Music your friends like</source> <translation>友だちの好きな音楽</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>マイ ご近所さんラジオ</translation> </message> <message> <source>Music from listeners like you</source> <translation>あなたに似たリスナーからの音楽</translation> </message> <message> <source>Recent Stations</source> <translation>最近のステーション</translation> </message> <message> <source>Now Playing</source> <translation>再生中</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>有料メンバー登録してラジオ再生 月額わずか %1</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>デバイス Scrobble</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>これらのトラックを再生したようです。Scrobble しますか?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>デバイスを自動的に Scrobble</translation> </message> <message> <source>Toggle selection</source> <translation>選択を切り替える</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>デバイスから %n 回 Scrobble</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>Love トラック</translation> </message> <message> <source>Add tags</source> <translation>タグを追加</translation> </message> <message> <source>Love</source> <translation>Love</translation> </message> <message> <source>Tag</source> <translation>タグ</translation> </message> <message> <source>Share</source> <translation>シェア</translation> </message> <message> <source>Share on Last.fm</source> <translation>Last.fm でシェア</translation> </message> <message> <source>Share on Twitter</source> <translation>Twitter でシェア</translation> </message> <message> <source>Share on Facebook</source> <translation>Facebook でシェア</translation> </message> <message> <source>Buy</source> <translation>購入</translation> </message> <message> <source>Unlove track</source> <translation>Love じゃないトラック</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Scrobble メディア</translation> </message> <message> <source>percent of the track</source> <translation>トラック%</translation> </message> <message> <source>Enable scrobbling</source> <translation>Scrobble を有効</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>...または4分で(いずれか早い方)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>ポッドキャストを Scrobble</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>マイ トラックの Last.fm フィンガープリントを許可</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>選択したディレクトリは Scrobble できません</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>Last.fm でもっと Scrobble</translation> </message> <message> <source>Refreshing...</source> <translation>再読込中…</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Scrobble をリフレッシュ</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>アーティスト</translation> </message> <message> <source>Title</source> <translation>タイトル</translation> </message> <message> <source>Album</source> <translation>アルバム</translation> </message> <message> <source>Plays</source> <translation>再生</translation> </message> <message> <source>Last Played</source> <translation>最終再生</translation> </message> <message> <source>Loved</source> <translation>Love</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>このトラックは30秒以内です</translation> </message> <message> <source>The artist name is missing</source> <translation>アーティスト名は不明です</translation> </message> <message> <source>Invalid track title</source> <translation>無効なトラック名</translation> </message> <message> <source>Invalid artist</source> <translation>無効なアーティスト</translation> </message> <message> <source>There is no timestamp</source> <translation>タイムスタンプがありません</translation> </message> <message> <source>This track is too far in the future</source> <translation>このトラックはずっと先のものです</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>このトラックは2週間以上前に再生されました</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>まだ Last.fm で音楽を Scrobble していません。</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>お使いのメディアプレイヤーで音楽再生を開始するかラジオステーションをスタート:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>友だちとシェア</translation> </message> <message> <source>With:</source> <translation>シェア with:</translation> </message> <message> <source>Message (optional):</source> <translation>メッセージ(オプション)</translation> </message> <message> <source>include in my recent activity</source> <translation>最近のマイ アクティビティに組み込む</translation> </message> <message> <source>A track by %1</source> <translation>トラック by %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>トラック by %1, %2</translation> </message> <message> <source>Check out %1</source> <translation>%1 をチェック</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>ただ今再生中</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobble</translation> </message> <message> <source>Profile</source> <translation>プロフィール</translation> </message> <message> <source>Friends</source> <translation>友だち</translation> </message> <message> <source>Radio</source> <translation>ラジオ</translation> </message> <message> <source>Next Section</source> <translation>次項</translation> </message> <message> <source>Previous Section</source> <translation>前項</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>ラジオを開始できませんでした:%1</translation> </message> <message> <source>no results for "%1"</source> <translation>"%1" の検索結果はありません</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>Scrobble オフ</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>オンライン</translation> </message> <message> <source>Offline</source> <translation>オフライン</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>タグ</translation> </message> <message> <source>Choose something to tag:</source> <translation>何かを選択してタグ付け:</translation> </message> <message> <source>Track</source> <translation>トラック</translation> </message> <message> <source>Artist</source> <translation>アーティスト</translation> </message> <message> <source>Album</source> <translation>アルバム</translation> </message> <message> <source>icon</source> <translation>アイコン</translation> </message> <message> <source>Add tags:</source> <translation>タグを追加:</translation> </message> <message> <source>A track by %1</source> <translation>トラック by %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>トラック by %1, %2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>上にタグを入力するか以下から選択</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>人気順にソート</translation> </message> <message> <source>Sort Alphabetically</source> <translation>アルファベット順にソート</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>このタグの Last.fm ページを開く</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>スタート準備ができました!<strong>終了</strong>をクリックして探索を開始してください。</p><p>再生履歴もインポート完了、Last.fm マイ プロフィールに追加しました。</p><p>Last.fm デスクトップ・アプリをインストールしていただきありがとうございます。Last.fm を楽しんでご利用ください!</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>これで終了、いよいよスタートです!</translation> </message> <message> <source>Finish</source> <translation>終了</translation> </message> <message> <source><< Back</source> <translation><< 戻る</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>スクリーン上の赤い矢印はシステムトレイでの Last.fm デスクトップ・アプリ位置を指しています。</p><p>アイコンをクリックすると、ラジオ再生コントロールへの素早いアクセス、トラックのシェア&タグ付け、設定の編集や Last.fm マイ プロフィールへのアクセスができます。</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>メニューバーに Last.fm デスクトップ・アプリ</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>システムトレイに Last.fm デスクトップ・アプリ</translation> </message> <message> <source>Continue</source> <translation>続行</translation> </message> <message> <source><< Back</source> <translation><< 戻る</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>バイオ、再生統計データ、写真や似てるアーティスト、さらにリスナーが分類に利用するタグなど、再生中の音楽について詳細情報を見つけてください。</p><p><strong>ただ今再生中</strong>タブをチェックするか <strong>Scrobble</strong> タブ上のトラックをクリックするだけで詳細をご覧いただけます。</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>大好きなアーティスト情報の発見</translation> </message> <message> <source>Continue</source> <translation>続行</translation> </message> <message> <source><< Back</source> <translation><< 戻る</translation> </message> <message> <source>Skip Tour >></source> <translation>サイトツアーをスキップ>></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>ノンストップラジオ、カスタムラジオを再生</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>聴きたい音楽に基づいたカスタムラジオを再生するには、Last.fm デスクトップ・アプリをお使いください。</p><p>アーティストやタグベースのステーションからあなたの音楽テイストにピッタリの最新おすすめ情報まで、Last.fm のステーションはすべて再生ごとに異なる音楽をお届けします。</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>有料メンバー登録してノンストップラジオ、カスタムラジオを再生</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>聴きたい音楽に基づいたカスタムラジオを再生するには、Last.fm に有料メンバー登録して Last.fm デスクトップ・アプリをお使いください。</p><p>アーティストやタグベースのステーションからあなたの音楽テイストにピッタリの最新おすすめ情報まで、Last.fm ステーションはすべて再生ごとに異なる音楽をお届けします。</p></translation> </message> <message> <source>Subscribe</source> <translation>有料メンバー登録</translation> </message> <message> <source>Continue</source> <translation>続行</translation> </message> <message> <source><< Back</source> <translation><< 戻る</translation> </message> <message> <source>Skip Tour >></source> <translation>サイトツアーをスキップ>></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>デスクトップ・クライアントは、再生している音楽で Last.fm マイ プロフィールをひそかにアップデートしながら、バックグラウンドで動作し、おすすめ音楽やライブの内部情報などの入手に活用できます。</p><p>また Last.fm デスクトップ・アプリも再生中のアーティスト詳細情報チェックやカスタムラジオ再生にご利用いただけます。</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>ようこそ Last.fm デスクトップ・アプリへ!</translation> </message> <message> <source>Continue</source> <translation>続行</translation> </message> <message> <source><< Back</source> <translation><< 戻る</translation> </message> <message> <source>Skip Tour >></source> <translation>サイトツアーをスキップ>></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>トラック</translation> </message> <message> <source>Album</source> <translation>アルバム</translation> </message> <message> <source>Artist</source> <translation>アーティスト</translation> </message> <message> <source>Love</source> <translation>Love</translation> </message> <message> <source>Tag</source> <translation>タグ</translation> </message> <message> <source>Share</source> <translation>共有</translation> </message> <message> <source>Buy</source> <translation>購入</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>この Scrobble をマイ プロフィールから削除する</translation> </message> <message> <source>Play %1 Radio</source> <translation>%1 ラジオを再生</translation> </message> <message> <source>Cue %1 Radio</source> <translation>%1 ラジオを続けて再生</translation> </message> <message> <source>%1 Radio</source> <translation>%1 ラジオ</translation> </message> <message> <source>Cached</source> <translation>キャッシュ済み</translation> </message> <message> <source>Error: %1</source> <translation>エラー:%1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Last.fm でシェア</translation> </message> <message> <source>Share on Twitter</source> <translation>Twitter でシェア</translation> </message> <message> <source>Share on Facebook</source> <translation>Facebook でシェア</translation> </message> <message> <source>Now listening</source> <translation>ただ今再生中</translation> </message> <message> <source>Downloads</source> <translation>ダウンロード</translation> </message> <message> <source>Search on %1</source> <translation>%1 で検索</translation> </message> <message> <source>Buy on %1 %2</source> <translation>%1 %2 で購入</translation> </message> <message> <source>Physical</source> <translation>CD</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>ユーザーアカウントに接続済み</translation> </message> <message> <source>Add New User Account</source> <translation>新しいユーザーアカウントを追加</translation> </message> <message> <source>Add User Error</source> <translation>ユーザーエラーを追加</translation> </message> <message> <source>This user has already been added.</source> <translation>このユーザーはすでに追加済みです。</translation> </message> <message> <source>Removing %1</source> <translation>%1 削除中</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>このユーザーを本当に削除しますか?ユーザー設定はすべて失われ、今後 Scrobble するには再度認証が必要になります。</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>有料メンバー登録</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>削除</translation> </message> <message> <source>(currently logged in)</source> <translation>(現在ログイン中)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>アカウント</translation> </message> <message> <source>Show Scrobbler</source> <translation>Scrobbler 表示</translation> </message> <message> <source>Love</source> <translation>Love</translation> </message> <message> <source>Play</source> <translation>再生</translation> </message> <message> <source>Skip</source> <translation>スキップ</translation> </message> <message> <source>Tag</source> <translation>タグ</translation> </message> <message> <source>Share</source> <translation>シェア</translation> </message> <message> <source>Ban</source> <translation>禁止</translation> </message> <message> <source>Mute</source> <translation>ミュート</translation> </message> <message> <source>Scrobble iPod...</source> <translation>iPod を Scrobble...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Last.fm プロフィールにアクセス</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Scrobble を有効</translation> </message> <message> <source>Quit %1</source> <translation>%1 を終了</translation> </message> <message> <source>from %1</source> <translation>from %1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>このステーションのスキップ制限に達しました。%n 分後にもう一度スキップしてください。</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>このステーション上でのスキップはあと %n 回です。</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>認証が必要です</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>ユーザーアカウント <strong>%1</strong> は Last.fm ではもはや認証されません。</p><p>OK をクリックしして設定手順を開始し、このアカウントを再度認証してください。</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>本当に %1 を終了しますか?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>%1 は終了しようとしています。続行すると再生したトラックは Scrobble されません。</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>ユーザー変更</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 で Scrobbler および Last.fm ラジオにログインします。今後すべての音楽はこのアカウントに Scrobble されます。続行しますか?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>続行するには以下のアプリを閉じてください。</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>プラグイン インストール・エラー</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>プラグイン更新中にエラーが発生しました。</p><p>後でもう一度試してください。</p></translation> </message> <message> <source>Plugin installed!</source> <translation>プラグイン インストール完了!</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>%1 プラグインのインストール完了です。</p><p>%1 で Scrobble する準備ができました。</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>%1 プラグインはインストールされていません。</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>%1 を閉じなかったためプラグインはインストールされませんでした。</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>プラグイン更新には iTunes を閉じてください!</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>お使いの iTunes プラグイン (%2) は当該アプリ (%1) の本バージョンに対応するものと異なります。.</p><p>iTunes を閉じて今すぐアップデートしてください。</p></translation> </message> <message> <source>not installed</source> <translation>インストールされていません</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>プラグインはインストールされていません</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>古いプラグインの削除中にエラーが発生しました</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>iTunes プラグイン インストール完了!</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>iTunes プラグインのインストール完了です。</p><p>お使いのメディア・プレイヤーで Scrobble する準備ができました。</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>新しいプラグインを所定の位置にコピー中、エラーが発生しました</translation> </message> <message> <source>You didn't close iTunes</source> <translation>iTunes を閉じませんでした</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>時間表示エラー</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>%n 分前</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>%n 時間前</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>ネットワークエラー発生:%1</translation> </message> <message> <source>You have not authorised this application</source> <translation>このアプリケーションは許可されていません</translation> </message> <message> <source>Authentication Error</source> <translation>認証エラー</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>スタイルシートを更新</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>次回以降確認しない</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>自動検出</translation> </message> <message> <source>No-proxy</source> <translation>プロキシなし</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_pl.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="pl"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>Informacje</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 (utworzone za pomocą Qt %2)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>Kliknij przycisk <strong>Tak, pozwól na dostęp</strong> w swojej przeglądarce, aby połączyć swoje konto Last.fm z aplikacją Last.fm.</p><p>Jeśli ich nie połączyłeś, ponieważ zamknąłeś okno przeglądarki lub kliknąłeś anuluj, spróbuj ponownie.<p /></p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>Czekamy, aż połączysz się z Last.fm</translation> </message> <message> <source><< Back</source> <translation><< Wstecz</translation> </message> <message> <source>Continue</source> <translation>Kontynuuj</translation> </message> <message> <source>Try Again</source> <translation>Spróbuj ponownie</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>Jeśli twoja przeglądarka nie otwarła się, skopiuj i wklej poniższy link do paska adresu.</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>Skróty klawiszowe</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Pokaż/Ukryj Last.fm</translation> </message> <message> <source>Proxy:</source> <translation>Proksy:</translation> </message> <message> <source>Enable SSL</source> <translation>Włącz SSL</translation> </message> <message> <source>Cache Size:</source> <translation>Rozmiar cache:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>Subskrybent</translation> </message> <message> <source>Moderator</source> <translation>Moderator</translation> </message> <message> <source>Staff</source> <translation>Pracownicy</translation> </message> <message> <source>Alumna</source> <translation>Alumna</translation> </message> <message> <source>Alumnus</source> <translation>Alumnus</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>W trasie</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>Aby uzyskać najlepsze, możliwe propozycje w oparciu o swój muzyczny gust zalecamy importowanie swojej historii odsłuchanych utworów z odtwarzacza multimediów.</p><p>Wybierz preferowany odtwarzacz i kliknij <strong>Rozpocznij importowanie</strong></p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Twoje wtyczki nie zostały zainstalowane</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Możesz zainstalować je później przez menu Plik</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>Teraz importujmy twoją historię odsłuchanych utworów</translation> </message> <message> <source>Start Import</source> <translation>Rozpocznij importowanie</translation> </message> <message> <source><< Back</source> <translation><< Wróć</translation> </message> <message> <source>Skip >></source> <translation>Pomiń >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>Nie martw się, proces wysyłania danych nie powinien trwać dłużej niż kilka minut, w zależności od wielkości twojej biblioteki muzycznej.</p><p>W czasie, gdy twoja historia będzie przetwarzana i porządkowana w profilu Last.fm, może chciałbyś odbyć krótką wycieczkę po aplikacji Last.fm. Kliknij <strong>Kontynuuj</strong>, aby rozpocząć zwiedzanie.</p></translation> </message> <message> <source>Continue</source> <translation>Dalej</translation> </message> <message> <source><< Back</source> <translation><< Wróć</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>Zamknij aplikację</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>Włączone scrobblowanie urządzenia - niekompatybilna wtyczka iTunes - %1</translation> </message> <message> <source>please update</source> <translation>zaktualizuj</translation> </message> <message> <source>Scrobble iPod</source> <translation>Scrobbluj iPoda</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>Czy chcesz powiązać urządzenie %1 ze swoim kontem użytkownika audioscrobblera?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>Urządzenie zostało pomyślne połączone z twoim kontem użytkownika. Od teraz możesz scrobblować utwory, których słuchasz na tym urządzeniu.</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>%1 utworów przescrobblowanych.</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>Brak utworów do przescrobblowania od ostatniej synchronizacji.</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>Nie udało się otworzyć bazy danych iPoda.</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>Wystąpił nieznany błąd w czasie próby uzyskania dostępu do bazy danych iPoda.</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>Diagnostyka</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobblowanie</translation> </message> <message> <source>This is an easter egg!</source> <translation>To ukryta niespodzianka!</translation> </message> <message> <source>Artist</source> <translation>Wykonawca</translation> </message> <message> <source>Track</source> <translation>Utwór</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Fingerprinting</source> <translation>Fingerprinting</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>Ostatnio fingerprintowane utwory.</translation> </message> <message> <source>iPod Scrobbling</source> <translation>Scrobblowanie iPoda</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>iTunes automatycznie zarządza moim iPodem</translation> </message> <message> <source>I manually manage my iPod</source> <translation>Zarządzam ręcznie moim iPodem</translation> </message> <message> <source>Scrobble iPod</source> <translation>Scrobbluj iPoda</translation> </message> <message> <source>Logs</source> <translation>Logi</translation> </message> <message> <source>&Close</source> <translation>&Zamknij</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n lokalnie zbuforowany utwór</numerusform> <numerusform>%n lokalnie zbuforowane utwory</numerusform> <numerusform>%n lokalnie zbuforowanych utworów</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>Aplikacja Last.fm</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>Dziękujemy <strong>%1</strong>, twoje konto jest teraz podłączone!</translation> </message> <message> <source>Importing...</source> <translation>Importowanie...</translation> </message> <message> <source>Import complete!</source> <translation>Import zakończony!</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Znajdź znajomych w Last.fm</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>Nie masz jeszcze żadnych znajomych w Last.fm.</h3><p>Znajdź swoich znajomych z Facebooka oraz z listy kontaktów mailowych w Last.fm wykorzystując wyszukiwarkę znajomych.<p /></p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>Szukaj znajomego podając nazwę użytkownika lub nazwisko</translation> </message> <message> <source>Refresh Friends</source> <translation>Odśwież znajomych</translation> </message> <message> <source>Refreshing...</source> <translation>Odświeżanie...</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>Radio biblioteki %1</translation> </message> <message> <source>Male</source> <translation>Mężczyzna</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>Teraz scrobblowane z %1</translation> </message> <message> <source>Female</source> <translation>Kobieta</translation> </message> <message> <source>Scrobbling now</source> <translation>Teraz scrobblowane</translation> </message> <message> <source>Neuter</source> <translation>Nieokreślona</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>Wyszukaj swoich znajomych</translation> </message> <message> <source>Browse Friends</source> <translation>Przeglądaj znajomych</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>Język:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>Pokaż ikonę aplikacji w pasku</translation> </message> <message> <source>Launch application with media players</source> <translation>Uruchom aplikację wraz z odtwarzaczem multimediów</translation> </message> <message> <source>Show dock icon</source> <translation>Pokaż ikonę dokowania</translation> </message> <message> <source>Show desktop notifications</source> <translation>Pokaż powiadomienia na pulpicie</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>Wyślij raport o błędzie do Last.fm</translation> </message> <message> <source>Check for updates automatically</source> <translation>Sprawdź automatycznie dostępność aktualizacji</translation> </message> <message> <source>Enable media keys</source> <translation>Aktywuj klucz dla mediów</translation> </message> <message> <source>System Language</source> <translation>Język systemu</translation> </message> <message> <source>Restart now?</source> <translation>Uruchomić ponownie teraz?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>Wymagane jest ponowne uruchomienia aplikacji, w celu wprowadzenia zmian. Czy chcesz uruchomić ponownie teraz?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>Aktualizuj do wersji Beta - Ostrzeżenie: tylko dla odważnych!</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>Nie udało się otworzyć bazy danych iPoda.</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>Korzystanie z aplikacji scrobblującej dla iOS, na przykład %1, może powodować powstawanie podwójnych scrobbli. Włącz scrobblowanie tylko w jednej z nich. </p><p>iTunes Match synchronizuje ilość odtworzeń, ale nie ilość odsłuchań pomiędzy wieloma urządzeniami. Powoduje to powstawanie podwójnych scrobbli o nieprawidłowych datach. Na chwilę obecną, zalecamy użytkownikom iTunes Match wyłączenie scrobblowania na urządzeniach stacjonarnych, a na iPhonach/iPodach scrobblowanie za pomocą aplikacji dla iOS, na przykład %1.</p></translation> </message> <message> <source>Setting not changed</source> <translation>Nie zmieniono ustawienia</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>Nie zamknięto iTunes, aby zmienić to ustawienia</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>Włącz scrobblowanie urządzenia</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>Potwierdź scrobble urządzenia</translation> </message> <message> <source>Please note</source> <translation>Pamiętaj</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>Licencje</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>To już wszystko?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>Po zatwierdzeniu aplikacji, kliknij OK.</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Last.fm wymaga najpierw twojej zgody!</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>Aplikacja wymaga twojej zgody na podłączenie do twojego konta profilu Last.fm. Kliknij OK, aby przejść na stronę Last.fm i wyrazić zgodę.</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>Jesteś już użytkownikiem Last.fm? Powiąż swoje konto z aplikacją Last.fm, a twój profil będzie automatycznie aktualizowany o muzykę, której słuchasz.</p><p>Jeśli nie masz jeszcze konta, możesz je utworzyć teraz za darmo.</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Zacznij od powiązania twojego konta Last.fm</translation> </message> <message> <source>Connect Your Account</source> <translation>Powiąż swoje konto</translation> </message> <message> <source>Sign up</source> <translation>Zarejestruj się</translation> </message> <message> <source>Proxy?</source> <translation>Proksy?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>Pojawiły się aktualizacje wtyczek do twoich odtwarzaczy. Czy chcesz je teraz zainstalować?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>Błąd instalacji wtyczki</numerusform> <numerusform>Błędy instalacji wtyczki</numerusform> <numerusform>Błędy instalacji wtyczki</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>Wystąpił błąd w czasie aktualizacji wtyczki.</p><p>Spróbuj ponownie później.</p></numerusform> <numerusform><p>Wystąpił błąd w czasie aktualizacji wtyczek.</p><p>Spróbuj ponownie później.</p></numerusform> <numerusform><p>Wystąpił błąd w czasie aktualizacji wtyczek.</p><p>Spróbuj ponownie później.</p></numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>Wtyczka zainstalowana!</numerusform> <numerusform>Wtyczki zainstalowane!</numerusform> <numerusform>Wtyczki zainstalowane!</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>Twoja wtyczka została zainstalowana.</p><p>Jesteś gotowy do scrobblowania za pomocą odtwarzacza multimediów</p></numerusform> <numerusform><p>Twoje wtyczki zostały zainstalowane.</p><p>Jesteś gotowy do scrobblowania za pomocą odtwarzacza multimediów</p></numerusform> <numerusform><p>Twoje wtyczki zostały zainstalowane.</p><p>Jesteś gotowy do scrobblowania za pomocą odtwarzacza multimediów</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Twoje wtyczki nie zostały zainstalowane</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Możesz zainstalować je później przez menu Plik</translation> </message> <message> <source>File</source> <translation>Plik</translation> </message> <message> <source>Install plugins</source> <translation>Zainstaluj wtyczki</translation> </message> <message> <source>&Quit</source> <translation>&Wyjdź</translation> </message> <message> <source>View</source> <translation>Widok</translation> </message> <message> <source>My Last.fm Profile</source> <translation>Mój profil Last.fm</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobble</translation> </message> <message> <source>Refresh</source> <translation>Odśwież</translation> </message> <message> <source>Controls</source> <translation>Opcje</translation> </message> <message> <source>Account</source> <translation>Konto</translation> </message> <message> <source>Tools</source> <translation>Narzędzia</translation> </message> <message> <source>Check for Updates</source> <translation>Sprawdź czy są dostępne aktualizacje</translation> </message> <message> <source>Options</source> <translation>Opcje</translation> </message> <message> <source>Window</source> <translation>Okno</translation> </message> <message> <source>Minimize</source> <translation>Minimalizuj</translation> </message> <message> <source>Zoom</source> <translation>Zbliż</translation> </message> <message> <source>Bring All to Front</source> <translation>Pokaż wszystko na wierzchu</translation> </message> <message> <source>Help</source> <translation>Pomoc</translation> </message> <message> <source>About</source> <translation>O</translation> </message> <message> <source>FAQ</source> <translation>FAQ</translation> </message> <message> <source>Forums</source> <translation>Fora</translation> </message> <message> <source>Tour</source> <translation>Przewodnik</translation> </message> <message> <source>Show Licenses</source> <translation>Pokaż licencje</translation> </message> <message> <source>Diagnostics</source> <translation>Diagnostyka</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform><a href="tracks">%n odtworzenie</a> zostało przescrobblowane na tym urządzeniu</numerusform> <numerusform><a href="tracks">%n odtworzenia</a> zostały przescrobblowane na tym urządzeniu</numerusform> <numerusform><a href="tracks">%n odtworzeń</a> zostało przescrobblowanych na tym urządzeniu</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Cofnij do Scrobbli</translation> </message> <message> <source>Popular tags:</source> <translation>Popularne tagi:</translation> </message> <message> <source>Your tags:</source> <translation>Twoje tagi:</translation> </message> <message> <source>Similar Artists</source> <translation>Podobni wykonawcy</translation> </message> <message> <source>by %1</source> <translation>przez %1</translation> </message> <message> <source>from %1</source> <translation>z %1</translation> </message> <message> <source>Play %1 Radio</source> <translation>Odtwórz radio %1</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>Odtworzenie</numerusform> <numerusform>Odtworzenia</numerusform> <numerusform>Odtworzeń</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>Odtworzenie w twojej bibliotece</numerusform> <numerusform>Odtworzenia w twojej bibliotece</numerusform> <numerusform>Odtworzeń w twojej bibliotece</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>Słuchacz</numerusform> <numerusform>Słuchaczy</numerusform> <numerusform>Słuchaczy</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>Z %1 i innymi.</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>Z %1, %2 i innymi.</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>Edytowane %1 | %2 Edytuj</translation> </message> <message> <source>Downloads</source> <translation>Pobrań</translation> </message> <message> <source>Search on %1</source> <translation>Wyszukaj w %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Kup w %1 %2</translation> </message> <message> <source>Physical</source> <translation>Płyta CD</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>Polecane ponieważ słuchałeś %1.</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>Polecane ponieważ słuchałeś %1 oraz %2.</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>Polecane ponieważ słuchałeś %1, %2 oraz %3.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>Polecane ponieważ słuchałeś %1, %2, %3 oraz %4.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>Polecane ponieważ słuchałeś %1, %2, %3, %4 oraz %5.</translation> </message> <message> <source>From %1's library.</source> <translation>Z biblioteki %1.</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>Z bibliotek %1 oraz %2.</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 raz</numerusform> <numerusform>%L1 razy</numerusform> <numerusform>%L1 razy</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>Z bibliotek %1, %2 oraz %3.</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>Słuchałeś %1 %2 i %3 %4.</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>Z bibliotek %1, %2, %3 oraz %4.</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>Słuchałeś %1 %2, ale nie tego utworu.</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>Z bibliotek %1, %2, %3, %4 oraz %5.</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>Po raz pierwszy słuchasz %1.</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>Witaj</translation> </message> <message> <source>Start a radio station</source> <translation>Uruchom stację radiową</translation> </message> <message> <source>Open iTunes</source> <translation>Uruchom iTunes</translation> </message> <message> <source>Open Music</source> <translation>Uruchom Music</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Uruchom Windows Media Player</translation> </message> <message> <source>Open Winamp</source> <translation>Uruchom Winamp</translation> </message> <message> <source>Open Foobar</source> <translation>Uruchom Foobar</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>Scrobbluj ze swojego odtwarzacza muzyki</h2><p>Rozpocznij słuchanie swojej muzyki w odtwarzaczu. Więcej informacji o utworach znajdziesz w zakładce Odtwarzane.</p></translation> </message> <message> <source>Hello, %1!</source> <translation>Witaj %1!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>Stacja radiowa</translation> </message> <message> <source>Play %1</source> <translation>Odtwórz %1</translation> </message> <message> <source>Multi-Library Radio</source> <translation>Radio wielu bibliotek</translation> </message> <message> <source>Cue %1</source> <translation>Przestaw na %1</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>Odtwórz radio biblioteki %1 oraz %2</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>Przestaw na radio biblioteki %1 oraz %2</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>Polub</translation> </message> <message> <source>Ban</source> <translation>Zablokuj</translation> </message> <message> <source>Play</source> <translation>Odtwórz</translation> </message> <message> <source>Skip</source> <translation>Pomiń</translation> </message> <message> <source>Pause</source> <translation>Pauza</translation> </message> <message> <source>Resume</source> <translation>Wznów</translation> </message> <message> <source>Unlove</source> <translation>Nie lubię</translation> </message> <message> <source>Tuning</source> <translation>Dostrajanie</translation> </message> <message> <source>A Radio Station</source> <translation>Stacja radiowa</translation> </message> <message> <source>Listening to</source> <translation>Słucha</translation> </message> <message> <source>Scrobbling from</source> <translation>Scrobbluje z</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 odtworzenie</numerusform> <numerusform>%L1 odtworzenia</numerusform> <numerusform>%L1 odtworzeń</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>Last.fm zaimportował twoją bibliotekę utworów. Kliknij OK, aby kontynuować.</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Import biblioteki Last.fm</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>Czy na pewno chcesz anulować importowanie?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Last.fm nie mógł znaleźć żadnych odtworzonych utworów w twojej bibliotece. Kliknij OK, aby kontynuować.</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm importuje twoją, bieżącą bibliotekę mediów...</translation> </message> <message> <source>Where is Winamp?</source> <translation>Gdzie jest Winamp?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Gdzie jest Windows Media Player?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>Importowanie biblioteki zakończone</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>Last.fm właśnie wysłał historię twoich odsłuchanych utworów na serwer. Twój profil zostanie zaktualizowany o nowe utwory w ciągu kilku minut.</translation> </message> <message> <source>Library Import Failed</source> <translation>Import biblioteki nie powiódł się</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Last.fm nie był w stanie zaimportować twojej historii odsłuchanych utworów. Może być to spowodowane przescrobblowaniem zbyt dużej liczby utworów. Historia odsłuchanych utworów może być importowana tylko do nowych profili.</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>Zastosuj się do wskazówek pojawiających się w twoim systemie operacyjnym, aby zainstalować wtyczki.</p><p>Po zainstalowaniu wtyczek na komputerze, kliknij <strong>Kontynuuj</strong>.</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>Trwa instalacja twoich wtyczek</translation> </message> <message> <source>Continue</source> <translation>Dalej</translation> </message> <message> <source><< Back</source> <translation><< Wróć</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Twoje wtyczki nie zostały zainstalowane</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Możesz zainstalować je później przez menu Plik</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>Twoje odtwarzacze multimediów wymagają specjalnej wtyczki Last.fm, aby móc scrobblować muzykę, której słuchasz.</p><p>Wybierz odtwarzacz, z którego chciałbyś scrobblować muzykę i kliknij <strong>Zainstaluj wtyczki</strong></p></translation> </message> <message> <source>(newer version)</source> <translation>(nowsza wersja)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(Wtyczka zainstalowana, zaznacz, aby przeinstalować)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>Kolejny krok, zainstaluj wtyczki Last.fm, aby móc scrobblować muzykę, której słuchasz.</translation> </message> <message> <source>Install Plugins</source> <translation>Zainstaluj wtyczki</translation> </message> <message> <source>Continue</source> <translation>Kontynuuj</translation> </message> <message> <source><< Back</source> <translation><< Wróć</translation> </message> <message> <source>Skip >></source> <translation>Pomiń >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>Ogólne</translation> </message> <message> <source>Accounts</source> <translation>Konta</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobblowanie</translation> </message> <message> <source>Devices</source> <translation>Urządzenia</translation> </message> <message> <source>Advanced</source> <translation>Zaawansowane</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>Radio %1</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 odtworzenie</numerusform> <numerusform>%L1 odtworzenia</numerusform> <numerusform>%L1 odtworzeń</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>Ulubieni wykonawcy tygodnia</translation> </message> <message> <source>Top Artists Overall</source> <translation>Ulubieni wykonawcy</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>Scrobble</numerusform> <numerusform>Scrobble</numerusform> <numerusform>Scrobbli</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>Ulubiony utwór</numerusform> <numerusform>Ulubione utwory</numerusform> <numerusform>Ulubionych utworów</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 wykonawca</numerusform> <numerusform>%L1 wykonawców</numerusform> <numerusform>%L1 wykonawców</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 utwór</numerusform> <numerusform>%L1 utwory</numerusform> <numerusform>%L1 utworów</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>Masz %1 w swojej bibliotece i słuchasz średnio %2 na dzień.</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>Scrobble od %1</numerusform> <numerusform>Scrobble od %1</numerusform> <numerusform>Scrobbli od %1</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>Ustawienia proksy</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>Host:</translation> </message> <message> <source>Username:</source> <translation>Nazwa użytkownika:</translation> </message> <message> <source>Port:</source> <translation>Port:</translation> </message> <message> <source>Password:</source> <translation>Hasło:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>nieznany odtwarzacz multimediów</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>Gdzie jest podłączony Twój iPod?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>Wpisz wykonawcę, tag i naciśnij Odtwórz</translation> </message> <message> <source>Play</source> <translation>Odtwórz</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>Być może warto posłuchać również %1, %2, %3 lub %4.</translation> </message> <message> <source>Play next</source> <translation>Odtwórz następny</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>Stacja radiowa</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>Musisz być subskrybentem, aby słuchać radia</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>Ostatnia stacja</translation> </message> <message> <source>A Radio Station</source> <translation>Stacja radiowa</translation> </message> <message> <source>Personal Stations</source> <translation>Stacje osobiste</translation> </message> <message> <source>My Library Radio</source> <translation>Radio mojej biblioteki</translation> </message> <message> <source>Music you know and love</source> <translation>Muzyka, którą znasz i lubisz</translation> </message> <message> <source>My Mix Radio</source> <translation>Moje radio mix</translation> </message> <message> <source>Your library plus new music</source> <translation>Twoja biblioteka wraz z nową muzyką</translation> </message> <message> <source>My Recommended Radio</source> <translation>Moje polecane radio</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>Subskrybuj, aby słuchać radia.</translation> </message> <message> <source>New music from Last.fm</source> <translation>Nowa muzyka z Last.fm</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>Musisz być subskrybentem Last.fm, aby słuchać radia w tej aplikacji. Subskrybuj teraz, aby słuchać i korzystać z dodatkowych możliwości!</translation> </message> <message> <source>Network Stations</source> <translation>Stacje sieciowe</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Subskrybuj Last.fm</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>Słuchaj za darmo na www.last.fm</translation> </message> <message> <source>My Friends' Radio</source> <translation>Radio moich znajomych</translation> </message> <message> <source>Music your friends like</source> <translation>Muzyka, którą lubią twoi znajomi</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>Radio moich sąsiadów</translation> </message> <message> <source>Music from listeners like you</source> <translation>Muzyka słuchaczy, których lubisz</translation> </message> <message> <source>Recent Stations</source> <translation>Ostatnio odtwarzane stacje</translation> </message> <message> <source>Now Playing</source> <translation>Odtwarzane</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>Subskrybuj, aby słuchać radia, tylko za %1 na miesiąc</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>Scrobble urządzenia</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>Wygląda na to, że odtwarzałeś już te utwory. Czy chcesz je przescrobblować?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>Scrobbluj urządzenia automatycznie</translation> </message> <message> <source>Toggle selection</source> <translation>Przełącz wybrane</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>%n odtworzenie zostało przescrobblowane na tym urządzeniu</numerusform> <numerusform>%n odtworzenia zostały przescrobblowane na tym urządzeniu</numerusform> <numerusform>%n odtworzeń zostało przescrobblowanych na tym urządzeniu</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>Ulubiony utwór</translation> </message> <message> <source>Add tags</source> <translation>Dodaj tagi</translation> </message> <message> <source>Love</source> <translation>Polub</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Udostępnij</translation> </message> <message> <source>Share on Last.fm</source> <translation>Udostępnij w Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Udostępnij na Twitterze</translation> </message> <message> <source>Share on Facebook</source> <translation>Udostępnij na Facebooku</translation> </message> <message> <source>Buy</source> <translation>Kup</translation> </message> <message> <source>Unlove track</source> <translation>Usuń z ulubionych</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Scrobbluj na</translation> </message> <message> <source>percent of the track</source> <translation>procent utworu</translation> </message> <message> <source>Enable scrobbling</source> <translation>Włącz scrobblowanie</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>...lub po 4 minutach (zależnie co nastąpi pierwsze)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>Scrobbluj podcasty</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>Pozwól Last.fm na fingerprinting moich utworów</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>Zaznaczone katalogi nie będą scrobblowane</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>Więcej scrobbli w Last.fm</translation> </message> <message> <source>Refreshing...</source> <translation>Odświeżanie...</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Odśwież scrobble</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>Wykonawca</translation> </message> <message> <source>Title</source> <translation>Tytuł</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Plays</source> <translation>Odtworzeń</translation> </message> <message> <source>Last Played</source> <translation>Ostatnio odtwarzany</translation> </message> <message> <source>Loved</source> <translation>Ulubiony</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>Utwór ma mniej niż 30 sekund</translation> </message> <message> <source>The artist name is missing</source> <translation>Brakuje nazwy wykonawcy</translation> </message> <message> <source>Invalid track title</source> <translation>Nieprawidłowa nazwa utworu</translation> </message> <message> <source>Invalid artist</source> <translation>Nieprawidłowy wykonawca</translation> </message> <message> <source>There is no timestamp</source> <translation>Brak oznaczenia czasowego</translation> </message> <message> <source>This track is too far in the future</source> <translation>Ten utwór jest z przyszłości</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>Utwór był odtwarzany ponad dwa tygodnie temu</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>Nie scrobblowałeś jeszcze żadnej muzyki w Last.fm</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>Zacznij słuchać muzyki w swoim odtwarzaczu lub uruchom stację radiową:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>Poleć znajomym</translation> </message> <message> <source>With:</source> <translation>Z:</translation> </message> <message> <source>Message (optional):</source> <translation>Wiadomość (opcjonalnie):</translation> </message> <message> <source>include in my recent activity</source> <translation>dodaj do mojej ostatniej aktywności</translation> </message> <message> <source>A track by %1</source> <translation>Utwór w wykonaniu %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Utwór w wykonaniu %1 z wydania %2</translation> </message> <message> <source>Check out %1</source> <translation>Sprawdź %1</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>Odtwarzane</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobble</translation> </message> <message> <source>Profile</source> <translation>Profil</translation> </message> <message> <source>Friends</source> <translation>Znajomi</translation> </message> <message> <source>Radio</source> <translation>Radio</translation> </message> <message> <source>Next Section</source> <translation>Następna sekcja</translation> </message> <message> <source>Previous Section</source> <translation>Poprzednia sekcja</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>Nie można uruchomić radia: %1</translation> </message> <message> <source>no results for "%1"</source> <translation>brak wyników dla "%1"</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>Scrobblowanie jest wyłączone</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>Dostępny</translation> </message> <message> <source>Offline</source> <translation>Niedostępny</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Choose something to tag:</source> <translation>Wybierz coś do otagowania:</translation> </message> <message> <source>Track</source> <translation>Utwór</translation> </message> <message> <source>Artist</source> <translation>Wykonawca</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>icon</source> <translation>ikona</translation> </message> <message> <source>Add tags:</source> <translation>Dodaj tagi:</translation> </message> <message> <source>A track by %1</source> <translation>Utwór w wykonaniu %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Utwór w wykonaniu %1 z wydania %2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>Wpisz tag powyżej lub wybierz z poniższych</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>Sortuj wg popularności</translation> </message> <message> <source>Sort Alphabetically</source> <translation>Sortuj alfabetycznie</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>Otwórz stronę Last.fm dla tego tagu</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>Teraz już możesz zacząć! Kliknij <strong>Zakończ</strong> i rozpocznij odkrywanie aplikacji.</p><p>Zakończył się także import twojej historii odsłuchanych utworów, co znaczy, że dodano je do twojego profilu Last.fm.</p><p>Dziękujemy za instalację aplikacji Last.fm, mamy nadzieję, że ci się spodoba!</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>Gotowe, możesz zaczynać!</translation> </message> <message> <source>Finish</source> <translation>Zakończ</translation> </message> <message> <source><< Back</source> <translation><< Wróć</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>Czerwona strzałka na twoim ekranie wskazuje lokalizację ikony aplikacji Last.fm w zasobniku systemowym.</p><p>Kliknij ikonę, aby szybko uzyskać dostęp do opcji odtwarzania, udostępniania lub tagowania utworów, edytowania swoich ustawień lub aby odwiedzić swój profil Last.fm.</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>Aplikacja Last.fm w twoim pasku menu</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>Aplikacja Last.fm w twoim zasobniku systemowym</translation> </message> <message> <source>Continue</source> <translation>Dalej</translation> </message> <message> <source><< Back</source> <translation><< Wróć</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>Dowiedz się więcej o muzyce, której słuchasz, włączając biografie, statystki odsłuchań, zdjęcia i podobnych wykonawców, jak również tagi stosowane przez innych słuchaczy opisujące to, czego słuchasz.</p><p>Zobacz zakładkę <strong>Odtwarzane</strong> lub kliknij dowolny utwór w zakładce <strong>Scrobble</strong>, aby dowiedzieć się więcej.</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>Odkryj więcej informacji o wykonawcy, którego lubisz</translation> </message> <message> <source>Continue</source> <translation>Dalej</translation> </message> <message> <source><< Back</source> <translation><< Wróć</translation> </message> <message> <source>Skip Tour >></source> <translation>Pomiń przewodnik >></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>Słuchaj non-stop spersonalizowanego radia</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Korzystaj z aplikacji Last.fm, aby słuchać spersonalizowanego radia opartego na muzyce, której chcesz słuchać.</p><p>Każde odtworzenie, każdej stacji radiowej Last.fm jest zupełnie inne, zaczynając od stacji opartych na wykonawcach i tagach, po zupełnie nowe propozycje dostosowane do twojego muzycznego gustu.</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>Subskrybuj i słuchaj non-stop spersonalizowanego radia</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Subskrybuj Last.fm, aby korzystać z aplikacji Last.fm umożliwiającej słuchanie spersonalizowanego radia opartego na muzyce, której chcesz słuchać.</p><p>Każde odtworzenie, każdej stacji radiowej Last.fm jest zupełnie inne, zaczynając od stacji opartych na wykonawcach i tagach, po zupełnie nowe propozycje dostosowane do twojego muzycznego gustu.</p></translation> </message> <message> <source>Subscribe</source> <translation>Subskrybuj</translation> </message> <message> <source>Continue</source> <translation>Dalej</translation> </message> <message> <source><< Back</source> <translation><< Wróć</translation> </message> <message> <source>Skip Tour >></source> <translation>Pomiń przewodnik >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>Aplikacja działa w tle, bezgłośnie aktualizując twój profil Last.fm muzyką, której słuchasz, dzięki czemu możesz otrzymać propozycje muzyczne, informacje o występach i wiele więcej. </p><p>Możesz również wykorzystać aplikację Last.fm, do odszukiwania informacji o wykonawcach, których słuchasz oraz do odtwarzania spersonalizowanego radia.</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>Witaj w aplikacji Last.fm</translation> </message> <message> <source>Continue</source> <translation>Dalej</translation> </message> <message> <source><< Back</source> <translation><< Wstecz</translation> </message> <message> <source>Skip Tour >></source> <translation>Pomiń przewodnik >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>Utwór</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Artist</source> <translation>Wykonawca</translation> </message> <message> <source>Love</source> <translation>Polub</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Poleć</translation> </message> <message> <source>Buy</source> <translation>Kup</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>Usuń ten scrobble z profilu</translation> </message> <message> <source>Play %1 Radio</source> <translation>Odtwórz radio %1</translation> </message> <message> <source>Cue %1 Radio</source> <translation>Przestaw na radio %1</translation> </message> <message> <source>%1 Radio</source> <translation>Radio %1</translation> </message> <message> <source>Cached</source> <translation>Buforowane</translation> </message> <message> <source>Error: %1</source> <translation>Błąd: %1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Poleć w Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Poleć na Twitterze</translation> </message> <message> <source>Share on Facebook</source> <translation>Poleć na Facebooku</translation> </message> <message> <source>Now listening</source> <translation>Teraz słucha</translation> </message> <message> <source>Downloads</source> <translation>Pobrań</translation> </message> <message> <source>Search on %1</source> <translation>Wyszukaj w %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Kup w %1 %2</translation> </message> <message> <source>Physical</source> <translation>Nośnik</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>Powiązane konta użytkownika:</translation> </message> <message> <source>Add New User Account</source> <translation>Dodaj nowe konto użytkownika</translation> </message> <message> <source>Add User Error</source> <translation>Błąd dodawania użytkownika</translation> </message> <message> <source>This user has already been added.</source> <translation>Ten użytkownik został już dodany.</translation> </message> <message> <source>Removing %1</source> <translation>Usuwanie %1</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>Czy na pewno chcesz usunąć tego użytkownika? Wszystkie ustawienia użytkownika zostaną usunięte i będziesz musiał ponownie autoryzować konto, aby móc scrobblować.</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>Subskrybuj</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>Usuń</translation> </message> <message> <source>(currently logged in)</source> <translation>(aktualnie zalogowany)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>Konta</translation> </message> <message> <source>Show Scrobbler</source> <translation>Pokaż scrobbler</translation> </message> <message> <source>Love</source> <translation>Polub</translation> </message> <message> <source>Play</source> <translation>Odtwórz</translation> </message> <message> <source>Skip</source> <translation>Pomiń</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Poleć</translation> </message> <message> <source>Ban</source> <translation>Zablokuj</translation> </message> <message> <source>Mute</source> <translation>Wycisz</translation> </message> <message> <source>Scrobble iPod...</source> <translation>Scrobbluj iPod...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Odwiedź profil Last.fm</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Włącz scrobblowanie</translation> </message> <message> <source>Quit %1</source> <translation>Wyłącz %1</translation> </message> <message> <source>from %1</source> <translation>z %1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>Osiągnąłeś limit pominięć dla tej stacji. Będziesz mógł pominąć ponownie za %n minutę.</numerusform> <numerusform>Osiągnąłeś limit pominięć dla tej stacji. Będziesz mógł pominąć ponownie za %n minuty.</numerusform> <numerusform>Osiągnąłeś limit pominięć dla tej stacji. Będziesz mógł pominąć ponownie za %n minut.</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>Pozostało %n pominięcie dla tej stacji.</numerusform> <numerusform>Pozostały %n pominięcia dla tej stacji.</numerusform> <numerusform>Pozostało %n pominięć dla tej stacji.</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>Wymagana autoryzacja</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>Konto użytkownika <strong>%1</strong> nie posiada autoryzacji w Last.fm.</p><p>Kliknij OK, aby rozpocząć proces konfiguracji i ponownie autoryzować to konto.</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>Czy na pewno chcesz wyłączyć %1?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>%1 zostanie wyłączony. Odtwarzane utwory nie będą scrobblowane, jeśli będziesz kontynuować.</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>Zmiana użytkownika</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 będzie zalogowany w Scrobblerze oraz radiu Last.fm. Muzyka będzie teraz scrobblowana dla tego konta. Czy na pewno chcesz kontynuować?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>Zamknij następujące aplikacje, aby kontynuować.</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>Błąd instalacji wtyczki</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>Wystąpił błąd podczas aktualizacji wtyczki.</p><p>Spróbuj ponownie później.</p></translation> </message> <message> <source>Plugin installed!</source> <translation>Wtyczka zainstalowana!</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>Wtyczka dla %1 została zainstalowana.</p><p>Jesteś gotowy na scrobblowanie z %1.</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>Wtyczka dla %1 nie została zainstalowana</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>Nie zamknięto %1, w wyniku czego nie zainstalowano wtyczki.</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>Zamknij iTunes, aby zaktualizować wtyczkę!</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>Twoja wtyczka iTunes (%2) jest inna niż dostarczona z tą wersją aplikacji (%1).</p><p>Zamknij iTunes, aby aktualizować teraz.</p></translation> </message> <message> <source>not installed</source> <translation>nie zainstalowana</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>Twoja wtyczka nie została zainstalowana</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>Wystąpił błąd w czasie usuwania starej wtyczki</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>Wtyczka iTunes została zainstalowana!</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>Twoja wtyczka iTunes została zainstalowana.</p><p>Można scrobblować na tym urządzeniu.</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>Wystąpił błąd w czasie kopiowania nowej wtyczki do jej lokalizacji</translation> </message> <message> <source>You didn't close iTunes</source> <translation>Nie zamknięto iTunes</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>Nastąpiła podróż w czasie</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>%n minutę temu</numerusform> <numerusform>%n minuty temu</numerusform> <numerusform>%n minut temu</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>%n godzinę temu</numerusform> <numerusform>%n godziny temu</numerusform> <numerusform>%n godzin temu</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>Wystąpił błąd sieci: %1</translation> </message> <message> <source>You have not authorised this application</source> <translation>Nie uwierzytelniono tej aplikacji</translation> </message> <message> <source>Authentication Error</source> <translation>Błąd autoryzacji</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>Odśwież arkusz</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>Nie pytaj ponownie</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>Wykryj automatycznie</translation> </message> <message> <source>No-proxy</source> <translation>Brak proksy</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_pt.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="pt"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>Sobre</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 (criado no Qt %2)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>Clique no botão <strong>Sim, permitir acesso</strong> no seu navegador para conectar sua conta Last.fm ao Last.fm Desktop App.</p><p>Caso não tenha se conectado porque fechou a janela ou clicou em Cancelar, tente novamente.<p /></p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>Estamos esperando você conectar-se à Last.fm</translation> </message> <message> <source><< Back</source> <translation><< Voltar</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source>Try Again</source> <translation>Tentar novamente</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>Se o navegador não abrir, copie e cole o link abaixo na barra de endereços.</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>Atalhos de teclado:</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Mostrar/Ocultar Last.fm</translation> </message> <message> <source>Proxy:</source> <translation>Proxy:</translation> </message> <message> <source>Enable SSL</source> <translation>Habilitar SSL</translation> </message> <message> <source>Cache Size:</source> <translation>Tamanho do cache:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>Assinante</translation> </message> <message> <source>Moderator</source> <translation>Moderador</translation> </message> <message> <source>Staff</source> <translation>Equipe</translation> </message> <message> <source>Alumna</source> <translation>Ex-equipe</translation> </message> <message> <source>Alumnus</source> <translation>Ex-equipe</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>Em tour</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>Para obter as melhores recomendações possíveis com base no seu gosto musical, sugerimos que você importe o histórico de músicas do seu media player.</p><p>Selecione o seu player preferido e clique em <strong>Iniciar importação</strong></p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Seus plug-ins ainda não foram instalados</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Você poderá instalá-los mais tarde por meio do menu Arquivo</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>Agora vamos importar o seu histórico de músicas</translation> </message> <message> <source>Start Import</source> <translation>Iniciar importação</translation> </message> <message> <source><< Back</source> <translation><< Voltar</translation> </message> <message> <source>Skip >></source> <translation>Pular >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>Não se preocupe, o processo de carregamento não deve levar mais do que alguns minutos, dependendo do tamanho da sua biblioteca de músicas.</p><p>Enquanto trabalhamos duro para adicionar o histórico de músicas ao seu perfil na Last.fm, por que você não aproveita para conferir os principais recursos do Last.fm Desktop App? Clique em <strong>Continuar</strong> para fazer um tour.</p></translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Voltar</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>Fechar aplicativos</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>Scobbling de dispositivo desativado - plug-in do iTunes incompatível - %1</translation> </message> <message> <source>please update</source> <translation>Atualize</translation> </message> <message> <source>Scrobble iPod</source> <translation>Fazer scrobble do iPod</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>Deseja associar o dispositivo %1 à sua conta de usuário do audioscrobbler?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>Dispositivo associado com sucesso à sua conta de usuário. A partir de agora você pode fazer o scrobble das faixas que ouvir neste dispositivo.</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>%1 faixas incluídas no scrobble.</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>Não foi incluída nenhuma faixa no scrobble desde a sua última sincronização.</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>Não foi possível abrir o banco de dados do iPod.</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>Ocorreu um erro desconhecido ao tentar acessar o banco de dados do iPod.</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>Diagnóstico</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbling</translation> </message> <message> <source>This is an easter egg!</source> <translation>Isto é um ovo de Páscoa!</translation> </message> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Track</source> <translation>Faixa</translation> </message> <message> <source>Album</source> <translation>Álbum</translation> </message> <message> <source>Fingerprinting</source> <translation>Fingerprinting</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>Faixas com fingerprints recentes</translation> </message> <message> <source>iPod Scrobbling</source> <translation>Scrobbling do iPod</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>O iTunes gerencia automaticamente meu iPod</translation> </message> <message> <source>I manually manage my iPod</source> <translation>Eu gerencio manualmente meu iPod</translation> </message> <message> <source>Scrobble iPod</source> <translation>Fazer scrobble do iPod</translation> </message> <message> <source>Logs</source> <translation>Registros</translation> </message> <message> <source>&Close</source> <translation>&Fechar</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n faixa no cache local</numerusform> <numerusform>%n faixas no cache local</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>Last.fm Desktop App</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>Obrigado <strong>%1</strong>, a sua conta está agora conectada!</translation> </message> <message> <source>Importing...</source> <translation>Importando...</translation> </message> <message> <source>Import complete!</source> <translation>Importação concluída!</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Encontre seus amigos na Last.fm</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>Você ainda não fez nenhum amigo na Last.fm.</h3><p>Encontre facilmente seus contatos e amigos do Facebook na Last.fm por meio do Localizador de amigos.<p /></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>Pesquisar por nome de usuário ou nome real</translation> </message> <message> <source>Refresh Friends</source> <translation>Atualizar amigos</translation> </message> <message> <source>Refreshing...</source> <translation>Atualizando...</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>Rádio da biblioteca de %1</translation> </message> <message> <source>Male</source> <translation>Masculino</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>Fazendo scrobble - %1</translation> </message> <message> <source>Female</source> <translation>Feminino</translation> </message> <message> <source>Scrobbling now</source> <translation>Fazendo scrobble</translation> </message> <message> <source>Neuter</source> <translation>Neutro</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>Procure seus amigos</translation> </message> <message> <source>Browse Friends</source> <translation>Procurar amigos</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>Idioma:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>Mostrar ícone do aplicativo na barra de menus</translation> </message> <message> <source>Launch application with media players</source> <translation>Abrir o aplicativo junto com media players</translation> </message> <message> <source>Show dock icon</source> <translation>Mostrar ícone de doca</translation> </message> <message> <source>Show desktop notifications</source> <translation>Mostrar notificações na área de trabalho</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>Enviar relatórios de falha para a Last.fm</translation> </message> <message> <source>Check for updates automatically</source> <translation>Procurar atualizações automaticamente</translation> </message> <message> <source>Enable media keys</source> <translation>Habilitar chaves de mídia</translation> </message> <message> <source>System Language</source> <translation>Idioma do sistema</translation> </message> <message> <source>Restart now?</source> <translation>Reinicializar agora?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>É necessário reinicializar o aplicativo para que a alteração tenha efeito. Deseja reinicializar agora?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>Atualização para versões beta - Aviso: somente para os corajosos!</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>Não foi possível abrir o banco de dados do iPod.</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>O uso de um aplicativo de scrobbling no iOS, como %1, pode resultar em scrobbles duplicados. Permita o scrobble em apenas um deles.</p> <p>O iTunes Match sincroniza as contagens de execução, mas não os horários das últimas execuções entre os vários dispositivos. Isso levará a scrobbles duplicados, em horários incorretos. Por enquanto, recomendamos que os usuários do iTunes Match desativem o scrobbling de dispositivo nos dispositivos desktop e façam o scrobble de iPhones/iPods usando um aplicativo de scrobbling de iOS, como o %1.</p></translation> </message> <message> <source>Setting not changed</source> <translation>A configuração não foi alterada</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>Você não fechou o iTunes para que esta configuração fosse alterada</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>Habilitar scrobbling do dispositivo</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>Confirmar scrobbles do dispositivo</translation> </message> <message> <source>Please note</source> <translation>Observação</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>Licenças</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>Terminamos?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>Se você aprovou o aplicativo, clique em OK.</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>A Last.fm precisa primeiro de sua permissão!</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>Este aplicativo precisa de permissão para se conectar ao seu perfil Last.fm. Clique em OK para ir ao site da Last.fm e fazer isso.</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>Você já é um usuário da Last.fm? Conecte a sua conta ao Last.fm Desktop App para que o seu perfil seja atualizado com as músicas que você ouve.</p><p>Se não tiver uma conta, você pode se registrar gratuitamente.</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Para começar, vamos conectar a sua conta Last.fm</translation> </message> <message> <source>Connect Your Account</source> <translation>Conecte sua conta</translation> </message> <message> <source>Sign up</source> <translation>Registre-se</translation> </message> <message> <source>Proxy?</source> <translation>Proxy?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>Existem atualizações para os plug-ins dos seus media players. Gostaria de instalá-las agora?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>Erro de instalação de plug-in</numerusform> <numerusform>Erro de instalação de plug-ins</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>Ocorreu um erro durante a atualização do plug-in.</p><p>Tente novamente mais tarde.</p></numerusform> <numerusform><p>Ocorreu um erro durante a atualização dos plug-ins.</p><p>Tente novamente mais tarde.</p></numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>Plug-in instalado!</numerusform> <numerusform>Plug-ins instalados!</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>O plug-in foi instalado.</p><p>Você agora está pronto para fazer scrobble com o seu media player</p></numerusform> <numerusform><p>Os plug-ins foram instalados.</p><p>Você agora está pronto para fazer scrobble com os seus media players</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Seus plug-ins ainda não estão instalados</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Você poderá instalá-los mais tarde por meio do menu Arquivo</translation> </message> <message> <source>File</source> <translation>Arquivo</translation> </message> <message> <source>Install plugins</source> <translation>Instalar plug-ins</translation> </message> <message> <source>&Quit</source> <translation>&Sair</translation> </message> <message> <source>View</source> <translation>Exibir</translation> </message> <message> <source>My Last.fm Profile</source> <translation>Meu perfil na Last.fm</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobbles</translation> </message> <message> <source>Refresh</source> <translation>Atualizar</translation> </message> <message> <source>Controls</source> <translation>Controles</translation> </message> <message> <source>Account</source> <translation>Conta</translation> </message> <message> <source>Tools</source> <translation>Ferramentas</translation> </message> <message> <source>Check for Updates</source> <translation>Verificar atualizações</translation> </message> <message> <source>Options</source> <translation>Opções</translation> </message> <message> <source>Window</source> <translation>Janela</translation> </message> <message> <source>Minimize</source> <translation>Minimizar</translation> </message> <message> <source>Zoom</source> <translation>Zoom</translation> </message> <message> <source>Bring All to Front</source> <translation>Trazer todos para frente</translation> </message> <message> <source>Help</source> <translation>Ajuda</translation> </message> <message> <source>About</source> <translation>Sobre</translation> </message> <message> <source>FAQ</source> <translation>Perguntas frequentes</translation> </message> <message> <source>Forums</source> <translation>Fóruns</translation> </message> <message> <source>Tour</source> <translation>Tour</translation> </message> <message> <source>Show Licenses</source> <translation>Mostrar licenças</translation> </message> <message> <source>Diagnostics</source> <translation>Diagnóstico</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform><a href="tracks">%n execução</a> incluída no scrobble a partir de um dispositivo</numerusform> <numerusform><a href="tracks">%n execuções</a> incluídas no scrobble a partir de um dispositivo</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Voltar para os scrobbles</translation> </message> <message> <source>Popular tags:</source> <translation>Tags populares:</translation> </message> <message> <source>Your tags:</source> <translation>Suas tags:</translation> </message> <message> <source>Similar Artists</source> <translation>Artistas similares</translation> </message> <message> <source>by %1</source> <translation>por %1</translation> </message> <message> <source>from %1</source> <translation>de %1</translation> </message> <message> <source>Play %1 Radio</source> <translation>Executar rádio %1</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>Execução</numerusform> <numerusform>Execuções</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>Execução na sua biblioteca</numerusform> <numerusform>Execuções na sua biblioteca</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>Ouvinte</numerusform> <numerusform>Ouvintes</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>Com %1 e mais.</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>Com %1, %2 e mais.</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>Editado em %1 | %2 Editar.</translation> </message> <message> <source>Downloads</source> <translation>Downloads</translation> </message> <message> <source>Search on %1</source> <translation>Pesquisar em %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Comprar em %1 %2</translation> </message> <message> <source>Physical</source> <translation>Físico</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>Recomendado porque você ouve %1.</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>Recomendado porque você ouve %1 e %2.</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>Recomendado porque você ouve %1, %2 e %3.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>Recomendado porque você ouve %1, %2, %3 e %4.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>Recomendado porque você ouve %1, %2, %3, %4 e %5.</translation> </message> <message> <source>From %1's library.</source> <translation>Da biblioteca de %1.</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>Das bibliotecas de %1 e %2.</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 vez</numerusform> <numerusform>%L1 vezes</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>Das bibliotecas de %1, %2 e %3</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>Você ouviu %1 %2 e %3 %4.</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>Das bibliotecas de %1, %2, %3 e %4.</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>Você ouviu %1 %2, mas nunca esta faixa.</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>Das bibliotecas de %1, %2, %3, %4 e %5.</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>Esta é a primeira vez que você ouve %1.</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>Olá!</translation> </message> <message> <source>Start a radio station</source> <translation>Iniciar uma estação de rádio</translation> </message> <message> <source>Open iTunes</source> <translation>Abrir o iTunes</translation> </message> <message> <source>Open Music</source> <translation>Abrir o Music</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Abrir o Windows Media Player</translation> </message> <message> <source>Open Winamp</source> <translation>Abrir o Winamp</translation> </message> <message> <source>Open Foobar</source> <translation>Abrir o Foobar</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>Faça o scrobble do seu player de músicas</h2><p>Comece a ouvir músicas no seu media player. Você pode ver mais informações sobre as faixas que ouve na guia Em execução.</p></translation> </message> <message> <source>Hello, %1!</source> <translation>Olá, %1!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>Uma estação de rádio</translation> </message> <message> <source>Play %1</source> <translation>Executar %1</translation> </message> <message> <source>Multi-Library Radio</source> <translation>Rádio de várias bibliotecas</translation> </message> <message> <source>Cue %1</source> <translation>Programar %1</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>Executar rádio de bibliotecas %1 e %2</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>Programar rádio de bibliotecas %1 e %2</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>Adicionar como preferida</translation> </message> <message> <source>Ban</source> <translation>Banir</translation> </message> <message> <source>Play</source> <translation>Executar</translation> </message> <message> <source>Skip</source> <translation>Pular</translation> </message> <message> <source>Pause</source> <translation>Pausa</translation> </message> <message> <source>Resume</source> <translation>Retomar</translation> </message> <message> <source>Unlove</source> <translation>Excluir das preferidas</translation> </message> <message> <source>Tuning</source> <translation>Sintonizando</translation> </message> <message> <source>A Radio Station</source> <translation>Uma estação de rádio</translation> </message> <message> <source>Listening to</source> <translation>Ouvindo</translation> </message> <message> <source>Scrobbling from</source> <translation>Fazendo o scrobble de</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 execução</numerusform> <numerusform>%L1 execuções</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>A Last.fm importou sua bilioteca de mídias. Clique em OK para continuar.</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Importar biblioteca Last.fm</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>Tem certeza de que deseja cancelar a importação?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>A Last.fm não encontrou nenhuma faixa executada na sua biblioteca de mídias. Clique em OK para continuar.</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>A Last.fm está importando a sua biblioteca de mídias atual...</translation> </message> <message> <source>Where is Winamp?</source> <translation>Onde está o Winamp?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Onde está o Windows Media Player?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>Importação de biblioteca de mídias concluída</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>A Last.fm enviou seu histórico de músicas para o servidor. O seu perfil será atualizado com as novas faixas em alguns instantes.</translation> </message> <message> <source>Library Import Failed</source> <translation>Falha ao importar a biblioteca</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Desculpe, a Last.fm não conseguiu importar o seu histórico de músicas. Provavelmente isso ocorreu porque você já fez o scrobble de muitas faixas. O histórico de músicas somente poderá ser importado para perfis recém-criados.</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>Siga as instruções exibidas no seu sistema operacional para instalar os plug-ins.</p><p>Uma vez instalados os plug-ins no computador, clique em <strong>Continuar</strong>.</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>Os plug-ins estão agora sendo instalados</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Voltar</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Seus plug-ins ainda não estão instalados</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Você poderá instalá-los mais tarde por meio do menu Arquivo</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>Os seus media players precisam de um plug-in especial da Last.fm para poder fazer o scrobble das músicas que você ouve.</p><p>Selecione os media players dos quais você gostaria de incluir as músicas e clique em <strong>Instalar plug-ins</strong></p></translation> </message> <message> <source>(newer version)</source> <translation>(versão mais recente)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(Plug-in instalado, marque para reinstalar)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>Em seguida, instale os plug-ins da Last.fm para poder fazer o scrobble das músicas que você ouve.</translation> </message> <message> <source>Install Plugins</source> <translation>Instalar plug-ins</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Voltar</translation> </message> <message> <source>Skip >></source> <translation>Pular >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>Geral</translation> </message> <message> <source>Accounts</source> <translation>Contas</translation> </message> <message> <source>Scrobbling</source> <translation>Scrobbling</translation> </message> <message> <source>Devices</source> <translation>Dispositivos</translation> </message> <message> <source>Advanced</source> <translation>Avançado</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>Rádio %1</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 execução</numerusform> <numerusform>%L1 execuções</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>Principais artistas desta semana</translation> </message> <message> <source>Top Artists Overall</source> <translation>Principais artistas - Geral</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>Scrobble</numerusform> <numerusform>Scrobbles</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>Faixa preferida</numerusform> <numerusform>Faixas preferidas</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 artista</numerusform> <numerusform>%L1 artistas</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 faixa</numerusform> <numerusform>%L1 faixas</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>Você tem %1 na sua biblioteca e, em média, ouve %2 por dia.</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>Scrobble desde %1</numerusform> <numerusform>Scrobbles desde %1</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>Configurações do Proxy</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>Host:</translation> </message> <message> <source>Username:</source> <translation>Nome do usuário:</translation> </message> <message> <source>Port:</source> <translation>Porta:</translation> </message> <message> <source>Password:</source> <translation>Senha:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>Media player desconhecido</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>Onde está montado seu iPod?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>Digite artista ou tag e aperte Executar</translation> </message> <message> <source>Play</source> <translation>Executar</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>Por que não experimenta %1, %2, %3 ou %4?</translation> </message> <message> <source>Play next</source> <translation>Executar próxima</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>Uma estação de rádio</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>Você precisa ser assinante para ouvir as rádios</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>Última estação</translation> </message> <message> <source>A Radio Station</source> <translation>Uma estação de rádio</translation> </message> <message> <source>Personal Stations</source> <translation>Estações pessoais</translation> </message> <message> <source>My Library Radio</source> <translation>Rádio da minha biblioteca</translation> </message> <message> <source>Music you know and love</source> <translation>Músicas que você conhece e gosta</translation> </message> <message> <source>My Mix Radio</source> <translation>Minha Rádio Mix</translation> </message> <message> <source>Your library plus new music</source> <translation>Sua biblioteca acompanhada de novas músicas</translation> </message> <message> <source>My Recommended Radio</source> <translation>Minha Rádio de Recomendações</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>Assine para ouvir as rádios</translation> </message> <message> <source>New music from Last.fm</source> <translation>Novas músicas da Last.fm</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>Você precisa ser assinante da Last.fm para ouvir a rádio neste aplicativo. Assine agora para começar a ouvir e aproveitar as vantagens dos outros excelentes benefícios!</translation> </message> <message> <source>Network Stations</source> <translation>Estações da sua rede</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Assine a Last.fm</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>Ouça gratuitamente no site www.last.fm</translation> </message> <message> <source>My Friends' Radio</source> <translation>Rádio dos meus amigos</translation> </message> <message> <source>Music your friends like</source> <translation>Músicas de que seus amigos gostam</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>Rádio dos meus vizinhos</translation> </message> <message> <source>Music from listeners like you</source> <translation>Músicas de ouvintes como você</translation> </message> <message> <source>Recent Stations</source> <translation>Estações recentes</translation> </message> <message> <source>Now Playing</source> <translation>Em execução</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>Assine para ouvir rádios, apenas %1 por mês</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>Scrobbles do dispositivo</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>Aparentemente você já ouviu essas faixas. Gostaria de fazer o scrobble delas?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>Fazer automaticamente o scrobble dos dispositivos</translation> </message> <message> <source>Toggle selection</source> <translation>Alternar seleção</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>%n execução foi incluída no scrobble de um dispositivo</numerusform> <numerusform>%n execuções foram incluídas no scrobble de um dispositivo</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>Adicionar faixa às preferidas</translation> </message> <message> <source>Add tags</source> <translation>Adicionar tags</translation> </message> <message> <source>Love</source> <translation>Adicionar às preferidas</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Compartilhar</translation> </message> <message> <source>Share on Last.fm</source> <translation>Compartilhar na Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Compartilhar no Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Compartilhar no Facebook</translation> </message> <message> <source>Buy</source> <translation>Comprar</translation> </message> <message> <source>Unlove track</source> <translation>Excluir faixa da lista de preferidas</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Fazer scrobble em</translation> </message> <message> <source>percent of the track</source> <translation>porcentagem da faixa</translation> </message> <message> <source>Enable scrobbling</source> <translation>Habilitar scrobbling</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>...ou aos 4 minutos (o que ocorrer primeiro)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>Fazer scrobble de podcasts</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>Permitir que a Last.fm gere fingerprints das minhas faixas</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>Os diretórios selecionados não serão incluídos no scrobble</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>Mais scrobbles na Last.fm</translation> </message> <message> <source>Refreshing...</source> <translation>Atualizando...</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Atualizar scrobbles</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Title</source> <translation>Título</translation> </message> <message> <source>Album</source> <translation>Álbum</translation> </message> <message> <source>Plays</source> <translation>Execuções</translation> </message> <message> <source>Last Played</source> <translation>Executada pela última vez em</translation> </message> <message> <source>Loved</source> <translation>Preferida</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>Esta faixa tem menos de 30 segundos</translation> </message> <message> <source>The artist name is missing</source> <translation>Falta o nome do artista</translation> </message> <message> <source>Invalid track title</source> <translation>Título de faixa inválido</translation> </message> <message> <source>Invalid artist</source> <translation>Artista inválido</translation> </message> <message> <source>There is no timestamp</source> <translation>Não existe registro de data/hora</translation> </message> <message> <source>This track is too far in the future</source> <translation>Esta faixa foi executada muito no futuro</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>Esta faixa foi executada há mais de duas semanas</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>Você ainda não fez o scrobble de nenhuma música para a Last.fm.</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>Comece a ouvir músicas no seu media player ou inicie uma estação de rádio:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>Compartilhe com amigos</translation> </message> <message> <source>With:</source> <translation>Com:</translation> </message> <message> <source>Message (optional):</source> <translation>Mensagem (opcional):</translation> </message> <message> <source>include in my recent activity</source> <translation>Incluir em minha atividade recente</translation> </message> <message> <source>A track by %1</source> <translation>Uma faixa de %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Uma faixa de %1 do álbum %2</translation> </message> <message> <source>Check out %1</source> <translation>Confira %1</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>Em execução</translation> </message> <message> <source>Scrobbles</source> <translation>Scrobbles</translation> </message> <message> <source>Profile</source> <translation>Perfil</translation> </message> <message> <source>Friends</source> <translation>Amigos</translation> </message> <message> <source>Radio</source> <translation>Rádio</translation> </message> <message> <source>Next Section</source> <translation>Próxima seção</translation> </message> <message> <source>Previous Section</source> <translation>Seção anterior</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>Não foi possível iniciar a rádio: %1</translation> </message> <message> <source>no results for "%1"</source> <translation>Não existem resultados para "%1"</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>O scrobbling está desativado</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>On-line</translation> </message> <message> <source>Offline</source> <translation>Off-line</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Choose something to tag:</source> <translation>Escolha um item para receber a tag:</translation> </message> <message> <source>Track</source> <translation>Faixa</translation> </message> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Album</source> <translation>Álbum</translation> </message> <message> <source>icon</source> <translation>ícone</translation> </message> <message> <source>Add tags:</source> <translation>Adicionar tags:</translation> </message> <message> <source>A track by %1</source> <translation>Uma faixa de %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Uma faixa de %1 do álbum %2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>Digite uma tag acima ou escolha abaixo</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>Classificar por popularidade</translation> </message> <message> <source>Sort Alphabetically</source> <translation>Classificar por ordem alfabética</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>Abrir a página Last.fm correspondente a esta tag</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>Você está pronto para começar! Clique em <strong>Concluir</strong> e comece a explorar.</p><p>Também terminamos de importar o histórico de músicas e o adicionamos ao seu perfil na Last.fm.</p><p>Obrigado por instalar o Last.fm Desktop App, esperamos que você goste de usá-lo!</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>Isso é tudo, você está pronto para prosseguir!</translation> </message> <message> <source>Finish</source> <translation>Concluir</translation> </message> <message> <source><< Back</source> <translation><< Voltar</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>A seta vermelha na tela aponta para a localização do Last.fm Desktop App na bandeja do sistema.</p><p>Clique no ícone para acessar rapidamente os controles de execução da rádio, compartilhar faixas e adicionar tags a elas, editar suas preferências e visitar o seu perfil Last.fm.</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>Last.fm Desktop App na sua barra de menus</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>Last.fm Desktop App na bandeja do sistema</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Voltar</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>Saiba mais sobre as músicas que você ouve, incluindo biografias, estatísticas musicais, fotos e artistas similares, bem como as tags que os ouvintes usam para descrevê-las.</p><p>Confira a guia <strong>Em execução</strong> ou simplesmente clique em qualquer faixa na guia <strong>Scrobbles</strong> para saber mais.</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>Descubra mais sobre os seus artistas preferidos</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Voltar</translation> </message> <message> <source>Skip Tour >></source> <translation>Pular tour >></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>Ouça rádios personalizadas ininterruptamente</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Use o Last.fm Desktop App para ouvir rádios personalizadas de acordo com as músicas que você gosta.</p><p>As faixas de cada estação Last.fm são totalmente diferentes e incluem desde estações baseadas em artistas e tags até recomendações totalmente novas, adaptadas ao seu gosto musical.</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>Assine e ouça rádios personalizadas, ininterruptamente</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Assine a Last.fm e use o Last.fm Desktop App para ouvir rádios personalizadas de acordo com as músicas que você gosta.</p> <p>As faixas de cada estação Last.fm são totalmente diferentes e incluem desde estações baseadas em artistas e tags até recomendações totalmente novas, adaptadas ao seu gosto musical.</p></translation> </message> <message> <source>Subscribe</source> <translation>Assinar</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Voltar</translation> </message> <message> <source>Skip Tour >></source> <translation>Pular tour >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>O cliente desktop é executado em segundo plano e atualiza silenciosamente o seu perfil Last.fm com as músicas que você ouve, o que lhe permite receber recomendações de músicas, dicas de shows e muito mais. </p><p>Você também pode usar o Last.fm Desktop App para saber mais sobre o artista que está ouvindo e para executar a rádio personalizada.</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>Bem-vindo ao Last.fm Desktop App!</translation> </message> <message> <source>Continue</source> <translation>Continuar</translation> </message> <message> <source><< Back</source> <translation><< Voltar</translation> </message> <message> <source>Skip Tour >></source> <translation>Pular tour >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>Faixa</translation> </message> <message> <source>Album</source> <translation>Álbum</translation> </message> <message> <source>Artist</source> <translation>Artista</translation> </message> <message> <source>Love</source> <translation>Adicionar às preferidas</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Compartilhar</translation> </message> <message> <source>Buy</source> <translation>Comprar</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>Excluir este scrobble do seu perfil</translation> </message> <message> <source>Play %1 Radio</source> <translation>Executar rádio %1</translation> </message> <message> <source>Cue %1 Radio</source> <translation>Programar rádio %1</translation> </message> <message> <source>%1 Radio</source> <translation>Rádio %1</translation> </message> <message> <source>Cached</source> <translation>Em cache</translation> </message> <message> <source>Error: %1</source> <translation>Erro: %1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Compartilhar na Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Compartilhar no Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Compartilhar no Facebook</translation> </message> <message> <source>Now listening</source> <translation>Ouvindo no momento</translation> </message> <message> <source>Downloads</source> <translation>Downloads</translation> </message> <message> <source>Search on %1</source> <translation>Pesquisar em %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Comprar em %1 %2</translation> </message> <message> <source>Physical</source> <translation>Físico</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>Contas de usuário conectadas:</translation> </message> <message> <source>Add New User Account</source> <translation>Adicionar nova conta de usuário</translation> </message> <message> <source>Add User Error</source> <translation>Adicionar erro de usuário</translation> </message> <message> <source>This user has already been added.</source> <translation>Esse usuário já foi adicionado.</translation> </message> <message> <source>Removing %1</source> <translation>Removendo %1</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>Tem certeza de que deseja remover este usuário? Todas as configurações de usuário serão perdidas e você precisará refazer a autenticação para voltar a fazer o scrobbling.</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>Assinar</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>Remover</translation> </message> <message> <source>(currently logged in)</source> <translation>(conectado no momento)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>Contas</translation> </message> <message> <source>Show Scrobbler</source> <translation>Mostrar Scrobbler</translation> </message> <message> <source>Love</source> <translation>Adicionar às preferidas</translation> </message> <message> <source>Play</source> <translation>Executar</translation> </message> <message> <source>Skip</source> <translation>Pular</translation> </message> <message> <source>Tag</source> <translation>Tag</translation> </message> <message> <source>Share</source> <translation>Compartilhar</translation> </message> <message> <source>Ban</source> <translation>Banir</translation> </message> <message> <source>Mute</source> <translation>Mudo</translation> </message> <message> <source>Scrobble iPod...</source> <translation>Fazer scrobble do iPod...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Visitar perfil Last.fm</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Habilitar scrobbling</translation> </message> <message> <source>Quit %1</source> <translation>Fechar %1</translation> </message> <message> <source>from %1</source> <translation>de %1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>Você atingiu o limite de pulos de faixas desta estação. Pule novamente em %n minuto.</numerusform> <numerusform>Você atingiu o limite de pulos de faixas desta estação. Pule novamente em %n minutos.</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>Você tem %n pulo de faixa restante nesta estação.</numerusform> <numerusform>Você tem %n pulos de faixas restantes nesta estação.</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>É necessário autenticar</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>A conta do usuário <strong>%1</strong> não está mais autenticada na Last.fm.</p><p>Clique em OK para iniciar o processo de configuração e autenticar novamente essa conta.</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>Tem certeza de que deseja fechar %1?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>%1 está prestes a fechar. As faixas executadas não serão incluídas no scrobble se você continuar.</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>Alterando o usuário</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 será conectado ao Scrobbler e à Rádio Last.fm. Todas as músicas serão agora direcionadas a esta conta. Deseja continuar?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>Para continuar, feche os aplicativos a seguir.</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>Ocorreu um erro ao instalar o plug-in.</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>Ocorreu um erro durante a atualização do plug-in.</p><p>Tente novamente mais tarde.</p></translation> </message> <message> <source>Plugin installed!</source> <translation>Plug-in instalado!</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>O plug-in %1 foi instalado.</p><p>Você agora está pronto para fazer o scrobble com %1.</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>O plug-in %1 não foi instalado</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>Você não fechou o %1, por isso o plug-in não foi instalado.</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>Feche o iTunes para atualizar o plug-in!</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>O plug-in do iTunes (%2) é diferente do plug-in enviado para esta versão do aplicativo (%1).</p><p>Feche o iTunes agora para atualizar.</p></translation> </message> <message> <source>not installed</source> <translation>Não instalado</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>O seu plug-in não foi instalado</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>Ocorreu um erro durante a remoção do plug-in antigo</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>Plug-in do iTunes instalado!</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>O plug-in do iTunes foi instalado.</p><p>Você está pronto para fazer o scrobble do dispositivo.</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>Ocorreu um erro ao copiar o novo plug-in para o local de destino</translation> </message> <message> <source>You didn't close iTunes</source> <translation>Você não fechou o iTunes</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>Data/hora inválidos</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>%n minuto atrás</numerusform> <numerusform>%n minutos atrás</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>%n hora atrás</numerusform> <numerusform>%n horas atrás</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>Ocorreu um erro na rede: %1</translation> </message> <message> <source>You have not authorised this application</source> <translation>Você não autorizou este aplicativo</translation> </message> <message> <source>Authentication Error</source> <translation>Erro de autenticação</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>Atualizar folha de estilos</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>Não perguntar novamente</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>Detectar automaticamente</translation> </message> <message> <source>No-proxy</source> <translation>Sem proxy</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_ru.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="ru"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>О программе</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 (на базе Qt %2)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>Нажми кнопку <strong>Да, разрешить доступ</strong> в браузере, чтобы привязать аккаунт Last.fm к скробблеру Last.fm.</p><p>Если не удалось привязать аккаунт из-за того, что ты нажал(а) кнопку "Отмена" или закрыл(а) окно браузера, попробуй еще раз.</p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>Необходимо подключиться к Last.fm</translation> </message> <message> <source><< Back</source> <translation><< Назад</translation> </message> <message> <source>Continue</source> <translation>Продолжить</translation> </message> <message> <source>Try Again</source> <translation>Повторить попытку</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>Если окно браузера не открылось, скопируй и вставь ссылку, опубликованную ниже, в строку адреса.</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>Сочетания клавиш:</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Показать/скрыть Last.fm</translation> </message> <message> <source>Proxy:</source> <translation>Прокси-сервер:</translation> </message> <message> <source>Enable SSL</source> <translation>Включить SSL</translation> </message> <message> <source>Cache Size:</source> <translation>Объем кэша:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>Подписчик</translation> </message> <message> <source>Moderator</source> <translation>Модератор</translation> </message> <message> <source>Staff</source> <translation>Сотрудник</translation> </message> <message> <source>Alumna</source> <translation>Выпускница</translation> </message> <message> <source>Alumnus</source> <translation>Выпускник</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>В турне</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>Ты можешь получать рекомендации, соответствующие твоему вкусу. Для этого рекомендуем импортировать историю воспроизведения из медиаплеера.</p><p>Выбери медиаплеер и нажми <strong>Начать импорт</strong></p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Модули не установлены</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Их можно установить позднее из меню "Файл"</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>Теперь мы импортируем твою историю воспроизведения</translation> </message> <message> <source>Start Import</source> <translation>Начать импорт</translation> </message> <message> <source><< Back</source> <translation><< Назад</translation> </message> <message> <source>Skip >></source> <translation>Пропустить >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>Не волнуйся, загрузка займет не более двух минут в зависимости от размера твоей музыкальной библиотеки.</p><p>Пока мы работаем над добавлением твоей истории воспроизведения в профиль Last.fm, ты можешь ознакомиться со списком возможностей скробблера Last.fm. Нажми <strong>Продолжить</strong> для ознакомления.</p></translation> </message> <message> <source>Continue</source> <translation>Продолжить</translation> </message> <message> <source><< Back</source> <translation><< Назад</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>Необходимо закрыть приложения</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>Скробллинг с устройства отключен - несовместимый модуль iTunes - %1</translation> </message> <message> <source>please update</source> <translation>требуется обновление</translation> </message> <message> <source>Scrobble iPod</source> <translation>Скробблинг iPod</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>Связать устройство %1 с твоим аккаунтом в аудиоскробблере?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>Устройство зарегистрировано в аккаунте. Теперь ты можешь скробблить композиции, прослушанные на этом устройстве.</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>Число заскроббленных композиций: %1</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>Нет композиций, прослушанных после последней синхронизации</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>Не удалось открыть базу данных iPod.</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>Неизвестная ошибка при обращении к базе данных iPod.</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>Диагностика</translation> </message> <message> <source>Scrobbling</source> <translation>Скробблинг</translation> </message> <message> <source>This is an easter egg!</source> <translation>Это пасхальное яйцо!</translation> </message> <message> <source>Artist</source> <translation>Исполнитель</translation> </message> <message> <source>Track</source> <translation>Композиция</translation> </message> <message> <source>Album</source> <translation>Альбом</translation> </message> <message> <source>Fingerprinting</source> <translation>Опознание музыки (Fingerprinting)</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>Недавно опознанные композиции</translation> </message> <message> <source>iPod Scrobbling</source> <translation>Скробблинг iPod</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>iPod автоматически синхронизируется с iTunes</translation> </message> <message> <source>I manually manage my iPod</source> <translation>Синхронизировать iPod вручную</translation> </message> <message> <source>Scrobble iPod</source> <translation>Скробблинг iPod</translation> </message> <message> <source>Logs</source> <translation>Журналы</translation> </message> <message> <source>&Close</source> <translation>&Закрыть</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n композиция сохранена в локальном кэше</numerusform> <numerusform>%n композиции сохранены в локальном кэше</numerusform> <numerusform>%n композиций сохранены в локальном кэше</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>Скробблер Last.fm</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>Большое спасибо, <strong>%1</strong>, твой аккаунт привязан!</translation> </message> <message> <source>Importing...</source> <translation>Импорт...</translation> </message> <message> <source>Import complete!</source> <translation>Импорт завершен!</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Найти друзей на Last.fm</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>У тебя пока нет друзей на Last.fm.</h3><p>Используй Friend Finder, чтобы найти своих друзей по Facebook и почтовые контакты на Last.fm.<p /></p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>Поиск по имени пользователя или реальному имени</translation> </message> <message> <source>Refresh Friends</source> <translation>Обновить список друзей</translation> </message> <message> <source>Refreshing...</source> <translation>Обновление...</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>Радио библиотеки %1</translation> </message> <message> <source>Male</source> <translation>Муж</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>Выполняется скробблинг из %1</translation> </message> <message> <source>Female</source> <translation>Жен</translation> </message> <message> <source>Scrobbling now</source> <translation>Выполняется сробблинг</translation> </message> <message> <source>Neuter</source> <translation>Не определен</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>Поиск друзей</translation> </message> <message> <source>Browse Friends</source> <translation>Просмотр списка друзей</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>Язык:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>Показывать значок приложения в строке меню</translation> </message> <message> <source>Launch application with media players</source> <translation>Запускать приложение одновременно с музыкальным проигрывателем</translation> </message> <message> <source>Show dock icon</source> <translation>Показать значок на панели Dock</translation> </message> <message> <source>Show desktop notifications</source> <translation>Показывать уведомления на рабочем столе</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>Отправлять отчеты об ошибках в Last.fm</translation> </message> <message> <source>Check for updates automatically</source> <translation>Автоматически проверять наличие обновлений</translation> </message> <message> <source>Enable media keys</source> <translation>Включить кнопки мультимедиа</translation> </message> <message> <source>System Language</source> <translation>Язык системы</translation> </message> <message> <source>Restart now?</source> <translation>Перезапустить приложение?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>Чтобы изменения вступили в силу, необходимо перезапустить приложение. Перезапустить приложение сейчас?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>Обновление до бета-версий (только для храбрых!)</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>Не удалось открыть базу данных iPod.</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>Использование приложения для скробблинга под iOS, например %1, может привести к дублированию скробблинга. Скробблинг следует активировать только в одном приложении.</p> <p>iTunes Match синхронизирует между устройствами информацию о количестве воспроизведений, но не о времени последнего воспроизведения. Это приводит к появлению повторяющихся записей и сохранению ошибочных временных меток. В настоящее время мы рекомендуем пользователям iTunes Match отключить скробблинг на компьютерах и использовать для скробблинга на iPhone/iPod скробблер для iOS, например %1.</p></translation> </message> <message> <source>Setting not changed</source> <translation>Настройка не была изменена</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>Чтобы изменить настройку, необходимо закрыть приложение iTunes</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>Включить скробблинг с устройства</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>Подтвердить скробблинг с устройства</translation> </message> <message> <source>Please note</source> <translation>Обрати внимание</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>Лицензии</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>Готово?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>Нажмите кнопку ОК после того, как подтвердите это приложение.</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Сначала Last.fm требуется твое разрешение!</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>Этому приложению требуется разрешение для подключения к твоему профилю Last.fm. Нажми кнопку OK, чтобы перейти на сайт Last.fm и предоставить разрешение.</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>Ты уже являешься пользователем Last.fm? Подключи свой аккаунт к скробблеру Last.fm для того, чтобы сохранить в твоем профиле музыку, которую ты слушаешь.</p><p>Если у тебя нет аккаунта, можешь зарегистрироваться прямо сейчас. Это бесплатно!</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Давай начнем с подключения твоего аккаунта Last.fm</translation> </message> <message> <source>Connect Your Account</source> <translation>Подключить аккаунт</translation> </message> <message> <source>Sign up</source> <translation>Регистрация</translation> </message> <message> <source>Proxy?</source> <translation>Прокси-сервер?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>Доступны обновления для модулей медиаплееров. Установить обновления?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>В ходе установки модуля возникла ошибка</numerusform> <numerusform>В ходе установки модулей возникла ошибка</numerusform> <numerusform>В ходе установки модулей возникла ошибка</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>При обновлении модуля возникла ошибка.</p><p>Повтори попытку позже.</p></numerusform> <numerusform><p>При обновлении модулей возникла ошибка.</p><p>Повтори попытку позже.</p></numerusform> <numerusform><p>При обновлении модулей возникла ошибка.</p><p>Повтори попытку позже.</p></numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>Модуль установлен!</numerusform> <numerusform>Модули установлены!</numerusform> <numerusform>Модули установлены!</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>Модуль установлен.</p><p>Проигрыватель готов для скробблинга</p></numerusform> <numerusform><p>Модули установлены.</p><p>Проигрыватель готов для скробблинга</p></numerusform> <numerusform><p>Подключаемые модули установлены.</p><p>Все готово для скробблинга в вашем плеере</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Модули не установлены</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Их можно установить позднее из меню "Файл"</translation> </message> <message> <source>File</source> <translation>Файл</translation> </message> <message> <source>Install plugins</source> <translation>Установить модули</translation> </message> <message> <source>&Quit</source> <translation>&Выход</translation> </message> <message> <source>View</source> <translation>Вид</translation> </message> <message> <source>My Last.fm Profile</source> <translation>Мой профиль Last.fm</translation> </message> <message> <source>Scrobbles</source> <translation>Композиции</translation> </message> <message> <source>Refresh</source> <translation>Обновить</translation> </message> <message> <source>Controls</source> <translation>Элементы управления</translation> </message> <message> <source>Account</source> <translation>Аккаунт</translation> </message> <message> <source>Tools</source> <translation>Инструменты</translation> </message> <message> <source>Check for Updates</source> <translation>Проверить наличие обновлений</translation> </message> <message> <source>Options</source> <translation>Настройки</translation> </message> <message> <source>Window</source> <translation>Окно</translation> </message> <message> <source>Minimize</source> <translation>Свернуть</translation> </message> <message> <source>Zoom</source> <translation>Масштаб</translation> </message> <message> <source>Bring All to Front</source> <translation>Все окна на передний план</translation> </message> <message> <source>Help</source> <translation>Справка</translation> </message> <message> <source>About</source> <translation>О программе</translation> </message> <message> <source>FAQ</source> <translation>Вопросы и ответы</translation> </message> <message> <source>Forums</source> <translation>Форумы</translation> </message> <message> <source>Tour</source> <translation>Обзор</translation> </message> <message> <source>Show Licenses</source> <translation>Показать лицензии</translation> </message> <message> <source>Diagnostics</source> <translation>Диагностика</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform><a href="tracks">%n композиция</a> заскробблена с этого устройства</numerusform> <numerusform><a href="tracks">%n композиции</a> заскробблены с этого устройства</numerusform> <numerusform><a href="tracks">%n композиций</a> заскробблены с этого устройства</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Вернуться к композициям</translation> </message> <message> <source>Popular tags:</source> <translation>Популярные теги:</translation> </message> <message> <source>Your tags:</source> <translation>Твои теги:</translation> </message> <message> <source>Similar Artists</source> <translation>Похожие исполнители</translation> </message> <message> <source>by %1</source> <translation>%1</translation> </message> <message> <source>from %1</source> <translation>с альбома %1</translation> </message> <message> <source>Play %1 Radio</source> <translation>Слушать радио %1</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>воспроизведение</numerusform> <numerusform>воспроизведения</numerusform> <numerusform>воспроизведений</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>воспроизведение в твоей библиотеке</numerusform> <numerusform>воспроизведения в твоей библиотеке</numerusform> <numerusform>воспроизведений в твоей библиотеке</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>слушатель</numerusform> <numerusform>слушателя</numerusform> <numerusform>слушателей</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>%1 и более.</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>%1, %2 и более.</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>Дата изменения %1 | %2 Изменить</translation> </message> <message> <source>Downloads</source> <translation>Загрузки</translation> </message> <message> <source>Search on %1</source> <translation>Поиск в %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Купить в %1 %2</translation> </message> <message> <source>Physical</source> <translation>На носителе</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>Рекомендуется, так как ты слушаешь %1.</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>Рекомендуется, так как ты слушаешь %1 и %2.</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>Рекомендуется, так как ты слушаешь %1, %2 и %3.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>Рекомендуется, так как ты слушаешь %1, %2, %3 и %4.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>Рекомендуется, так как ты слушаешь %1, %2, %3, %4 и %5.</translation> </message> <message> <source>From %1's library.</source> <translation>Из библиотеки %1.</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>Из библиотек %1 и %2.</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 раз</numerusform> <numerusform>%L1 раза</numerusform> <numerusform>%L1 раз</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>Из библиотек %1, %2 и %3.</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>Ты слушал(а) %1 %2 и %3 %4.</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>Из библиотек %1, %2, %3 и %4'.</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>Ты слушал(а) %1 %2, но не эту композицию</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>Из библиотек %1, %2, %3, %4 и %5.</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>Это твое первое прослушивание %1.</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>Привет!</translation> </message> <message> <source>Start a radio station</source> <translation>Включить радиостанцию</translation> </message> <message> <source>Open iTunes</source> <translation>Открыть iTunes</translation> </message> <message> <source>Open Music</source> <translation>Открыть Music</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Открыть проигрыватель Windows Media</translation> </message> <message> <source>Open Winamp</source> <translation>Открыть Winamp</translation> </message> <message> <source>Open Foobar</source> <translation>Открыть Foobar</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>Скробблинг из твоего музыкального проигрывателя</h2><p>Начни слушать музыку со своего проигрывателя. Дополнительная информация о воспроизводимых композициях будет отображаться на вкладке "Ты слушаешь".</p></translation> </message> <message> <source>Hello, %1!</source> <translation>Привет, %1!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>Радиостанция</translation> </message> <message> <source>Play %1</source> <translation>Слушать %1</translation> </message> <message> <source>Multi-Library Radio</source> <translation>Радио нескольких библиотек</translation> </message> <message> <source>Cue %1</source> <translation>Поставить в очередь %1</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>Слушать радио библиотек %1 и %2</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>Поставить в очередь радио библиотеки %1 и %2</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>В любимые</translation> </message> <message> <source>Ban</source> <translation>Запретить</translation> </message> <message> <source>Play</source> <translation>Слушать</translation> </message> <message> <source>Skip</source> <translation>Пропустить</translation> </message> <message> <source>Pause</source> <translation>Пауза</translation> </message> <message> <source>Resume</source> <translation>Возобновить</translation> </message> <message> <source>Unlove</source> <translation>Удалить из списка любимых</translation> </message> <message> <source>Tuning</source> <translation>Настройка</translation> </message> <message> <source>A Radio Station</source> <translation>Радиостанция</translation> </message> <message> <source>Listening to</source> <translation>Воспроизведение</translation> </message> <message> <source>Scrobbling from</source> <translation>Скробблинг из</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 воспроизведение</numerusform> <numerusform>%L1 воспроизведения</numerusform> <numerusform>%L1 воспроизведений</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>Клиент Last.fm завершил импорт твоей библиотеки. Нажми OK для продолжения.</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Импорт библиотеки Last.fm</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>Отменить импорт?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Last.fm не удалось найти композиции в твоей библиотеке. Нажми OK для продолжения.</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm выполняет импорт твоей библиотеки...</translation> </message> <message> <source>Where is Winamp?</source> <translation>Где находится Winamp?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Где находится проигрыватель Windows Media?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>Импорт библиотеки завершен</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>Приложение Last.fm передало журнал прослушенной музыки на сервер. Через несколько минут твой профиль будет обновлен, и в нем появятся новые композиции.</translation> </message> <message> <source>Library Import Failed</source> <translation>Не удалось импортировать библиотеку</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Не удалось импортировать журнал прослушанной музыки. Вероятно, в твоем профиле уже заскробблено некоторое количество композиций. Журнал прослушиваний можно импортировать только в новые профили.</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>Чтобы установить модули, следуй инструкциям операционной системы.</p><p>После установки модулей нажми кнопку <strong>Продолжить</strong>.</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>Выполняется установка модулей</translation> </message> <message> <source>Continue</source> <translation>Продолжить</translation> </message> <message> <source><< Back</source> <translation><< Назад</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Модули не установлены</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Их можно установить позднее из меню "Файл"</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>Чтобы скробблить музыку, которую ты слушаешь, необходимо установить специальные модули Last.fm для медиапроигрывателей.</p><p>Выбери медиапроигрыватели, с помощью которых ты хочешь скробблить музыку, и нажми <strong>Установить модули</strong></p></translation> </message> <message> <source>(newer version)</source> <translation>(более новая версия)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(Модуль установлен, щелкни флажок, чтобы переустановить)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>Следующий шаг — установка модулей Last.fm для скробблинга музыки.</translation> </message> <message> <source>Install Plugins</source> <translation>Установить модули</translation> </message> <message> <source>Continue</source> <translation>Продолжить</translation> </message> <message> <source><< Back</source> <translation><< Назад</translation> </message> <message> <source>Skip >></source> <translation>Пропустить >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>Общие</translation> </message> <message> <source>Accounts</source> <translation>Аккаунты</translation> </message> <message> <source>Scrobbling</source> <translation>Скробблинг</translation> </message> <message> <source>Devices</source> <translation>Устройства</translation> </message> <message> <source>Advanced</source> <translation>Дополнительно</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>Радио %1</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 воспроизведение</numerusform> <numerusform>%L1 воспроизведения</numerusform> <numerusform>%L1 воспроизведений</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>Лучшие исполнители этой недели</translation> </message> <message> <source>Top Artists Overall</source> <translation>Лучшие исполнители за все время</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>заскроббленная композиция</numerusform> <numerusform>заскроббленные композиции</numerusform> <numerusform>заскроббленных композиций</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>любимая композиция</numerusform> <numerusform>любимые композиции</numerusform> <numerusform>любимых композиций</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 исполнитель</numerusform> <numerusform>%L1 исполнителя</numerusform> <numerusform>%L1 исполнителей</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 композиция</numerusform> <numerusform>%L1 композиции</numerusform> <numerusform>%L1 композиций</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>%1 в твоей библиотеке, число прослушиваний в день: %2.</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>Композиция с %1</numerusform> <numerusform>Композиции с %1</numerusform> <numerusform>Композиций с %1</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>Параметры прокси-сервера</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>Сервер:</translation> </message> <message> <source>Username:</source> <translation>Имя пользователя:</translation> </message> <message> <source>Port:</source> <translation>Порт:</translation> </message> <message> <source>Password:</source> <translation>Пароль:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>неизвестный медиаплеер</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>Как подключен твой iPod?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>Укажи исполнителя/тег и нажми Слушать</translation> </message> <message> <source>Play</source> <translation>Воспр.</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>Может попробуешь %1, %2, %3 или %4?</translation> </message> <message> <source>Play next</source> <translation>Следующая композиция</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>Радиостанция</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>Слушать радио могут только подписчики</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>Последняя станция</translation> </message> <message> <source>A Radio Station</source> <translation>Радиостанция</translation> </message> <message> <source>Personal Stations</source> <translation>Персональные станции</translation> </message> <message> <source>My Library Radio</source> <translation>Радио моей библиотеки</translation> </message> <message> <source>Music you know and love</source> <translation>Музыка, которую ты знаешь и любишь</translation> </message> <message> <source>My Mix Radio</source> <translation>Мой радиомикс</translation> </message> <message> <source>Your library plus new music</source> <translation>Твоя библиотека и новая музыка</translation> </message> <message> <source>My Recommended Radio</source> <translation>Радио "Мои рекомендации"</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>Подпишись, чтобы получить доступ к радио</translation> </message> <message> <source>New music from Last.fm</source> <translation>Новая музыка на Last.fm</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>Радио в этом приложении доступно только для подписчиков Last.fm. Подпишись, и ты сможешь слушать радио и получишь доступ к другим функциям.</translation> </message> <message> <source>Network Stations</source> <translation>Сетевые станции</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Стать подписчиком Last.fm</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>Слушать бесплатно на www.lastfm.ru</translation> </message> <message> <source>My Friends' Radio</source> <translation>Радио моих друзей</translation> </message> <message> <source>Music your friends like</source> <translation>Музыка, которую любят твои друзья</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>Радио соседей</translation> </message> <message> <source>Music from listeners like you</source> <translation>Музыка пользователей, похожих на тебя</translation> </message> <message> <source>Recent Stations</source> <translation>Последние станции</translation> </message> <message> <source>Now Playing</source> <translation>Ты слушаешь</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>Стань подписчиком, чтобы слушать радио. Всего %1 в месяц!</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>Скробблинг с устройства</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>Ты слушал(а) эти композиции. Хочешь их заскробблить?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>Автоматический сробблинг с устройства</translation> </message> <message> <source>Toggle selection</source> <translation>Переключить</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>%n композиция заскробблена с устройства</numerusform> <numerusform>%n композиции заскробблено с устройства</numerusform> <numerusform>%n композиций заскробблено с устройства</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>В любимые</translation> </message> <message> <source>Add tags</source> <translation>Добавить теги</translation> </message> <message> <source>Love</source> <translation>В любимые</translation> </message> <message> <source>Tag</source> <translation>Тег</translation> </message> <message> <source>Share</source> <translation>Поделиться</translation> </message> <message> <source>Share on Last.fm</source> <translation>Поделиться на Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Поделиться в Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Поделиться в Facebook</translation> </message> <message> <source>Buy</source> <translation>Купить</translation> </message> <message> <source>Unlove track</source> <translation>Удалить композицию из списка любимых</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Скробблиниг на</translation> </message> <message> <source>percent of the track</source> <translation>% композиции</translation> </message> <message> <source>Enable scrobbling</source> <translation>Включить скробблинг</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>...или через 4 минуты (в зависимости от того, какое событие наступит раньше)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>Скробблинг подкастов</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>Разрешить Last.fm опознавать композиции</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>Не удалось выполнить скробблинг для выбранных каталогов</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>Другие заскроббленные композиции на Last.fm</translation> </message> <message> <source>Refreshing...</source> <translation>Обновление...</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Обновить заскроббленные композиции</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>Исполнитель</translation> </message> <message> <source>Title</source> <translation>Название</translation> </message> <message> <source>Album</source> <translation>Альбом</translation> </message> <message> <source>Plays</source> <translation>Воспроизведение</translation> </message> <message> <source>Last Played</source> <translation>Последнее воспроизведение</translation> </message> <message> <source>Loved</source> <translation>Любимые</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>Эта композиция короче 30 секунд</translation> </message> <message> <source>The artist name is missing</source> <translation>Имя исполнителя отсутствует</translation> </message> <message> <source>Invalid track title</source> <translation>Недопустимое название композиции</translation> </message> <message> <source>Invalid artist</source> <translation>Недопустимый исполнитель</translation> </message> <message> <source>There is no timestamp</source> <translation>Временная метка отсутствует</translation> </message> <message> <source>This track is too far in the future</source> <translation>Эта композиция слишком далеко в будущем</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>Эта композиция воспроизводилась более двух недель назад</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>Ты еще не заскробблил(а) музыку на Last.fm</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>Начни слушать музыку в медиаплеере или запусти радиостанцию:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>Поделиться с друзьями</translation> </message> <message> <source>With:</source> <translation>Кому:</translation> </message> <message> <source>Message (optional):</source> <translation>Сообщение (необязательно):</translation> </message> <message> <source>include in my recent activity</source> <translation>Включить в мои последние действия</translation> </message> <message> <source>A track by %1</source> <translation>Композиция %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Композиция %1 из альбома %2</translation> </message> <message> <source>Check out %1</source> <translation>Оцени %1</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>Ты слушаешь</translation> </message> <message> <source>Scrobbles</source> <translation>Композиции</translation> </message> <message> <source>Profile</source> <translation>Профиль</translation> </message> <message> <source>Friends</source> <translation>Друзья</translation> </message> <message> <source>Radio</source> <translation>Радио</translation> </message> <message> <source>Next Section</source> <translation>Следующий раздел</translation> </message> <message> <source>Previous Section</source> <translation>Предыдущий раздел</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>Не удалось включить радио: %1</translation> </message> <message> <source>no results for "%1"</source> <translation>нет результатов для "%1"</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>Скробблинг выключен</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>В сети</translation> </message> <message> <source>Offline</source> <translation>Не в сети</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>Тег</translation> </message> <message> <source>Choose something to tag:</source> <translation>Выбери, что нужно отметить тегом:</translation> </message> <message> <source>Track</source> <translation>Композиция</translation> </message> <message> <source>Artist</source> <translation>Исполнитель</translation> </message> <message> <source>Album</source> <translation>Альбом</translation> </message> <message> <source>icon</source> <translation>значок</translation> </message> <message> <source>Add tags:</source> <translation>Добавить теги:</translation> </message> <message> <source>A track by %1</source> <translation>Композиция %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>Композиция %1 из альбома %2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>Введи тег выше или выбери его из списка внизу</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>Сортировать по популярности</translation> </message> <message> <source>Sort Alphabetically</source> <translation>Сортировать по алфавиту</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>Открыть страницу этого тега на Last.fm</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>Все готово, можешь приступать! Просто нажми кнопку <strong>Готово</strong> и начинай изучать возможности приложения.</p><p>Мы также завершили импорт твоей истории воспроизведения и добавили ее в твой профиль Last.fm.</p><p>Благодарим за установку скробблера Last.fm, надеемся он тебе понравится!</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>Все готово, можешь приступать!</translation> </message> <message> <source>Finish</source> <translation>Готово</translation> </message> <message> <source><< Back</source> <translation><< Назад</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>Красной стрелкой на экране отмечено расположение скробблера Last.fm в панели задач.</p><p>Щелкни по этому значку, чтобы получить быстрый доступ к элементам управления радио, опубликовать композиции, добавить теги, изменить настройки и открыть профиль Last.fm.</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>Скробблер Last.fm в строке меню</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>Скробблер Last.fm в панели задач</translation> </message> <message> <source>Continue</source> <translation>Продолжить</translation> </message> <message> <source><< Back</source> <translation><< Назад</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>Узнай больше о музыке, которую слушаешь: в твоем распоряжении биографии, статистика воспроизведения, фотографии, рекомендации похожих исполнителей, а также теги, добавленные слушателями.</p><p>Открой вкладку <strong>Ты слушаешь</strong> или щелкни любую композицию на вкладке <strong>Композиции</strong>, чтобы узнать больше.</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>Узнай больше о своих любимых исполнителях</translation> </message> <message> <source>Continue</source> <translation>Продолжить</translation> </message> <message> <source><< Back</source> <translation><< Назад</translation> </message> <message> <source>Skip Tour >></source> <translation>Пропустить обзор >></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>Слушай персональное радио в режиме нон-стоп</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Используй Скробблер Last.fm для прослушивания персонального радио, соответствующего твоим предпочтениям.</p><p>Выбери любую станцию Last.fm - исполнителей, тегов или твоих рекомендаций, - и слушай каждый раз новое радио.</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>Стань подписчиком и слушай персональное радио в режиме нон-стоп</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Стань подписчиком Last.fm и используй Скробблер Last.fm для прослушивания персонального радио, соответствующего твоим предпочтениям.</p><p>Выбери любую станцию Last.fm - исполнителей, тегов или твоих рекомендаций, - и слушай каждый раз новое радио.</p></translation> </message> <message> <source>Subscribe</source> <translation>Подписка</translation> </message> <message> <source>Continue</source> <translation>Продолжить</translation> </message> <message> <source><< Back</source> <translation><< Назад</translation> </message> <message> <source>Skip Tour >></source> <translation>Пропустить обзор >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>Скробблер работает в фоновом режиме и обновляет твой профиль Last.fm музыкой, которую ты слушаешь. В дальнейшем эта информация используется для составления рекомендаций по музыке и концертам и др. </p><p>Кроме того, скробблер Last.fm можно использовать для поиска информации об исполнителе и прослушивания персональных радиостанций.</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>Добро пожаловать в скробблер Last.fm!</translation> </message> <message> <source>Continue</source> <translation>Продолжить</translation> </message> <message> <source><< Back</source> <translation><< Назад</translation> </message> <message> <source>Skip Tour >></source> <translation>Пропустить обзор >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>Композиция</translation> </message> <message> <source>Album</source> <translation>Альбом</translation> </message> <message> <source>Artist</source> <translation>Исполнитель</translation> </message> <message> <source>Love</source> <translation>В любимые</translation> </message> <message> <source>Tag</source> <translation>Тег</translation> </message> <message> <source>Share</source> <translation>Поделиться</translation> </message> <message> <source>Buy</source> <translation>Купить</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>Удалить эту композицию из твоего профиля</translation> </message> <message> <source>Play %1 Radio</source> <translation>Слушать радио %1</translation> </message> <message> <source>Cue %1 Radio</source> <translation>Поставить в очередь радио %1</translation> </message> <message> <source>%1 Radio</source> <translation>Радио %1</translation> </message> <message> <source>Cached</source> <translation>Сохранено в кэше</translation> </message> <message> <source>Error: %1</source> <translation>Ошибка: %1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Поделиться на Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Поделиться в Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Поделиться в Facebook</translation> </message> <message> <source>Now listening</source> <translation>Ты слушаешь</translation> </message> <message> <source>Downloads</source> <translation>Загрузки</translation> </message> <message> <source>Search on %1</source> <translation>Поиск в %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Купить в %1 %2</translation> </message> <message> <source>Physical</source> <translation>На носителе</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>Подключенные аккаунты</translation> </message> <message> <source>Add New User Account</source> <translation>Добавить аккаунт</translation> </message> <message> <source>Add User Error</source> <translation>При добавлении пользователя возникла ошибка</translation> </message> <message> <source>This user has already been added.</source> <translation>Пользователь уже добавлен.</translation> </message> <message> <source>Removing %1</source> <translation>Удаление %1</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>Действительно удалить пользователя? Все настройки этого пользователя будут потеряны, и тебе придется ввести имя пользователя или пароль, чтобы активировать скробблинг в будущем.</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>Подписаться</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>Удалить</translation> </message> <message> <source>(currently logged in)</source> <translation>(в системе)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>Аккаунты</translation> </message> <message> <source>Show Scrobbler</source> <translation>Показать скробблер</translation> </message> <message> <source>Love</source> <translation>В любимые</translation> </message> <message> <source>Play</source> <translation>Воспр.</translation> </message> <message> <source>Skip</source> <translation>Пропустить</translation> </message> <message> <source>Tag</source> <translation>Тег</translation> </message> <message> <source>Share</source> <translation>Поделиться</translation> </message> <message> <source>Ban</source> <translation>Запретить</translation> </message> <message> <source>Mute</source> <translation>Выключить звук</translation> </message> <message> <source>Scrobble iPod...</source> <translation>Скробблиниг iPod...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Открыть профиль Last.fm</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Включить скробблинг</translation> </message> <message> <source>Quit %1</source> <translation>Закрыть %1</translation> </message> <message> <source>from %1</source> <translation>с альбома %1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>Достигнуто ограничение станции по числу переходов между композициями. Повтори попытку через %n минуту.</numerusform> <numerusform>Достигнуто ограничение станции по числу переходов между композициями. Повтори попытку через %n минуты.</numerusform> <numerusform>Достигнуто ограничение станции по числу переходов между композициями. Повтори попытку через %n минут.</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>Для этой станции доступен %n переход между композициями.</numerusform> <numerusform>Для этой станции доступно %n перехода между композициями.</numerusform> <numerusform>Для этой станции доступно %n переходов между композициями.</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>Требуется аутентификация</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>Аккаунт <strong>%1</strong> больше не аутентифицирован на Last.fm.</p><p>Нажми кнопку OK, чтобы запустить процесс установки и заново аутентифицировать аккаунт.</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>Закрыть %1?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>Приложение %1 будет закрыто. Композиции не будут скробблиться, если ты продолжишь.</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>Смена пользователя</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 зарегистрируется в скробблере и радио Last.fm. Вся музыка будет скробблиться в этот аккаунт. Продолжить?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>Чтобы продолжить, закрой следующие приложения.</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>В ходе установки модуля возникла ошибка</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>В ходе установки модуля возникла ошибка.</p><p>Повтори попытку позже.</p></translation> </message> <message> <source>Plugin installed!</source> <translation>Модуль установлен.</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>Модуль %1 установлен.</p><p>Все готово для скробблинга в %1.</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>Модуль %1 не установлен</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>Ты не закрыл(а) приложение %1, поэтому модуль для него не был установлен.</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>Закрой iTunes для обновления модуля!</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>Версия установленного модуля iTunes (%2) отличается от версии, которая поставляется с этой версией приложения (%1).</p><p>Закрой iTunes для обновления.</p></translation> </message> <message> <source>not installed</source> <translation>не установлено</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>Модуль не установлен</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>Ошибка удаления старого модуля</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>Модуль iTunes не установлен!</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>Установка модуля iTunes завершена.</p><p>Все готово для скробблинга с этого устройства.</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>Ошибка копирования нового модуля</translation> </message> <message> <source>You didn't close iTunes</source> <translation>Ты не закрыл(а) iTunes</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>Ошибка отсчета времени</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>%n мин. назад</numerusform> <numerusform>%n мин. назад</numerusform> <numerusform>%n мин. назад</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>%n ч. назад</numerusform> <numerusform>%n ч. назад</numerusform> <numerusform>%n ч. назад</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>Ошибка сети: %1</translation> </message> <message> <source>You have not authorised this application</source> <translation>Ты не авторизовал(а) это приложение</translation> </message> <message> <source>Authentication Error</source> <translation>Ошибка аутентификации</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>Обновить таблицу стилей</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>Больше не спрашивать</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>Определять автоматически</translation> </message> <message> <source>No-proxy</source> <translation>Без прокси-сервера</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_sv.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="sv"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>Om</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 (byggt på Qt %2)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>Klicka på knappen <strong>Ja, tillåt åtkomst</strong> i din webbläsare för att ansluta ditt Last.fm-konto till Last.fm-programmet.</p><p>Om du inte har anslutit eftersom du har stängt din webbläsare eller klickat på avbryt, försök igen.</p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>Vi väntar på att du ska ansluta dig till Last.fm</translation> </message> <message> <source><< Back</source> <translation><< Tillbaka</translation> </message> <message> <source>Continue</source> <translation>Fortsätt</translation> </message> <message> <source>Try Again</source> <translation>Försök igen</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>Om din webbläsare inte öppnades, kopiera och klistra in länken nedan i ditt adressfält.</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>Tangentbordsgenvägar</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Visa/dölj Last.fm</translation> </message> <message> <source>Proxy:</source> <translation>Proxy:</translation> </message> <message> <source>Enable SSL</source> <translation>Aktivera SSL</translation> </message> <message> <source>Cache Size:</source> <translation>Cachestorlek:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>Abonnent</translation> </message> <message> <source>Moderator</source> <translation>Moderator</translation> </message> <message> <source>Staff</source> <translation>Personal</translation> </message> <message> <source>Alumna</source> <translation>Alumni</translation> </message> <message> <source>Alumnus</source> <translation>Alumni</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>På turné</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>För de bästa möjliga rekommendationerna baserat på din musiksmak rekommenderar vi att du importerar din lyssningshistorik från din mediaspelare.</p><p>Välj den mediaspelare du föredrar och klicka på <strong>Starta import</strong></p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Dina insticksprogram har inte installerats</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Du kan installera dem senare genom filmenyn</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>Låt oss nu importera din lyssningshistorik</translation> </message> <message> <source>Start Import</source> <translation>Starta import</translation> </message> <message> <source><< Back</source> <translation><< Tillbaka</translation> </message> <message> <source>Skip >></source> <translation>Hoppa över >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>Oroa dig inte, uppladdningsprocessen bör inte ta mer än några minuter beroende på storleken på ditt musikbibliotek.</p><p>Medan vi jobbar hårt med att lägga till din lyssningshistorik till din Last.fm-profil kan du kolla in Last.fm-programmets huvudfunktioner. Klicka på <strong>Fortsätt</strong> för att ta turen.</p></translation> </message> <message> <source>Continue</source> <translation>Fortsätt</translation> </message> <message> <source><< Back</source> <translation><< Tillbaka</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>Stäng program</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>Enhetsskrobbling aktiverad - inkompatibelt iTunes-insticksprogram - %1</translation> </message> <message> <source>please update</source> <translation>vänligen uppdatera</translation> </message> <message> <source>Scrobble iPod</source> <translation>Skrobbla iPod</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>Vill du associera enheten %1 till ditt audioscrobbler-användarkonto?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>Enheten har framgångsrikt associerats till ditt användarkonto. Från och med nu kan du skrobbla låtarna du lyssnar på på denna enhet.</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>%1 låtar skrobblade.</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>Inga låtar att skrobbla sedan din senaste synkning.</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>iPod-databasen kunde inte öppnas.</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>Ett okänt fel uppstod vid försöket att komma åt iPod-databasen.</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>Diagnostik</translation> </message> <message> <source>Scrobbling</source> <translation>Skrobbling</translation> </message> <message> <source>This is an easter egg!</source> <translation>Detta är ett påskägg!</translation> </message> <message> <source>Artist</source> <translation>Artist</translation> </message> <message> <source>Track</source> <translation>Låt</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Fingerprinting</source> <translation>Fingeravtryck</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>Nyligen fingerprintade låtar</translation> </message> <message> <source>iPod Scrobbling</source> <translation>iPod-skrobbling</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>iTunes hanterar min iPod automatiskt</translation> </message> <message> <source>I manually manage my iPod</source> <translation>Jag hanterar min iPod manuellt</translation> </message> <message> <source>Scrobble iPod</source> <translation>Skrobbla iPod</translation> </message> <message> <source>Logs</source> <translation>Loggar</translation> </message> <message> <source>&Close</source> <translation>&Stäng</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n lokalt cachad låt </numerusform> <numerusform>%n lokalt cachade låtar</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>Last.fm-programmet</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>Tack <strong>%1</strong>, ditt konto är nu anslutet!</translation> </message> <message> <source>Importing...</source> <translation>Importerar ...</translation> </message> <message> <source>Import complete!</source> <translation>Import slutförd!</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Hitta dina vänner på Last.fm</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>Du har inga vänner på Last.fm än.</h3><p>Hitta dina Facebook-vänner och e-postkontakter på Last.fm snabbt och enkelt med vänsökaren.<p /></p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>Sök efter en vän med användarnamn eller riktigt namn</translation> </message> <message> <source>Refresh Friends</source> <translation>Uppdatera vänner</translation> </message> <message> <source>Refreshing...</source> <translation>Uppdaterar ...</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>%1s biblioteksradio</translation> </message> <message> <source>Male</source> <translation>Man</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>Skrobblar nu från %1</translation> </message> <message> <source>Female</source> <translation>Kvinna</translation> </message> <message> <source>Scrobbling now</source> <translation>Skrobblar nu</translation> </message> <message> <source>Neuter</source> <translation>Neutral</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>Sök dina vänner</translation> </message> <message> <source>Browse Friends</source> <translation>Sök vänner</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>Språk:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>Visa programikon i menylistan</translation> </message> <message> <source>Launch application with media players</source> <translation>Starta program med mediaspelare</translation> </message> <message> <source>Show dock icon</source> <translation>Visa dockikon</translation> </message> <message> <source>Show desktop notifications</source> <translation>Visa skrivbordsmeddelanden</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>Skicka kraschrapporter till Last.fm</translation> </message> <message> <source>Check for updates automatically</source> <translation>Leta automatiskt efter uppdateringar</translation> </message> <message> <source>Enable media keys</source> <translation>Aktivera mediaknappar</translation> </message> <message> <source>System Language</source> <translation>Systemspråk</translation> </message> <message> <source>Restart now?</source> <translation>Starta om nu?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>Det krävs en omstart av programmet för att ändringen ska börja gälla. Vill du starta om nu?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>Uppdatera till betaversioner - varning: endast för de modiga!</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>iPod-databasen kunde inte öppnas.</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>Användningen av en iOS-skrobblingsapp som %1 kan resultera i dubbla skrobblingar. Aktivera endast skrobbling i en av dem.</p><p>iTunes Match synkroniserar antalet spelade låtar, men inte senaste gången det har spelats från flera enheter. Detta leder till dubbla skrobblingar vid fel tider. För tillfället rekommenderar vi därför att iTunes Match-användare avaktiverar enheter som skrobblar på datorenheter och att de skrobblar iPhones/iPods med en iOS-skrobblingsapp som %1.</p></translation> </message> <message> <source>Setting not changed</source> <translation>Inställningen har inte ändrats</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>Du stängde inte iTunes så denna inställning kunde ändras</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>Aktivera enhetsskrobbling</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>Bekräfta enhetsskrobbling</translation> </message> <message> <source>Please note</source> <translation>Observera</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>Licenser</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>Är vi klara?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>Klicka på OK när du har godkänt denna app.</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Last.fm behöver din tillåtelse först!</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>Detta program behöver din tillåtelse för att ansluta till din Last.fm-profil. Klicka på OK för att gå till Last.fm-webbsidan och göra detta.</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>Redan Last.fm-användare? Anslut ditt konto till Last.fm-programmet så kommer din profil att uppdateras med den musik du lyssnar på.</p><p>Om du inte har ett konto kan du registrera dig gratis nu.</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Kom igång genom att ansluta ditt Last.fm-konto</translation> </message> <message> <source>Connect Your Account</source> <translation>Anslut ditt konto</translation> </message> <message> <source>Sign up</source> <translation>Registrera dig</translation> </message> <message> <source>Proxy?</source> <translation>Proxy?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>Det finns uppdateringar för insticksprogrammen för dina mediaspelare. Vill du installera dem nu?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>Insticksprogram installationsfel</numerusform> <numerusform>Insticksprogram installationsfel</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>Det uppstod ett fel vid uppdateringen av ditt insticksprogram.</p><p>Vänligen försök igen senare.</p></numerusform> <numerusform><p>Det uppstod ett fel vid uppdateringen av dina insticksprogram.</p><p>Vänligen försök igen senare.</p></numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>Insticksprogram installerat!</numerusform> <numerusform>Insticksprogram installerade!</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>Ditt insticksprogram har installerats.</p><p>Du kan nu skrobbla med din mediaspelare</p></numerusform> <numerusform><p>Dina insticksprogram har installerats.</p><p>Du kan nu skrobbla med dina mediaspelare</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Dina insticksprogram har inte installerats</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Du kan installera dem senare genom filmenyn</translation> </message> <message> <source>File</source> <translation>Fil</translation> </message> <message> <source>Install plugins</source> <translation>Installera insticksprogram</translation> </message> <message> <source>&Quit</source> <translation>&Avsluta</translation> </message> <message> <source>View</source> <translation>Visa</translation> </message> <message> <source>My Last.fm Profile</source> <translation>Min Last.fm-profil</translation> </message> <message> <source>Scrobbles</source> <translation>Skrobblingar</translation> </message> <message> <source>Refresh</source> <translation>Uppdatera</translation> </message> <message> <source>Controls</source> <translation>Kontroller</translation> </message> <message> <source>Account</source> <translation>Konto</translation> </message> <message> <source>Tools</source> <translation>Verktyg</translation> </message> <message> <source>Check for Updates</source> <translation>Kontrollera om det finns uppdateringar</translation> </message> <message> <source>Options</source> <translation>Alternativ</translation> </message> <message> <source>Window</source> <translation>Fönster</translation> </message> <message> <source>Minimize</source> <translation>Minimera</translation> </message> <message> <source>Zoom</source> <translation>Zoom</translation> </message> <message> <source>Bring All to Front</source> <translation>Ta fram alla</translation> </message> <message> <source>Help</source> <translation>Hjälp</translation> </message> <message> <source>About</source> <translation>Om</translation> </message> <message> <source>FAQ</source> <translation>FAQ</translation> </message> <message> <source>Forums</source> <translation>Forum</translation> </message> <message> <source>Tour</source> <translation>Guide</translation> </message> <message> <source>Show Licenses</source> <translation>Visa licenser</translation> </message> <message> <source>Diagnostics</source> <translation>Diagnostik</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform><a href="tracks">%n spelad låt</a> har skrobblats från enheten</numerusform> <numerusform><a href="tracks">%n spelade låtar</a> har skrobblats från enheten</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Tillbaka till skrobblingar</translation> </message> <message> <source>Popular tags:</source> <translation>Populära taggar:</translation> </message> <message> <source>Your tags:</source> <translation>Dina taggar:</translation> </message> <message> <source>Similar Artists</source> <translation>Liknande artister</translation> </message> <message> <source>by %1</source> <translation>av %1</translation> </message> <message> <source>from %1</source> <translation>från %1</translation> </message> <message> <source>Play %1 Radio</source> <translation>Spela %1-radio</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>Spelad låt</numerusform> <numerusform>Spelade låtar</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>Spelad låt i ditt bibliotek</numerusform> <numerusform>Spelade låtar i ditt bibliotek</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>Lyssnare</numerusform> <numerusform>Lyssnare</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>Med %1 och mer.</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>Med %1, %2 och mer.</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>Redigerat den %1 | %2 Redigera</translation> </message> <message> <source>Downloads</source> <translation>Nedladdningar</translation> </message> <message> <source>Search on %1</source> <translation>Sök på %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Köp på %1 %2</translation> </message> <message> <source>Physical</source> <translation>Fysiskt</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>Rekommenderat eftersom du lyssnar på %1.</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>Rekommenderat eftersom du lyssnar på %1 och %2.</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>Rekommenderat eftersom du lyssnar på %1, %2 och %3.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>Rekommenderat eftersom du lyssnar på %1, %2, %3 och %4.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>Rekommenderat eftersom du lyssnar på %1, %2, %3, %4 och %5.</translation> </message> <message> <source>From %1's library.</source> <translation>Från %1s bibliotek.</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>Från %1 och %2s bibliotek.</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 gång</numerusform> <numerusform>%L1 gånger</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>Från %1, %2 och %3s bibliotek.</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>Du har lyssnat på %1 %2 och %3 %4.</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>Från %1, %2, %3 och %4s bibliotek.</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>Du har lyssnat på %1 %2, men inte denna låt.</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>Från %1, %2, %3, %4 och %5s bibliotek.</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>Detta är första gången du lyssnar på %1.</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>Hej!</translation> </message> <message> <source>Start a radio station</source> <translation>Starta en radiostation</translation> </message> <message> <source>Open iTunes</source> <translation>Öppna iTunes</translation> </message> <message> <source>Open Music</source> <translation>Öppna Music</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Öppna Windows Media Player</translation> </message> <message> <source>Open Winamp</source> <translation>Öppna Winamp</translation> </message> <message> <source>Open Foobar</source> <translation>Öppna Foobar</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>Skrobbla från din musikspelare</h2><p>Börja lyssna på musik i din musikspelare. Du kan se mer information om låtarna du spelar under fliken Spelas nu.</p></translation> </message> <message> <source>Hello, %1!</source> <translation>Hej, %1!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>En radiostation</translation> </message> <message> <source>Play %1</source> <translation>Spela %1</translation> </message> <message> <source>Multi-Library Radio</source> <translation>Radio från flera bibliotek</translation> </message> <message> <source>Cue %1</source> <translation>Köa %1</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>Spela %1 och %2 biblioteksradio</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>Köa %1 och %2 biblioteksradio</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>Älska</translation> </message> <message> <source>Ban</source> <translation>Blockera</translation> </message> <message> <source>Play</source> <translation>Spela</translation> </message> <message> <source>Skip</source> <translation>Hoppa över</translation> </message> <message> <source>Pause</source> <translation>Pausa</translation> </message> <message> <source>Resume</source> <translation>Återuppta</translation> </message> <message> <source>Unlove</source> <translation>Sluta älska</translation> </message> <message> <source>Tuning</source> <translation>Inställning</translation> </message> <message> <source>A Radio Station</source> <translation>En radiostation</translation> </message> <message> <source>Listening to</source> <translation>Lyssnar på</translation> </message> <message> <source>Scrobbling from</source> <translation>Skrobblar från</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 spelad låt</numerusform> <numerusform>%L1 spelade låtar</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>Last.fm har importerat ditt mediabibliotek. Klicka på OK för att fortsätta.</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Last.fm-biblioteksimport</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>Är du säker på att du vill avbryta importen?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Last.fm kunde inte hitta några spelade låtar i ditt mediabibliotek. Klicka på OK för att fortsätta.</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm importerar ditt aktuella mediabibliotek ...</translation> </message> <message> <source>Where is Winamp?</source> <translation>Var är Wimamp?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Var är Windows Media Player?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>Import av mediabibliotek slutförd</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>Last.fm har skickat din lyssningshistorik till servern. Din profil kommer att uppdateras med de nya låtarna om några minuter.</translation> </message> <message> <source>Library Import Failed</source> <translation>Kunde inte importera bibliotek</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Ledsen, Last.fm kunde inte importera din lyssningshistorik. Du har förmodligen redan skrobblat för många låtar. Lyssningshistoriken kan bara importeras till nya profiler.</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>Följ instruktionerna som ditt operativsystem visar för att installera insticksprogrammen.</p><p>När insticksprogrammen har installerats på din dator, klicka på <strong>Fortsätt</strong>.</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>Dina insticksprogram installeras nu</translation> </message> <message> <source>Continue</source> <translation>Fortsätt</translation> </message> <message> <source><< Back</source> <translation><< Tillbaka</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Dina insticksprogram har inte installerats</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Du kan installera dem senare genom filmenyn</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>Dina mediaspelare behöver ett speciellt Last.fm-insticksprogram så att du kan skrobbla musiken du lyssnar på.</p><p>Vänligen välj de mediaspelare som du vill skrobbla din musik från och klicka på <strong>Installera insticksprogram</strong></p></translation> </message> <message> <source>(newer version)</source> <translation>(nyare version)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(Insticksprogram installerade, klicka för att installera om)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>Nästa steg, installera Last.fm-insticksprogrammen för att skrobbla musiken du lyssnar på.</translation> </message> <message> <source>Install Plugins</source> <translation>Installera insticksprogram</translation> </message> <message> <source>Continue</source> <translation>Fortsätt</translation> </message> <message> <source><< Back</source> <translation><< Tillbaka</translation> </message> <message> <source>Skip >></source> <translation>Hoppa över >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>Allmänt</translation> </message> <message> <source>Accounts</source> <translation>Konton</translation> </message> <message> <source>Scrobbling</source> <translation>Skrobbling</translation> </message> <message> <source>Devices</source> <translation>Enheter</translation> </message> <message> <source>Advanced</source> <translation>Avancerat</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>%1 radio</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 spelad låt</numerusform> <numerusform>%L1 spelade låtar</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>Toppartister denna vecka</translation> </message> <message> <source>Top Artists Overall</source> <translation>Toppartister generellt</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>Skrobbling</numerusform> <numerusform>Skrobblingar</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>Älskad låt</numerusform> <numerusform>Älskade låtar</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 artist</numerusform> <numerusform>%L1 artister</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 låt</numerusform> <numerusform>%L1 låtar</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>Du har %1 i ditt bibliotek och lyssnar i genomsnitt på %2 per dag.</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>Skrobbling sedan %1</numerusform> <numerusform>Skrobblingar sedan %1</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>Proxy-inställningar</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>Värd:</translation> </message> <message> <source>Username:</source> <translation>Användarnamn:</translation> </message> <message> <source>Port:</source> <translation>Port:</translation> </message> <message> <source>Password:</source> <translation>Lösenord:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>okänd mediaspelare</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>Var är din iPod placerad?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>Ange en artist eller tagg och tryck play</translation> </message> <message> <source>Play</source> <translation>Spela</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>Försök med %1, %2, %3 eller %4?</translation> </message> <message> <source>Play next</source> <translation>Spela nästa</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>En radiostation</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>Du måste vara abonnent för att lyssma på radio</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>Senaste station</translation> </message> <message> <source>A Radio Station</source> <translation>En radiostation</translation> </message> <message> <source>Personal Stations</source> <translation>Personliga stationer</translation> </message> <message> <source>My Library Radio</source> <translation>Min biblioteksradio</translation> </message> <message> <source>Music you know and love</source> <translation>Musik du känner till och älskar</translation> </message> <message> <source>My Mix Radio</source> <translation>Min blandradio</translation> </message> <message> <source>Your library plus new music</source> <translation>Ditt bibliotek plus ny musik</translation> </message> <message> <source>My Recommended Radio</source> <translation>Min rekommenderade radio</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>Abonnera för att lyssna på radio</translation> </message> <message> <source>New music from Last.fm</source> <translation>Ny musik från Last.fm</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>Du måste vara Last.fm-abonnent för att lyssna på radio i detta program. Abonnera nu för att börja lyssna och utnyttja andra bra fördelar!</translation> </message> <message> <source>Network Stations</source> <translation>Nätverksstationer</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Abonnera på Last.fm</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>Lyssna gratis på www.last.fm</translation> </message> <message> <source>My Friends' Radio</source> <translation>Mina vänners radio</translation> </message> <message> <source>Music your friends like</source> <translation>Musik dina vänner gillar</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>Min grannradio</translation> </message> <message> <source>Music from listeners like you</source> <translation>Musik från lyssnare som du</translation> </message> <message> <source>Recent Stations</source> <translation>Senaste stationer</translation> </message> <message> <source>Now Playing</source> <translation>Nu spelas</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>Abonnera för att lyssna på radio, endast %1 i månaden</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>Enhetsskrobblingar</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>Det ser ut som om du har spelat dessa låtar. Vill du skrobbla dem?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>Skrobbla enheter automatiskt</translation> </message> <message> <source>Toggle selection</source> <translation>Växla urval</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>%n spelad låt har skrobblats från en enhet </numerusform> <numerusform>%n spelade låtar har skrobblats från en enhet </numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>Älska låt</translation> </message> <message> <source>Add tags</source> <translation>Lägg till taggar</translation> </message> <message> <source>Love</source> <translation>Älska</translation> </message> <message> <source>Tag</source> <translation>Tagga</translation> </message> <message> <source>Share</source> <translation>Dela</translation> </message> <message> <source>Share on Last.fm</source> <translation>Dela på Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Dela på Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Dela på Facebook</translation> </message> <message> <source>Buy</source> <translation>Köp</translation> </message> <message> <source>Unlove track</source> <translation>Sluta älska låt</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Skrobbla vid</translation> </message> <message> <source>percent of the track</source> <translation>procent av låten</translation> </message> <message> <source>Enable scrobbling</source> <translation>Aktivera skrobbling</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>...eller vid 4 minuter (beroende på vad som kommer först)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>Skrobbla podcasts</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>Tillåt Last.fm att fingerprinta mina låtar</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>Valda kataloger kommer inte att skrobblas</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>Mer skrobblingar på Last.fm</translation> </message> <message> <source>Refreshing...</source> <translation>Uppdaterar ...</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Uppdatera skrobblingar</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>Artist</translation> </message> <message> <source>Title</source> <translation>Titel</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Plays</source> <translation>Spelade låtar</translation> </message> <message> <source>Last Played</source> <translation>Senast spelad</translation> </message> <message> <source>Loved</source> <translation>Älskad</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>Denna låt är under 30 sekunder</translation> </message> <message> <source>The artist name is missing</source> <translation>Artistnamnet saknas</translation> </message> <message> <source>Invalid track title</source> <translation>Ogiltig låttitel</translation> </message> <message> <source>Invalid artist</source> <translation>Ogiltig artist</translation> </message> <message> <source>There is no timestamp</source> <translation>Detta är inte en tidstämpel</translation> </message> <message> <source>This track is too far in the future</source> <translation>Denna låt är för långt in i framtiden</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>Denna låt spelades för över två veckor sedan</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>Du har inte skrobblat någon musik till Last.fm än.</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>Börja lyssna på musik i din mediaspelare eller starta en radiostation:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>Dela med vänner</translation> </message> <message> <source>With:</source> <translation>Med:</translation> </message> <message> <source>Message (optional):</source> <translation>Meddelande (valfritt):</translation> </message> <message> <source>include in my recent activity</source> <translation>inkludera i min senaste aktivitet</translation> </message> <message> <source>A track by %1</source> <translation>En låt av %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>En låt av %1 från utgåvan %2</translation> </message> <message> <source>Check out %1</source> <translation>Kolla in %1</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>Nu spelas</translation> </message> <message> <source>Scrobbles</source> <translation>Skrobblingar</translation> </message> <message> <source>Profile</source> <translation>Profil</translation> </message> <message> <source>Friends</source> <translation>Vänner</translation> </message> <message> <source>Radio</source> <translation>Radio</translation> </message> <message> <source>Next Section</source> <translation>Nästa sektion</translation> </message> <message> <source>Previous Section</source> <translation>Föregående sektion</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>Kunde inte starta radio: %1</translation> </message> <message> <source>no results for "%1"</source> <translation>inga resultat för "%1"</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>Skrobbling är av</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>Online</translation> </message> <message> <source>Offline</source> <translation>Offline</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>Tagg</translation> </message> <message> <source>Choose something to tag:</source> <translation>Välj något att tagga:</translation> </message> <message> <source>Track</source> <translation>Låt</translation> </message> <message> <source>Artist</source> <translation>Artist</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>icon</source> <translation>ikon</translation> </message> <message> <source>Add tags:</source> <translation>Lägg till taggar:</translation> </message> <message> <source>A track by %1</source> <translation>En låt av %1</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>En låt av %1 från utgåvan %2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>Skriv in en tagg ovan eller välj från nedanstående</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>Sortera efter popularitet</translation> </message> <message> <source>Sort Alphabetically</source> <translation>Sortera alfabetiskt</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>Öppna Last.fm-sidan för denna tagg</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>Nu kan du komma igång! Klicka bara på <strong>Avsluta</strong> och börja utforska.</p><p>Vi är också klara med importen av din lyssningshistorik och har lagt till den i din Last.fm-profil.</p><p>Tack för att du har installerat Last.fm-programmet, vi hoppas att du kommer att ha glädje av den!</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>Det var det, du är klar att börja!</translation> </message> <message> <source>Finish</source> <translation>Avsluta</translation> </message> <message> <source><< Back</source> <translation><< Tillbaka</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>Den röda pilen på din skärm visar var Last.fm-programmet finns i ditt aktivitetsfält.</p><p>Klicka på ikonen för att få snabb tillgång till radioknapparna, dela och tagga låt, redigera dina inställningar och besöka din last.fm-profil.</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>Last.fm-programmet i ditt menyfält</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>Last.fm-programmet i ditt aktivitetsfält.</translation> </message> <message> <source>Continue</source> <translation>Fortsätt</translation> </message> <message> <source><< Back</source> <translation><< Tillbaka</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>Få mer information om musiken du lyssnar på, inklusive biografier, lyssningsstatistik, foton och liknande artister samt taggar som lyssnare använder för att beskriva dem.</p><p>Kolla in fliken <strong>Spelas nu</strong> eller klicka bara på en låt på din flik <strong>Skrobblingar</strong> för mer information.</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>Läs mer om artisterna du älskar</translation> </message> <message> <source>Continue</source> <translation>Fortsätt</translation> </message> <message> <source><< Back</source> <translation><< Tillbaka</translation> </message> <message> <source>Skip Tour >></source> <translation>Hoppa över tur >></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>Lyssna på nonstop, personlig radio</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Använd Last.fm-programmet för att lyssna på personlig radio baserat på musiken du vill höra.</p><p>Alla uppspelningar av alla Last.fm-stationer är helt olika, allt från stationer baserat på artister och taggar till helt nya rekommendationer skräddarsydda efter din musiksmak.</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>Abonnera och lyssna på nonstop personlig radio</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Abonnera på Last.fm och använd Last.fm-programmet för att lyssna på personlig radio baserat på musiken du hör.</p><p>Alla uppspelningar av alla Last.fm-stationer är helt olika, allt från stationer baserat på artister och taggar till helt nya rekommendationer skräddarsydda efter din musiksmak.</p></translation> </message> <message> <source>Subscribe</source> <translation>Abonnera</translation> </message> <message> <source>Continue</source> <translation>Fortsätt</translation> </message> <message> <source><< Back</source> <translation><< Tillbaka</translation> </message> <message> <source>Skip Tour >></source> <translation>Hoppa över tur >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>Programmet körs i bakgrunden och uppdaterar tyst din Last.fm-profil i bakgrunden med musiken du spelar, vilket du kan använda för att få musikrekommendationer, spelningstips med mera. </p><p>Du kan också använda Last.fm-programmet för att få mer information om artisten du lyssnar på och spela personlig radio.</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>Välkommen till Last.fm-programmet!</translation> </message> <message> <source>Continue</source> <translation>Fortsätt</translation> </message> <message> <source><< Back</source> <translation><< Tillbaka</translation> </message> <message> <source>Skip Tour >></source> <translation>Hoppa över tur >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>Låt</translation> </message> <message> <source>Album</source> <translation>Album</translation> </message> <message> <source>Artist</source> <translation>Artist</translation> </message> <message> <source>Love</source> <translation>Älska</translation> </message> <message> <source>Tag</source> <translation>Tagga</translation> </message> <message> <source>Share</source> <translation>Dela</translation> </message> <message> <source>Buy</source> <translation>Köp</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>Radera denna skrobbling från din profil</translation> </message> <message> <source>Play %1 Radio</source> <translation>Spela %1-radio</translation> </message> <message> <source>Cue %1 Radio</source> <translation>Köa %1 radio</translation> </message> <message> <source>%1 Radio</source> <translation>%1-radio</translation> </message> <message> <source>Cached</source> <translation>Cachat</translation> </message> <message> <source>Error: %1</source> <translation>Fel: %1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Dela på Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>Dela på Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>Dela på Facebook</translation> </message> <message> <source>Now listening</source> <translation>Lyssnar nu</translation> </message> <message> <source>Downloads</source> <translation>Hämtningar</translation> </message> <message> <source>Search on %1</source> <translation>Sök på %1</translation> </message> <message> <source>Buy on %1 %2</source> <translation>Köp på %1 %2</translation> </message> <message> <source>Physical</source> <translation>Fysisk</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>Anslutna användarkonton:</translation> </message> <message> <source>Add New User Account</source> <translation>Lägg till nytt användarkonto</translation> </message> <message> <source>Add User Error</source> <translation>Lägg till användarfel</translation> </message> <message> <source>This user has already been added.</source> <translation>Användaren har redan lagts till.</translation> </message> <message> <source>Removing %1</source> <translation>Tar bort %1</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>Är du säker på att du vill ta bort denna användare? Alla användarinställningar kommer att gå förlorade och du måste ge tillstånd igen för att skrobbla i framtiden.</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>Abonnera</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>Ta bort</translation> </message> <message> <source>(currently logged in)</source> <translation>(inloggad för tillfället)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>Konton</translation> </message> <message> <source>Show Scrobbler</source> <translation>Visa skrobblare</translation> </message> <message> <source>Love</source> <translation>Älska</translation> </message> <message> <source>Play</source> <translation>Spela</translation> </message> <message> <source>Skip</source> <translation>Hoppa över</translation> </message> <message> <source>Tag</source> <translation>Tagga</translation> </message> <message> <source>Share</source> <translation>Dela</translation> </message> <message> <source>Ban</source> <translation>Blockera</translation> </message> <message> <source>Mute</source> <translation>Stäng av ljud</translation> </message> <message> <source>Scrobble iPod...</source> <translation>Skrobbla iPod ...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Besök Last.fm-profil</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Aktivera skrobbling</translation> </message> <message> <source>Quit %1</source> <translation>Avsluta %1</translation> </message> <message> <source>from %1</source> <translation>från %1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>Du har nått denna stations gräns för antalet gånger du får hoppa över en låt. Hoppa över igen om %n minut.</numerusform> <numerusform>Du har nått denna stations gräns för antalet gånger du får hoppa över en låt. Hoppa över igen om %n minuter.</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>Du får hoppa över %n låt till på denna station.</numerusform> <numerusform>Du får hoppa över %n låtar till på denna station.</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>Verifiering krävs</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>Användarkontot <strong>%1</strong> är inte längre verifierat med Last.fm.</p><p>Klicka på OK för att starta inställningsprocessen och verifiera detta konto igen.</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>Är du säker på att du vill avsluta %1?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>%1 håller på att avslutas. Låtar som spelas kommer inte att skrobblas om du fortsätter.</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>Ändra användare</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 kommer att loggas in i skrobblaren och Last.fm-radio. All musik kommer nu att skrobblas till detta konto. Vill du fortsätta?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>Stäng följande appar för att fortsätta.</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>Insticksprogram installationsfel</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>Det uppstod ett fel vid uppdateringen av ditt insticksprogram.</p><p>Försök igen senare.</p></translation> </message> <message> <source>Plugin installed!</source> <translation>Insticksprogram installerat!</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>Insticksprogrammet %1 har installerats.</p><p>Du kan nu skrobbla med %1.</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>Insticksprogrammet %1 har inte installerats</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>Du stängde inte %1 så dess insticksprogram har inte installerats.</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>Stäng iTunes för uppdatering av insticksprogrammet!</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>Ditt iTunes-insticksprogram (%2) skiljer sig från det som levereras med denna version av appen (%1).</p><p>Stäng iTunes nu för att uppdatera.</p></translation> </message> <message> <source>not installed</source> <translation>inte installerat</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>Ditt insticksprogram har inte installerats</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>Det uppstod ett fel när det gamla insticksprogrammet skulle tas bort</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>iTunes-insticksprogram installerat!</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>Ditt iTunes-insticksprogram har installerats.</p><p>Du kan nu skrobbla från enheten.</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>Det uppstod ett fel vid kopieringen av det nya insticksprogrammet</translation> </message> <message> <source>You didn't close iTunes</source> <translation>Du stängde inte iTunes</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>Tiden är avbruten</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>för %n minut sedan</numerusform> <numerusform>för %n minuter sedan</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>för %n timme sedan</numerusform> <numerusform>för %n timmar sedan</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>Det uppstod ett nätverksfel: %1</translation> </message> <message> <source>You have not authorised this application</source> <translation>Du har inte verifierat detta program</translation> </message> <message> <source>Authentication Error</source> <translation>Verifieringsfel</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>Uppdatera Stylesheet</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>Fråga inte detta igen</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>Automatisk igenkänning</translation> </message> <message> <source>No-proxy</source> <translation>Ingen proxy</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_tr.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="tr"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>Hakkında</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1 ( Qt %2 üzerinde oluşturuldu)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>Lütfen Last.fm hesabını Last.fm Masaüstü Uygulaması'na bağlamak için web tarayıcında <strong>Evet, Erişime İzin Ver</strong> düğmesine tıkla.</p><p>Tarayıcı penceresini kapattığın veya iptal seçeneğine tıkladığın için bağlanamadıysan, lütfen yeniden dene.</p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>Last.fm'e bağlanmanızı bekliyoruz</translation> </message> <message> <source><< Back</source> <translation><<Geri</translation> </message> <message> <source>Continue</source> <translation>Devam</translation> </message> <message> <source>Try Again</source> <translation>Yeniden Dene</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>Web tarayıcınız açılmadıysa, aşağıdaki bağlantıyı adres çubuğunuza kopyalayıp yapıştırın.</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>Klavye Kısayolları:</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>Last.fm'i Göster/Gizle</translation> </message> <message> <source>Proxy:</source> <translation>Proxy:</translation> </message> <message> <source>Enable SSL</source> <translation>SSL Etkinleştir</translation> </message> <message> <source>Cache Size:</source> <translation>Önbellek Boyutu:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>Abone</translation> </message> <message> <source>Moderator</source> <translation>Moderatör</translation> </message> <message> <source>Staff</source> <translation>Görevli</translation> </message> <message> <source>Alumna</source> <translation>Eski eleman</translation> </message> <message> <source>Alumnus</source> <translation>Eski eleman</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>Turnede</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>Müzik zevkine göre olası en iyi öneriler için, medya çalarından dinleme geçmişini indirmeni öneriyoruz.</p><p>Lütfen tercih ettiğin medya çaları seç ve <strong>İçe Aktarmayı Başlat</strong></p>'a tıkla.</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Eklentilerin kurulmamış</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Dosya menüsü üzerinden onları daha sonra yükleyebilirsin</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>Şimdi dinleme geçmişini içeri aktaralım</translation> </message> <message> <source>Start Import</source> <translation>İçe Aktarmayı Başlat</translation> </message> <message> <source><< Back</source> <translation>Geri</translation> </message> <message> <source>Skip >></source> <translation>Atla >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>Merak etme, karşıya yükleme süreci müzik arşivinin boyutuna bağlı olarak birkaç dakikadan daha uzun sürmeyecektir.</p><p>Biz dinleme geçmişini Last.fm profiline eklemek için çalışırken, neden sen de Last.fm Masaüstü Uygulaması'nın ana özelliklerine göz atmıyorsun? Bir tur atmak için <strong>Devam</strong>'a tıkla.</translation> </message> <message> <source>Continue</source> <translation>Devam</translation> </message> <message> <source><< Back</source> <translation>Geri</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>Uygulamaları Kapat</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>Cihaz skroplama devre dışı - uyumsuz iTunes eklentisi - %1</translation> </message> <message> <source>please update</source> <translation>lütfen güncelleştirin</translation> </message> <message> <source>Scrobble iPod</source> <translation>iPod'u skropla</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>%1 cihazının audioscrobbler kullanıcı hesabınla ilişkilendirilmesini istiyor musun?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>Cihaz başarıyla kullanıcı hesabınla ilişkilendirildi. Şu andan itibaren bu cihazda dinlediğiniz parçaları skroplayabilirsin.</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>%1 parça skroplandı.</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>Son senkronizasyondan bu yana skroplanacak parça yok.</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>iPod veritabanı açılamadı.</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>iPod veritabanına ulaşmaya çalışırken istenmeyen bir hata oluştu.</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>Tanı</translation> </message> <message> <source>Scrobbling</source> <translation>Skroplama</translation> </message> <message> <source>This is an easter egg!</source> <translation>Bu bir paskalya yumurtası!</translation> </message> <message> <source>Artist</source> <translation>Sanatçı</translation> </message> <message> <source>Track</source> <translation>Parça</translation> </message> <message> <source>Album</source> <translation>Albüm</translation> </message> <message> <source>Fingerprinting</source> <translation>Parmak İzi Kontrolü</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>Son Parmak İzi Kontrolü Yapılmış Parçalar</translation> </message> <message> <source>iPod Scrobbling</source> <translation>iPod Skroplaması</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>iTunes iPod'umu otomatik olarak yönetiyor</translation> </message> <message> <source>I manually manage my iPod</source> <translation>iPod'umu manuel olarak yönetiyorum</translation> </message> <message> <source>Scrobble iPod</source> <translation>iPod'u skropla</translation> </message> <message> <source>Logs</source> <translation>Kayıtlar</translation> </message> <message> <source>&Close</source> <translation>&Kapat</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n yerel önbellekteki parça</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>Last.fm Masaüstü Uygulaması</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>Teşekkürler <strong>%1</strong>, hesabın artık bağlandı!</translation> </message> <message> <source>Importing...</source> <translation>İçe aktarılıyor...</translation> </message> <message> <source>Import complete!</source> <translation>İçe aktarma tamamlandı!</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>Last.fm'de arkadaşlarını bul</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>Henüz Last.fm'de hiç arkadaşınız yok.</h3><p>Arkadaş bulucu ile Last.fm kullanan Facebook arkadaşlarını ve e-posta bağlantılarını kolayca ve hızla bul.<p /></p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>Kullanıcı adı veya gerçek adıyla bir arkadaşını ara</translation> </message> <message> <source>Refresh Friends</source> <translation>Arkadaşları Yenile</translation> </message> <message> <source>Refreshing...</source> <translation>Yenileniyor...</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>%1 Kişisel Radyosu</translation> </message> <message> <source>Male</source> <translation>Erkek</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>Şimdi %1 üzerinden skroplanıyor</translation> </message> <message> <source>Female</source> <translation>Kadın</translation> </message> <message> <source>Scrobbling now</source> <translation>Şimdi skroplanıyor</translation> </message> <message> <source>Neuter</source> <translation>Cinsiyetsiz</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>Arkadaşlarını ara</translation> </message> <message> <source>Browse Friends</source> <translation>Arkadaşlarına Göz At</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>Dil:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>Uygulama simgesini menü çubuğunda göster</translation> </message> <message> <source>Launch application with media players</source> <translation>Uygulamayı medya çalarlarla başlat</translation> </message> <message> <source>Show dock icon</source> <translation>Yuva simgesini göster</translation> </message> <message> <source>Show desktop notifications</source> <translation>Masaüstü bildirimlerini göster</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>Hata raporlarını Last.fm'e gönder</translation> </message> <message> <source>Check for updates automatically</source> <translation>Güncellemeleri otomatik olarak denetle</translation> </message> <message> <source>Enable media keys</source> <translation>Medya tuşlarını etkinleştir</translation> </message> <message> <source>System Language</source> <translation>Sistem Dili</translation> </message> <message> <source>Restart now?</source> <translation>Şİmdi yeniden başlatmak istiyor musun?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>Değişikliğin etkili olması için uygulamanın yeniden başlatılması gerekiyor. Şimdi yeniden başlatmak istiyor musun?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>Beta sürümlerine yükselt - Uyarı: Yalnızca cesurlar için!</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>iPod veritabanı açılamadı.</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>%1 gibi bir iOS skroplama uygulamasını kullanmak çifte skroplamaya neden olabilir. Lütfen bunlardan yalnızca birinde skroplamayı etkinleştirin.</p><p>iTunes Match çalış sayısını senkronize eder ancak çok sayıda cihaz arasındaki son çalınmaları kaydetmez. Bu yanlış saatlerde ikili skroplamalara neden olur. Şimdilik, iTunes Match kullanıcılarına masaüstü cihazlarda cihaz skroplamasını devre dışı bırakmalarını ve %1 gibi bir iOS skroplama uygulaması kullanarak iPhone/iPod'larını skroplamalarını öneriyoruz.</p></translation> </message> <message> <source>Setting not changed</source> <translation>Ayarlar değiştirilmedi</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>Bu ayarın değişmesi için iTunes'u kapatmadın</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>Cihaz Skroplamayı Etkinleştir</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>Cihaz Skroplamalarını Onayla</translation> </message> <message> <source>Please note</source> <translation>Lütfen dikkat</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>Lisanslar</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>Tamam mı?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>Bu uygulamayı onayladığında Tamam'a bas.</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Last.fm'in önce senin iznine ihtiyacı var!</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>Bu uygulamanın Last.fm profiline bağlanmak için senin iznine ihtiyacı var. Last.fm web sitesine gitmek için Tamam'a tıkla ve bunu yap.</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>Zaten bir Last.fm kullanıcısı mısın? Hesabını Masaüstü Uygulaması ile bağlarsan profilini dinlediğin müziğe göre güncelleyecektir.</p><p>Hesabın yoksa, şimdi ücretsiz kayıt olabilirsin.</translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>Last.fm hesabını bağlamakla başlayalım</translation> </message> <message> <source>Connect Your Account</source> <translation>Hesabını Bağla</translation> </message> <message> <source>Sign up</source> <translation>Kayıt Ol</translation> </message> <message> <source>Proxy?</source> <translation>Proxy?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>Medya çalar eklentilerin için güncellemeler var. Onları şimdi indirmek ister misin?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>Eklenti yükleme hatası</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>Eklenti(ler)inizi yüklerken bir hata oluştu.</p>Lütfen daha sonra tekrar deneyin.</p></numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>Eklenti(ler) yüklendi!</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>Eklentiniz yüklendi.</p><p>Artık medya oynatıcınızla skroplamaya hazırsınız</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Eklentilerin kurulmamış</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Dosya menüsü üzerinden onları daha sonra yükleyebilirsin</translation> </message> <message> <source>File</source> <translation>Dosya</translation> </message> <message> <source>Install plugins</source> <translation>Eklentileri yükle</translation> </message> <message> <source>&Quit</source> <translation>&Çık</translation> </message> <message> <source>View</source> <translation>Görüntüle</translation> </message> <message> <source>My Last.fm Profile</source> <translation>Last.fm Profilim</translation> </message> <message> <source>Scrobbles</source> <translation>Skroplamalar</translation> </message> <message> <source>Refresh</source> <translation>Yenile</translation> </message> <message> <source>Controls</source> <translation>Denetimler</translation> </message> <message> <source>Account</source> <translation>Hesap</translation> </message> <message> <source>Tools</source> <translation>Araçlar</translation> </message> <message> <source>Check for Updates</source> <translation>Güncellemeleri Denetle</translation> </message> <message> <source>Options</source> <translation>Tercihler</translation> </message> <message> <source>Window</source> <translation>Pencere</translation> </message> <message> <source>Minimize</source> <translation>Küçült</translation> </message> <message> <source>Zoom</source> <translation>Yakınlaştır</translation> </message> <message> <source>Bring All to Front</source> <translation>Tümünü Öne Getir</translation> </message> <message> <source>Help</source> <translation>Yardım</translation> </message> <message> <source>About</source> <translation>Hakkında</translation> </message> <message> <source>FAQ</source> <translation>SSS</translation> </message> <message> <source>Forums</source> <translation>Forumlar</translation> </message> <message> <source>Tour</source> <translation>Tur</translation> </message> <message> <source>Show Licenses</source> <translation>Lisansları Göster</translation> </message> <message> <source>Diagnostics</source> <translation>Tanı</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform><a href="tracks">%n çalış</a> bir cihazdan skroplandı.</numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>Skroplamalara Geri Dön</translation> </message> <message> <source>Popular tags:</source> <translation>Popüler etiketler:</translation> </message> <message> <source>Your tags:</source> <translation>Etiketlerin</translation> </message> <message> <source>Similar Artists</source> <translation>Benzer Sanatçılar</translation> </message> <message> <source>by %1</source> <translation>%1 tarafından</translation> </message> <message> <source>from %1</source> <translation>%1 üzerinden</translation> </message> <message> <source>Play %1 Radio</source> <translation>%1 Radyosunu Çal</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>Çalış</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>Arşivindeki çalış</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>Dinleyici</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>%1 veya daha fazlası ile.</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>%1, %2 ve daha fazlası ile.</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>%1 tarihinde düzenlendi | %2 Düzenleme</translation> </message> <message> <source>Downloads</source> <translation>İndirilenler</translation> </message> <message> <source>Search on %1</source> <translation>%1 üzerinde ara</translation> </message> <message> <source>Buy on %1 %2</source> <translation>%1 %2 üzerinden satın al</translation> </message> <message> <source>Physical</source> <translation>Fiziksel</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>%1 dinlediğin için önerildi.</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>%1 ve %2 dinlediğiniz için önerildi.</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>%1, %2 ve %3 dinlediğiniz için önerildi.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>%1, %2, %3 ve %4 dinlediğiniz için önerildi.</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>%1, %2, %3, %4 ve %5 dinlediğiniz için önerildi.</translation> </message> <message> <source>From %1's library.</source> <translation>%1 arşivinden.</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>%1 ve %2 arşivlerinden.</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 kere</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>%1, %2 ve %3 arşivlerinden.</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>%1 %2 ve %3 %4 dinledin.</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>%1, %2, %3 ve %4 arşivlerinden.</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>%1 %2 dinledin ama bu parçayı dinlemedin.</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>%1, %2, %3, %4 ve %5 arşivlerinden.</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>İlk defa %1 dinliyorsun.</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>Merhaba!</translation> </message> <message> <source>Start a radio station</source> <translation>Bir radyo istasyonu başlat</translation> </message> <message> <source>Open iTunes</source> <translation>iTunes'u Aç</translation> </message> <message> <source>Open Windows Media Player</source> <translation>Windows Media Player'ı Aç</translation> </message> <message> <source>Open Winamp</source> <translation>Winamp'ı Aç</translation> </message> <message> <source>Open Foobar</source> <translation>Foobar'ı Aç</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>Müzik çalarından skropla</h2><p>Medya çalarında bazı müzikleri dinlemeye başla. Şimdi Çalıyor sekmesinde çaldığın parçalarla ilgili daha fazla bilgi bulabilirsin.</p></translation> </message> <message> <source>Hello, %1!</source> <translation>Merhaba, %1!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>Radyo İstasyonu</translation> </message> <message> <source>Play %1</source> <translation>%1 Çal</translation> </message> <message> <source>Multi-Library Radio</source> <translation>Çok Arşivli Radyo</translation> </message> <message> <source>Cue %1</source> <translation>İşaret %1</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>%1 ve %2 Arşiv Radyosunu Çal</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>%1 ve %2 Arşiv Radyosunu İşaretle</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>Sev</translation> </message> <message> <source>Ban</source> <translation>Engelle</translation> </message> <message> <source>Play</source> <translation>Çal</translation> </message> <message> <source>Skip</source> <translation>Atla</translation> </message> <message> <source>Pause</source> <translation>Duraklat</translation> </message> <message> <source>Resume</source> <translation>Devam et</translation> </message> <message> <source>Unlove</source> <translation>Sevmekten Vazgeç</translation> </message> <message> <source>Tuning</source> <translation>Bağlanıyor</translation> </message> <message> <source>A Radio Station</source> <translation>Radyo İstasyonu</translation> </message> <message> <source>Listening to</source> <translation>Dinliyor</translation> </message> <message> <source>Scrobbling from</source> <translation>Skropluyor</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 çalış</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>Last.fm medya arşivini aktardı. Devam etmek için Tamam'a tıkla.</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Last.fm Kütüphane Aktarımı</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>İçe aktarma işlemini iptal etmek istediğinden emin misin?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Last.fm arşivinde çalınmış parça bulamadı. Devam etmek için Tamam'a tıkla.</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm güncel medya arşivini içe aktarıyor...</translation> </message> <message> <source>Where is Winamp?</source> <translation>Winamp nerede?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Windows Media Player nerede?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>Medya Arşivi Aktarımı Tamamlandı</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>Last.fm dinleme geçmişini sunucuya iletti. Profilin, yeni parçalarla birkaç dakika içinde güncellenecek.</translation> </message> <message> <source>Library Import Failed</source> <translation>Kütüphane Aktarımı Başarısız</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>Üzgünüz, Last.fm dinleme geçmişini aktaramadı. Bunun nedeni büyük olasılıkla bugüne kadar zaten çok parça skroplamış olman. Dinleme geçmişi sadece yeni profillere aktarılabilir.</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>Lütfen işletim sisteminizde eklentileri yüklemek için görünen talimatları izleyin.</p><p>Eklentiler bilgisayarınıza yüklendikten sonra <strong>Devam</strong>'a tıklayın.</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>Eklentileriniz şu anda yükleniyor</translation> </message> <message> <source>Continue</source> <translation>Devam</translation> </message> <message> <source><< Back</source> <translation>Geri</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>Eklentilerin kurulmamış</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>Dosya menüsü üzerinden onları daha sonra yükleyebilirsin</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>Medya çalarlarının, dinlediğin müziği skroplamak için özel bir Last.fm eklentisine ihtiyacı var.</p><p>Lütfen müziğinin skroplanmasını istediğin medya çaları seç ve <strong>Eklentileri Yükle</strong>'ye tıkla</p></translation> </message> <message> <source>(newer version)</source> <translation>(daha yeni sürüm)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(Yeniden yüklemek için eklenti yüklendi işareti)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>Bir sonraki adımda, dinlediğin müziği skroplayabilmek için Last.fm eklentilerini yükle.</translation> </message> <message> <source>Install Plugins</source> <translation>Eklentileri Yükle</translation> </message> <message> <source>Continue</source> <translation>Devam</translation> </message> <message> <source><< Back</source> <translation>Geri</translation> </message> <message> <source>Skip >></source> <translation>Atla >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>Genel</translation> </message> <message> <source>Accounts</source> <translation>Hesaplar</translation> </message> <message> <source>Scrobbling</source> <translation>Skroplama</translation> </message> <message> <source>Devices</source> <translation>Cihazlar</translation> </message> <message> <source>Advanced</source> <translation>Gelişmiş</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>%1 Radyosu</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 çalış</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>Bu Haftanın En Sevilen Sanatçıları</translation> </message> <message> <source>Top Artists Overall</source> <translation>Genelde En Sevilen Sanatçılar</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>Skroplama</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>Sevilen parçalar</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 sanatçı</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 parça</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>Arşivinde %1 var ve her gün ortalama %2 dinliyorsun.</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>%1 tarihinden bu yana skroplamalar</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>Proxy Ayarları</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>Ana Bilgisayar:</translation> </message> <message> <source>Username:</source> <translation>Kullanıcı adı:</translation> </message> <message> <source>Port:</source> <translation>Port:</translation> </message> <message> <source>Password:</source> <translation>Parola:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>bilinmeyen medya çalar</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>iPod'un nereye bağlı?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>Sanatçı veya etiket yaz ve çal'a tıkla</translation> </message> <message> <source>Play</source> <translation>Çal</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>%1, %2, %3 veya %4'ü neden denemiyorsun?</translation> </message> <message> <source>Play next</source> <translation>Bir sonrakini çal</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>Radyo İstasyonu</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>Radyo dinlemek için abone olman lazım</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>Son İstasyon</translation> </message> <message> <source>A Radio Station</source> <translation>Radyo İstasyonu</translation> </message> <message> <source>Personal Stations</source> <translation>Kişisel İstasyonlar</translation> </message> <message> <source>My Library Radio</source> <translation>Arşiv Radyom</translation> </message> <message> <source>Music you know and love</source> <translation>Bildiğin ve sevdiğin müzik</translation> </message> <message> <source>My Mix Radio</source> <translation>Karışık Radyom</translation> </message> <message> <source>Your library plus new music</source> <translation>Arşivin artı yeni müzik</translation> </message> <message> <source>My Recommended Radio</source> <translation>Önerilen Radyom</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>Radyo dinlemek için abone ol</translation> </message> <message> <source>New music from Last.fm</source> <translation>Last.fm'den yeni müzik</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>Bu uygulamada radyo dinlemek için Last.fm abonesi olman gerekir. Dinlemeye başlamak için şimdi abone ol ve diğer muhteşem imkanlardan da yararlan!</translation> </message> <message> <source>Network Stations</source> <translation>Ağ İstasyonları</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>Last.fm'e abone ol</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>www.last.fm sitesinde ücretsiz dinle</translation> </message> <message> <source>My Friends' Radio</source> <translation>Arkadaşlarımın Radyosu</translation> </message> <message> <source>Music your friends like</source> <translation>Arkadaşlarının sevdiği müzik</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>Komşumun Radyosu</translation> </message> <message> <source>Music from listeners like you</source> <translation>Sana benzeren dinleyicilerin müzikleri</translation> </message> <message> <source>Recent Stations</source> <translation>Son İstasyonlar</translation> </message> <message> <source>Now Playing</source> <translation>Çalıyor</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>Ayda yalnızca %1 karşılığında radyo dinlemek için abone ol</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>Cihaz Skroplamaları</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>Bu parçaları çalmış gibi görünüyorsun. Onları skroplamak ister misin?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>Cihazları otomatik olarak skropla</translation> </message> <message> <source>Toggle selection</source> <translation>Seçimi değiştir</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>Bir cihazdan %n çalış skroplandı</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>Parçayı sev</translation> </message> <message> <source>Add tags</source> <translation>Etiket ekle</translation> </message> <message> <source>Love</source> <translation>Sev</translation> </message> <message> <source>Tag</source> <translation>Etiket</translation> </message> <message> <source>Share</source> <translation>Paylaş</translation> </message> <message> <source>Share on Last.fm</source> <translation>Last.fm'de paylaş</translation> </message> <message> <source>Share on Twitter</source> <translation>Twitter'da paylaş</translation> </message> <message> <source>Share on Facebook</source> <translation>Facebook'ta paylaş</translation> </message> <message> <source>Buy</source> <translation>Satın al</translation> </message> <message> <source>Unlove track</source> <translation>Parçayı Sevmekten Vazgeç</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>Şurada skropla:</translation> </message> <message> <source>percent of the track</source> <translation>parçanın yüzdesi</translation> </message> <message> <source>Enable scrobbling</source> <translation>Skroplamayı etkinleştir</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>... veya 4 dakikada (hangisi önce gelirse)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>Podcast'ları skropla</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>Last.fm'in parçalarıma parmak izi kontrolü yapmasına izin ver</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>Seçili dizinler skroplanmayacak</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>Last.fm'de daha fazla skroplama</translation> </message> <message> <source>Refreshing...</source> <translation>Yenileniyor...</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>Skroplamaları Yenile</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>Sanatçı</translation> </message> <message> <source>Title</source> <translation>Başlık</translation> </message> <message> <source>Album</source> <translation>Albüm</translation> </message> <message> <source>Plays</source> <translation>Çalış</translation> </message> <message> <source>Last Played</source> <translation>Son Çalınma</translation> </message> <message> <source>Loved</source> <translation>Sevildi</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>Bu parça 30 saniyeden kısa</translation> </message> <message> <source>The artist name is missing</source> <translation>Sanatçı adı eksik</translation> </message> <message> <source>Invalid track title</source> <translation>Geçersiz parça başlığı</translation> </message> <message> <source>Invalid artist</source> <translation>Geçersiz sanatçı</translation> </message> <message> <source>There is no timestamp</source> <translation>Zaman etiketi yok</translation> </message> <message> <source>This track is too far in the future</source> <translation>Parça çok uzak gelecekte</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>Bu parça iki haftadan önce çalınmış</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>Last.fm'de henüz hiçbir müziği skroplamadın.</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>Medya çalarında biraz müzik dinlemeye başla veya bir radyo istasyonu başlat:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>Arkadaşlarınla Paylaş</translation> </message> <message> <source>With:</source> <translation>Birlikte:</translation> </message> <message> <source>Message (optional):</source> <translation>Mesaj (isteğe bağlı):</translation> </message> <message> <source>include in my recent activity</source> <translation>son etkinliklerimi dahil et</translation> </message> <message> <source>A track by %1</source> <translation>%1 tarafından bir parça</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>%2 yayımından %1 tarafından bir parça</translation> </message> <message> <source>Check out %1</source> <translation>%1 öğesine göz at</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>Çalıyor</translation> </message> <message> <source>Scrobbles</source> <translation>Skroplamalar</translation> </message> <message> <source>Profile</source> <translation>Profil</translation> </message> <message> <source>Friends</source> <translation>Arkadaşlar</translation> </message> <message> <source>Radio</source> <translation>Radyo</translation> </message> <message> <source>Next Section</source> <translation>Sonraki Bölüm</translation> </message> <message> <source>Previous Section</source> <translation>Önceki Bölüm</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>%1 radyosu başlatılamadı</translation> </message> <message> <source>no results for "%1"</source> <translation>"%1" için sonuç yok</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>Skroplama kapalı</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>Çevrimiçi</translation> </message> <message> <source>Offline</source> <translation>Çevrimdışı</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>Etiket</translation> </message> <message> <source>Choose something to tag:</source> <translation>Etiketlemek için bir şey seç:</translation> </message> <message> <source>Track</source> <translation>İz</translation> </message> <message> <source>Artist</source> <translation>Sanatçı</translation> </message> <message> <source>Album</source> <translation>Albüm</translation> </message> <message> <source>icon</source> <translation>simge</translation> </message> <message> <source>Add tags:</source> <translation>Etiket ekle:</translation> </message> <message> <source>A track by %1</source> <translation>%1 tarafından bir parça</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>%2 yayımından %1 tarafından bir parça</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>Yukarıya bir etiket yaz. veya aşağıdan seç</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>Popülerliğe Göre Sırala</translation> </message> <message> <source>Sort Alphabetically</source> <translation>Alfabetik Olarak Sırala</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>Bu Etiketin Last.fm Sayfasını Aç</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>Artık başlamaya hazırsın! Sadece <strong>Bitir</strong>'e tıkla ve keşfetmeye başla.</p><p>Dinleme geçmişini içe aktarmayı da başardık ve Last.fm profiline ekledik.</p><p>Last.fm Masaüstü Uygulamasını yüklediğin için teşekkürler, umarız kullanmaktan zevk alırsın!</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>Bu kadar, başlamaya hazırsın!</translation> </message> <message> <source>Finish</source> <translation>Son</translation> </message> <message> <source><< Back</source> <translation>Geri</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>Ekranındaki kırmızı ok, sistem tepsindeki Last.fm Masaüstü Uygulaması'nın yerini işaret eder.</p><p>Radyo çalma denetimlerine erişmek, parça paylaşmak ve etiketlemek, tercihlerini düzenlemek ve Last.fm profilini ziyaret etmek için simgeye tıkla.</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>Menü çubuğundaki Last.fm Masaüstü Uygulaması</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>Sistem tepsindeki Last.fm Masaüstü Uygulaması</translation> </message> <message> <source>Continue</source> <translation>Devam</translation> </message> <message> <source><< Back</source> <translation>Geri</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>Biyografiler, dinleme istatistikleri, fotoğraflar, benzer sanatçılar ve aynı zamanda dinleyicilerin onları açıklamak için kullandığı etiketler de dahil olmak üzere dinlediğin müzik hakkında daha fazla bilgi edin.</p><p><strong>Şimdi Çalıyor</strong> sekmesini kontrol et veya daha fazla bilgi için <strong>Skroplamalar</strong> sekmesindeki herhangi bir parçaya tıkla.</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>Sevdiğin daha fazla sanatçıyı keşfet</translation> </message> <message> <source>Continue</source> <translation>Devam</translation> </message> <message> <source><< Back</source> <translation>Geri</translation> </message> <message> <source>Skip Tour >></source> <translation>Turu Atla >></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>Kesintisiz, kişiselleştirilmiş radyo dinle</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Duymak istediğiniz müziği esas alan kişiselleştirilmiş radyo dinlemek için Last.fm Masaüstü Uygulaması'nı kullanın.</p><p>Last.fm istasyonunun her çalışı, sanatçılara ve etiketlere dayalı istasyonlardan müzik zevkinize uygun yepyeni önerileri bulunan istasyonlara kadar bambaşkadır.</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>Abone olun ve kesintisiz, kişiselleştirilmiş radyo dinleyin</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>Last.fm'e abone olun ve duymak istediğiniz müziği esas alan kişiselleştirilmiş radyoyu dinlemek için Last.fm Masaüstü Uygulaması'nı kullanın.</p><p>Her bir Last.fm istasyonunun tüm çalışları, sanatçılara ve etiketlere dayalı istasyonlardan müzik zevkinize uygun yepyeni önerileri bulunan istasyonlara kadar bambaşkadır.</p></translation> </message> <message> <source>Subscribe</source> <translation>Abone ol</translation> </message> <message> <source>Continue</source> <translation>Devam</translation> </message> <message> <source><< Back</source> <translation>Geri</translation> </message> <message> <source>Skip Tour >></source> <translation>Turu Atla >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>Masaüstü istemcisi; müzik önerileri, konser ipuçları ve daha fazlasını öğrenmek için kullanabileceğin dinlediğin müzikle Last.fm profilini sessizce güncelleyerek arka planda çalışır.</p><p>Last.fm masaüstü uygulamasını, dinlediğin sanatçılar hakkında daha fazla bilgi edinmek ve kişiselleştirilmiş radyonu çalmak için de kullanabilirsin.</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>Last.fm Masaüstü Uygulamasına hoş geldin!</translation> </message> <message> <source>Continue</source> <translation>Devam</translation> </message> <message> <source><< Back</source> <translation><<Geri</translation> </message> <message> <source>Skip Tour >></source> <translation>Turu Atla >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>İz</translation> </message> <message> <source>Album</source> <translation>Albüm</translation> </message> <message> <source>Artist</source> <translation>Sanatçı</translation> </message> <message> <source>Love</source> <translation>Sev</translation> </message> <message> <source>Tag</source> <translation>Etiket</translation> </message> <message> <source>Share</source> <translation>Paylaş</translation> </message> <message> <source>Buy</source> <translation>Satın al</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>Bu skroplamayı profilinden sil</translation> </message> <message> <source>Play %1 Radio</source> <translation>%1 Radyosunu Çal</translation> </message> <message> <source>Cue %1 Radio</source> <translation>İpucu %1 Radyosu</translation> </message> <message> <source>%1 Radio</source> <translation>%1 Radyosu</translation> </message> <message> <source>Cached</source> <translation>Önbellekte</translation> </message> <message> <source>Error: %1</source> <translation>Hata: %1</translation> </message> <message> <source>Share on Last.fm</source> <translation>Last.fm'de paylaş</translation> </message> <message> <source>Share on Twitter</source> <translation>Twitter'da paylaş</translation> </message> <message> <source>Share on Facebook</source> <translation>Facebook'ta paylaş</translation> </message> <message> <source>Now listening</source> <translation>Şimdi Dinleniyor</translation> </message> <message> <source>Downloads</source> <translation>İndirilenler</translation> </message> <message> <source>Search on %1</source> <translation>%1 üzerinde ara</translation> </message> <message> <source>Buy on %1 %2</source> <translation>%1 %2 üzerinden satın al</translation> </message> <message> <source>Physical</source> <translation>Fiziksel</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>Bağlı Kullanıcı Hesapları:</translation> </message> <message> <source>Add New User Account</source> <translation>Yeni Kullanıcı Hesabı Ekle</translation> </message> <message> <source>Add User Error</source> <translation>Kullanıcı Hatası Ekle</translation> </message> <message> <source>This user has already been added.</source> <translation>Bu kullanıcı zaten eklendi.</translation> </message> <message> <source>Removing %1</source> <translation>%1 Kaldırılıyor</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>Bu kullanıcıyı kaldırmak istediğinden emin misin? Tüm kullanıcı ayarları kaybolacak ve gelecekte skroplama yapabilmek için yeniden kimlik doğrulaması yapman gerekecek.</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>Abone ol</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>Kaldır</translation> </message> <message> <source>(currently logged in)</source> <translation>(şu anda oturum açık)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>Hesaplar</translation> </message> <message> <source>Show Scrobbler</source> <translation>Skroplayıcıyı Göster</translation> </message> <message> <source>Love</source> <translation>Sev</translation> </message> <message> <source>Play</source> <translation>Çal</translation> </message> <message> <source>Skip</source> <translation>Atla</translation> </message> <message> <source>Tag</source> <translation>Etiket</translation> </message> <message> <source>Share</source> <translation>Paylaş</translation> </message> <message> <source>Ban</source> <translation>Engelle</translation> </message> <message> <source>Mute</source> <translation>Sessiz</translation> </message> <message> <source>Scrobble iPod...</source> <translation>iPod'u Skropla...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>Last.fm profilini ziyaret et</translation> </message> <message> <source>Enable Scrobbling</source> <translation>Skroplamayı Etkinleştir</translation> </message> <message> <source>Quit %1</source> <translation>%1 Uygulamasından Çık</translation> </message> <message> <source>from %1</source> <translation>%1 üzerinden</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>Bu istasyonun atlama sınırına eriştin. %n dakika içinde yeniden atla.</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>Bu istasyon için %n atlama kaldı.</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>Kimlik Denetimi Gerekli</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>Kullanıcı hesabı<strong>%1</strong> artık Last.fm'de izinli değil.</p><p>Kurulum işlemine başlamak için Tamam'a tıklayın ve bu hesabı yeniden yetkilendirin.</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>%1 uygulamasından çıkmak istediğine emin misin?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>%1 uygulamasından çıkılmak üzere. Devam edersen çalınan parçalar skroplanmayacak.</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>Kullanıcı Değiştiriliyor</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 Skroplayıcıda ve Last.fm Radyosunda oturum açacak. Tüm müzikler artık bu hesaba skroplanacak. Devam etmek istiyor musun?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>Lütfen devam etmek için aşağıdaki uygulamaları kapatın.</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>Eklenti yükleme hatası</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>Eklentinizi güncellerken bir hata oluştu.</p><p>Lütfen daha sonra tekrar deneyin.</p></translation> </message> <message> <source>Plugin installed!</source> <translation>Eklenti yüklendi!</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>%1 eklentisi yüklendi.</p><p>Artık %1 ile skroplamaya hazırsınız.</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>%1 eklentisi yüklenmedi</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>%1 öğesini kapatmadığından eklentisi yüklenmedi.</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>Eklentiyi güncellemek için iTunes'u kapatın!</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>iTunes eklentin (%2), uygulamanın bu sürümüyle gönderilenden (%1) farklı.</p><p>Lütfen güncelleştirmek için iTunes'u şimdi kapatın.</p></translation> </message> <message> <source>not installed</source> <translation>kurulu değil</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>Eklentiniz kurulmamış</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>Eski eklentiyi kaldırırken bir hata oluştu</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>iTunes Eklentisi yüklendi!</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>iTunes eklentin yüklendi.</p><p>Cihazınızda skroplamaya hazırsınız.</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>Yeni eklentiyi yerine kopyalarken bir hata oluştu</translation> </message> <message> <source>You didn't close iTunes</source> <translation>iTunes'u kapatmadınız</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>Süre kesildi</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>%n dakika önce</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>%n saat önce</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>Ağ hatası oluştu: %1</translation> </message> <message> <source>You have not authorised this application</source> <translation>Bu uygulamaya izin vermedin</translation> </message> <message> <source>Authentication Error</source> <translation>Yetkilendirme hatası</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>Stil Sayfasını Yenile</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>Bunu bir daha sorma</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>Otomatik algıla</translation> </message> <message> <source>No-proxy</source> <translation>Proxy yok</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: i18n/lastfm_zh_CN.ts ================================================ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="zh-CN"> <context> <name>AboutDialog</name> <message> <source>About</source> <translation>关于</translation> </message> <message> <source>%1 (built on Qt %2)</source> <translation>%1(基于 Qt %2 制作)</translation> </message> </context> <context> <name>AccessPage</name> <message> <source><p>Please click the <strong>Yes, Allow Access</strong> button in your web browser to connect your Last.fm account to the Last.fm Desktop App.</p><p>If you haven't connected because you closed the browser window or you clicked cancel, please try again.</p></source> <translation><p>请点击浏览器中的<strong>允许访问</strong>按钮,将您的 Last.fm 帐户连接到 Last.fm 桌面程序。</p><p>如果您已关闭浏览器窗口或点击取消而未能连接,请再试一次。<p /></p></translation> </message> <message> <source>We're waiting for you to connect to Last.fm</source> <translation>我们正等待您连接到 Last.fm</translation> </message> <message> <source><< Back</source> <translation> << 返回</translation> </message> <message> <source>Continue</source> <translation>继续</translation> </message> <message> <source>Try Again</source> <translation>再试一次</translation> </message> <message> <source><p>If your web browser didn't open, copy and paste the link below into your address bar.</p></source> <translation><p>如果浏览器未启动,请复制以下链接并将其粘贴到浏览器地址栏。</p></translation> </message> </context> <context> <name>AdvancedSettingsWidget</name> <message> <source>Keyboard Shortcuts:</source> <translation>键盘快捷方式:</translation> </message> <message> <source>Raise/Hide Last.fm</source> <translation>显示/隐藏 Last.fm</translation> </message> <message> <source>Proxy:</source> <translation>代理:</translation> </message> <message> <source>Enable SSL</source> <translation>启用 SSL</translation> </message> <message> <source>Cache Size:</source> <translation>缓存大小:</translation> </message> </context> <context> <name>AvatarWidget</name> <message> <source>Subscriber</source> <translation>付费用户</translation> </message> <message> <source>Moderator</source> <translation>管理员</translation> </message> <message> <source>Staff</source> <translation>员工</translation> </message> <message> <source>Alumna</source> <translation>老友</translation> </message> <message> <source>Alumnus</source> <translation>老友</translation> </message> </context> <context> <name>BioWidget</name> <message> <source>On Tour</source> <translation>巡演中</translation> </message> </context> <context> <name>BootstrapPage</name> <message> <source><p>For the best possible recommendations based on your music taste we advise that you import your listening history from your media player.</p><p>Please select your preferred media player and click <strong>Start Import</strong></p></source> <translation><p>要获得最适合您音乐品味的推荐,建议您从媒体播放器导入收听历史。</p><p>请选择您最常用的媒体播放器,然后点击<strong>开始导入</strong></p></translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>插件尚未安装</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>稍后可从文件菜单安装插件</translation> </message> <message> <source>iTunes</source> <translation>iTunes</translation> </message> <message> <source>Now let's import your listening history</source> <translation>现在导入收听历史</translation> </message> <message> <source>Start Import</source> <translation>开始导入</translation> </message> <message> <source><< Back</source> <translation><< 返回</translation> </message> <message> <source>Skip >></source> <translation>跳过 >></translation> </message> </context> <context> <name>BootstrapProgressPage</name> <message> <source><p>Don't worry, the upload process shouldn't take more than a couple of minutes, depending on the size of your music library.</p><p>While we're hard at work adding your listening history to your Last.fm profile, why don't you check out the main features of the Last.fm Desktop App. Click <strong>Continue</strong> to take the tour.</p></source> <translation><p>别担心,上传过程至多只需要几分钟,具体取决于您的音乐库大小,。</p><p>趁我们忙着将收听历史添加到您的 Last.fm 专页,去看看 Last.fm 桌面程序有什么主要功能吧。点击<strong>继续</strong>观看功能介绍。</p></translation> </message> <message> <source>Continue</source> <translation>继续</translation> </message> <message> <source><< Back</source> <translation><< 返回</translation> </message> </context> <context> <name>CloseAppsDialog</name> <message> <source>Close Apps</source> <translation>关闭程序</translation> </message> </context> <context> <name>DeviceScrobbler</name> <message> <source>Device scrobbling disabled - incompatible iTunes plugin - %1</source> <translation>设备音乐记录已停用 - iTunes 插件不兼容 - %1</translation> </message> <message> <source>please update</source> <translation>请更新</translation> </message> <message> <source>Scrobble iPod</source> <translation>iPod 音乐记录</translation> </message> <message> <source>Do you want to associate the device %1 to your audioscrobbler user account?</source> <translation>要将 %1 设备关联到 audioscrobbler 帐户吗?</translation> </message> <message> <source>Device successfully associated to your user account. From now on you can scrobble the tracks you listen on this device.</source> <translation>设备与帐户成功关联。从现在起,在这台设备上收听的内容可以记录到帐户了。</translation> </message> <message> <source>%1 tracks scrobbled.</source> <translation>记录了 %1 首单曲。</translation> </message> <message> <source>No tracks to scrobble since your last sync.</source> <translation>上次同步后还未记录任何单曲。</translation> </message> <message> <source>The iPod database could not be opened.</source> <translation>无法打开 iPod 数据库。</translation> </message> <message> <source>An unknown error occurred while trying to access the iPod database.</source> <translation>访问 iPod 数据库时发生未知错误。</translation> </message> </context> <context> <name>DiagnosticsDialog</name> <message> <source>Diagnostics</source> <translation>诊断</translation> </message> <message> <source>Scrobbling</source> <translation>音乐记录</translation> </message> <message> <source>This is an easter egg!</source> <translation>发现新大陆!</translation> </message> <message> <source>Artist</source> <translation>艺术家</translation> </message> <message> <source>Track</source> <translation>曲目</translation> </message> <message> <source>Album</source> <translation>专辑</translation> </message> <message> <source>Fingerprinting</source> <translation>指纹识别</translation> </message> <message> <source>Recently Fingerprinted Tracks</source> <translation>最近经过指纹识别的曲目</translation> </message> <message> <source>iPod Scrobbling</source> <translation>iPod 音乐记录</translation> </message> <message> <source>iTunes automatically manages my iPod</source> <translation>由 iTunes 自动管理 iPod</translation> </message> <message> <source>I manually manage my iPod</source> <translation>手动管理 iPod</translation> </message> <message> <source>Scrobble iPod</source> <translation>从 iPod 记录歌曲</translation> </message> <message> <source>Logs</source> <translation>日志</translation> </message> <message> <source>&Close</source> <translation>关闭(&C)</translation> </message> <message numerus="yes"> <source>%n locally cached track(s)</source> <translation> <numerusform>%n 首在本地缓存的单曲</numerusform> </translation> </message> </context> <context> <name>FirstRunWizard</name> <message> <source>Last.fm Desktop App</source> <translation>Last.fm 桌面程序</translation> </message> <message> <source>Thanks <strong>%1</strong>, your account is now connected!</source> <translation>谢谢您,<strong>%1</strong>,帐户已连接!</translation> </message> <message> <source>Importing...</source> <translation>导入中...</translation> </message> <message> <source>Import complete!</source> <translation>导入完成!</translation> </message> </context> <context> <name>FriendListWidget</name> <message> <source>Find your friends on Last.fm</source> <translation>在 Last.fm 上寻找好友</translation> </message> <message> <source><h3>You haven't made any friends on Last.fm yet.</h3><p>Find your Facebook friends and email contacts on Last.fm quickly and easily using the friend finder.</p></source> <translation><h3>您尚未在 Last.fm 上结交任何好友。</h3><p>使用好友搜索功能,可以在 Last.fm 上轻松找到 Facebook 好友和电邮联系人。<p /></p></translation> </message> <message> <source>Search for a friend by username or real name</source> <translation>通过用户名或真名搜索好友</translation> </message> <message> <source>Refresh Friends</source> <translation>刷新好友</translation> </message> <message> <source>Refreshing...</source> <translation>正在刷新...</translation> </message> </context> <context> <name>FriendWidget</name> <message> <source>%1's Library Radio</source> <translation>%1的音乐库电台</translation> </message> <message> <source>Male</source> <translation>男</translation> </message> <message> <source>Scrobbling now from %1</source> <translation>正从 %1 记录音乐</translation> </message> <message> <source>Female</source> <translation>女</translation> </message> <message> <source>Scrobbling now</source> <translation>正在记录音乐</translation> </message> <message> <source>Neuter</source> <translation>中性</translation> </message> </context> <context> <name>FriendsPicker</name> <message> <source>Search your friends</source> <translation>搜索好友</translation> </message> <message> <source>Browse Friends</source> <translation>浏览好友</translation> </message> </context> <context> <name>GeneralSettingsWidget</name> <message> <source>Language:</source> <translation>语言:</translation> </message> <message> <source>Show application icon in menu bar</source> <translation>在菜单栏中显示应用程序图标</translation> </message> <message> <source>Launch application with media players</source> <translation>随媒体播放器启动程序</translation> </message> <message> <source>Show dock icon</source> <translation>显示 Dock 图标</translation> </message> <message> <source>Show desktop notifications</source> <translation>显示桌面通知</translation> </message> <message> <source>Send crash reports to Last.fm</source> <translation>向 Last.fm 发送程序崩溃报告</translation> </message> <message> <source>Check for updates automatically</source> <translation>自动检查更新</translation> </message> <message> <source>Enable media keys</source> <translation>启用媒体密钥</translation> </message> <message> <source>System Language</source> <translation>系统语言</translation> </message> <message> <source>Restart now?</source> <translation>现在重启?</translation> </message> <message> <source>An application restart is required for the change to take effect. Would you like to restart now?</source> <translation>要使更改生效,需要重启程序。要现在重启程序?</translation> </message> <message> <source>Update to beta versions - Warning: only for the brave!</source> <translation>更新到测试版 - 注意:这是勇敢者的游戏!</translation> </message> </context> <context> <name>IpodDeviceLinux</name> <message> <source>The iPod database could not be opened.</source> <translation>无法打开 iPod 数据库。</translation> </message> </context> <context> <name>IpodSettingsWidget</name> <message> <source><p>Using an iOS scrobbling app, like %1, may result in double scrobbles. Please only enable scrobbling in one of them.</p><p>iTunes Match synchronises play counts, but not last played times, across multiple devices. This will lead to duplicate scrobbles, at incorrect times. For now, we recommend iTunes Match users disable device scrobbling on desktop devices and scrobble iPhones/iPods using an iOS scrobbling app, like %1.</p></source> <translation><p>使用 iOS 音乐记录程序(如 %1)可能导致双重记录。请仅在其中一个程序中启用音乐记录。</p> <p>iTunes Match 在多部设备上同步播放次数,但不同步上次播放时间,因此会造成时间不正确的重复记录。目前我们建议 iTunes Match 用户在桌面设备上停用设备记录功能,然后使用一款 iOS 音乐记录程序(如 %1)记录 iPhone/iPod 上播放的音乐。</p></translation> </message> <message> <source>Setting not changed</source> <translation>设置未更改</translation> </message> <message> <source>You did not close iTunes for this setting to change</source> <translation>您未关闭 iTunes,因此设置未更改</translation> </message> <message> <source>Enable Device Scrobbling</source> <translation>启用设备音乐记录</translation> </message> <message> <source>Confirm Device Scrobbles</source> <translation>确认设备音乐记录</translation> </message> <message> <source>Please note</source> <translation>请注意</translation> </message> </context> <context> <name>LicensesDialog</name> <message> <source>Licenses</source> <translation>许可</translation> </message> </context> <context> <name>LoginContinueDialog</name> <message> <source>Are we done?</source> <translation>好了吗?</translation> </message> <message> <source>Click OK once you have approved this app.</source> <translation>请在授权此程序后点击“确定”。</translation> </message> </context> <context> <name>LoginDialog</name> <message> <source>Last.fm needs your permission first!</source> <translation>Last.fm 需要您的许可!</translation> </message> <message> <source>This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.</source> <translation>在连接到您的 Last.fm 专页前,此程序需要获得您的许可。请点击“确认”前往 Last.fm 网站进行相关操作。</translation> </message> </context> <context> <name>LoginPage</name> <message> <source><p>Already a Last.fm user? Connect your account with the Last.fm Desktop App and it'll update your profile with the music you're listening to.</p><p>If you don't have an account you can sign up now for free now.</p></source> <translation><p>已经是 Last.fm 用户了?将 Last.fm 桌面程序连接到帐户吧,这样您收听的音乐就会更新到专页。</p><p>还没有帐户?马上免费注册。</p></translation> </message> <message> <source>Let's get started by connecting your Last.fm account</source> <translation>我们首先来连接您的 Last.fm 帐户</translation> </message> <message> <source>Connect Your Account</source> <translation>连接帐户</translation> </message> <message> <source>Sign up</source> <translation>注册</translation> </message> <message> <source>Proxy?</source> <translation>代理?</translation> </message> </context> <context> <name>MainWindow</name> <message> <source>There are updates to your media player plugins. Would you like to install them now?</source> <translation>您的媒体播放器插件有更新,想现在安装吗?</translation> </message> <message numerus="yes"> <source>Plugin install error</source> <translation> <numerusform>插件安装错误</numerusform> </translation> </message> <message numerus="yes"> <source><p>There was an error updating your plugin(s).</p><p>Please try again later.</p></source> <translation> <numerusform><p>更新插件时发生错误。</p><p>请稍后再试。</p></numerusform> </translation> </message> <message numerus="yes"> <source>Plugin(s) installed!</source> <translation> <numerusform>插件安装完毕!</numerusform> </translation> </message> <message numerus="yes"> <source><p>Your plugin(s) ha(s|ve) been installed.</p><p>You're now ready to scrobble with your media player(s)</p></source> <translation> <numerusform><p>插件安装完毕。</p><p>现在可以用媒体播放器记录音乐了。</p></numerusform> </translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>还没安装插件</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>稍后可从文件菜单安装插件</translation> </message> <message> <source>File</source> <translation>文件</translation> </message> <message> <source>Install plugins</source> <translation>安装插件</translation> </message> <message> <source>&Quit</source> <translation>退出(&Q)</translation> </message> <message> <source>View</source> <translation>视图</translation> </message> <message> <source>My Last.fm Profile</source> <translation>我的 Last.fm 专页</translation> </message> <message> <source>Scrobbles</source> <translation>音乐记录</translation> </message> <message> <source>Refresh</source> <translation>刷新</translation> </message> <message> <source>Controls</source> <translation>控制</translation> </message> <message> <source>Account</source> <translation>帐户</translation> </message> <message> <source>Tools</source> <translation>工具</translation> </message> <message> <source>Check for Updates</source> <translation>检查更新</translation> </message> <message> <source>Options</source> <translation>选项</translation> </message> <message> <source>Window</source> <translation>窗口</translation> </message> <message> <source>Minimize</source> <translation>最小化</translation> </message> <message> <source>Zoom</source> <translation>缩放</translation> </message> <message> <source>Bring All to Front</source> <translation>前置全部窗口</translation> </message> <message> <source>Help</source> <translation>帮助</translation> </message> <message> <source>About</source> <translation>关于</translation> </message> <message> <source>FAQ</source> <translation>常见问题</translation> </message> <message> <source>Forums</source> <translation>论坛</translation> </message> <message> <source>Tour</source> <translation>功能介绍</translation> </message> <message> <source>Show Licenses</source> <translation>显示许可证</translation> </message> <message> <source>Diagnostics</source> <translation>诊断</translation> </message> <message> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> <source>%1</source> <translation>%1</translation> </message> <message> <source>%1: %2</source> <translation>%1: %2</translation> </message> <message numerus="yes"> <source><a href="tracks">%n play(s)</a> ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>已从设备记录 <a href="tracks">%n 次播放</a></numerusform> </translation> </message> </context> <context> <name>MetadataWidget</name> <message> <source>Back to Scrobbles</source> <translation>返回音乐记录</translation> </message> <message> <source>Popular tags:</source> <translation>热门标签:</translation> </message> <message> <source>Your tags:</source> <translation>您的标签:</translation> </message> <message> <source>Similar Artists</source> <translation>相似艺术家</translation> </message> <message> <source>by %1</source> <translation>艺术家:%1</translation> </message> <message> <source>from %1</source> <translation>专辑:%1</translation> </message> <message> <source>Play %1 Radio</source> <translation>播放%1电台</translation> </message> <message> <source>%L1</source> <translation>%L1</translation> </message> <message numerus="yes"> <source>Play(s)</source> <translation> <numerusform>次播放</numerusform> </translation> </message> <message numerus="yes"> <source>Play(s) in your library</source> <translation> <numerusform>次播放(在音乐库中)</numerusform> </translation> </message> <message numerus="yes"> <source>Listener(s)</source> <translation> <numerusform>位听众</numerusform> </translation> </message> <message> <source>With %1 and more.</source> <translation>包括%1等艺术家。</translation> </message> <message> <source>With %1, %2 and more.</source> <translation>包括%1、%2等艺术家。</translation> </message> <message> <source> %1</source> <translation> %1</translation> </message> <message> <source> %1 %2</source> <translation> %1 %2</translation> </message> <message> <source>Edited on %1 | %2 Edit</source> <translation>编辑时间 %1 | %2 编辑</translation> </message> <message> <source>Downloads</source> <translation>下载</translation> </message> <message> <source>Search on %1</source> <translation>在 %1 上搜索</translation> </message> <message> <source>Buy on %1 %2</source> <translation>到 %1 %2 购买</translation> </message> <message> <source>Physical</source> <translation>CD</translation> </message> <message> <source>Recommended because you listen to %1.</source> <translation>因为您收听%1,所以向您推荐这位艺术家。</translation> </message> <message> <source>Recommended because you listen to %1 and %2.</source> <translation>因为您收听%1和%2,所以向您推荐这位艺术家。</translation> </message> <message> <source>Recommended because you listen to %1, %2, and %3.</source> <translation>因为您收听%1、%2和%3,所以向您推荐这位艺术家。</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, and %4.</source> <translation>因为您收听%1、%2、%3和%4,所以向您推荐这位艺术家。</translation> </message> <message> <source>Recommended because you listen to %1, %2, %3, %4, and %5.</source> <translation>因为您收听%1、%2、%3、%4和%5,所以向您推荐这位艺术家。</translation> </message> <message> <source>From %1's library.</source> <translation>选自%1的音乐库。</translation> </message> <message> <source>From %1 and %2's libraries.</source> <translation>选自%1和%2的音乐库。</translation> </message> <message numerus="yes"> <source>%L1 time(s)</source> <translation> <numerusform>%L1 次</numerusform> </translation> </message> <message> <source>From %1, %2, and %3's libraries.</source> <translation>选自%1、%2和%3的音乐库。</translation> </message> <message> <source>You've listened to %1 %2 and %3 %4.</source> <translation>您收听过%1 %2,%3 %4。</translation> </message> <message> <source>From %1, %2, %3, and %4's libraries.</source> <translation>选自%1、%2、%3和%4的音乐库。</translation> </message> <message> <source>You've listened to %1 %2, but not this track.</source> <translation>您收听过%1 %2,但不是这首单曲。</translation> </message> <message> <source>From %1, %2, %3, %4, and %5's libraries.</source> <translation>选自%1、%2、%3、%4和%5的音乐库。</translation> </message> <message> <source>This is the first time you've listened to %1.</source> <translation>这是您第一次收听%1。</translation> </message> </context> <context> <name>NothingPlayingWidget</name> <message> <source>Hello!</source> <translation>您好!</translation> </message> <message> <source>Start a radio station</source> <translation>开始收听电台</translation> </message> <message> <source>Open iTunes</source> <translation>打开 iTunes</translation> </message> <message> <source>Open Music</source> <translation>打开 Music</translation> </message> <message> <source>Open Windows Media Player</source> <translation>打开 Windows Media Player</translation> </message> <message> <source>Open Winamp</source> <translation>打开 Winamp</translation> </message> <message> <source>Open Foobar</source> <translation>打开 Foobar</translation> </message> <message> <source><h2>Scrobble from your music player</h2><p>Start listening to some music in your media player. You can see more information about the tracks you play on the Now Playing tab.</p></source> <translation><h2>从您的音乐播放器记录音乐</h2><p>在媒体播放器中播放些音乐,其详细信息会显示在“正在播放”页面。</p></translation> </message> <message> <source>Hello, %1!</source> <translation>%1,您好!</translation> </message> </context> <context> <name>PlayableItemWidget</name> <message> <source>A Radio Station</source> <translation>电台</translation> </message> <message> <source>Play %1</source> <translation>播放%1</translation> </message> <message> <source>Multi-Library Radio</source> <translation>多音乐库电台</translation> </message> <message> <source>Cue %1</source> <translation>接下来播放%1</translation> </message> <message> <source>Play %1 and %2 Library Radio</source> <translation>播放 %1 和 %2 音乐库电台</translation> </message> <message> <source>Cue %1 and %2 Library Radio</source> <translation>接下来播放%1和%2音乐库电台</translation> </message> </context> <context> <name>PlaybackControlsWidget</name> <message> <source>Love</source> <translation>喜欢</translation> </message> <message> <source>Ban</source> <translation>禁止</translation> </message> <message> <source>Play</source> <translation>播放</translation> </message> <message> <source>Skip</source> <translation>跳过</translation> </message> <message> <source>Pause</source> <translation>暂停</translation> </message> <message> <source>Resume</source> <translation>继续</translation> </message> <message> <source>Unlove</source> <translation>不喜欢</translation> </message> <message> <source>Tuning</source> <translation>正在找台</translation> </message> <message> <source>A Radio Station</source> <translation>某电台</translation> </message> <message> <source>Listening to</source> <translation>正在收听</translation> </message> <message> <source>Scrobbling from</source> <translation>记录自</translation> </message> <message> <source>Scrobble meter: %1%</source> <translation type="unfinished"></translation> </message> <message> <source>Not scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Enable scrobbling by getting the %1.</source> <translation type="unfinished"></translation> </message> <message> <source>Last.fm app for Spotify</source> <translation type="unfinished"></translation> </message> <message> <source>Scrobbled</source> <translation type="unfinished"></translation> </message> <message> <source>Error: "%1"</source> <translation type="unfinished"></translation> </message> </context> <context> <name>PlaysLabel</name> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 次播放</numerusform> </translation> </message> </context> <context> <name>PluginBootstrapper</name> <message> <source>Last.fm has imported your media library. Click OK to continue.</source> <translation>Last.fm 已经导入了您的媒体库。 单击“确定”继续。</translation> </message> <message> <source>Last.fm Library Import</source> <translation>Last.fm 媒体库导入</translation> </message> <message> <source>Are you sure you want to cancel the import?</source> <translation>确定要取消导入吗?</translation> </message> <message> <source>Last.fm couldn't find any played tracks in your media library. Click OK to continue.</source> <translation>Last.fm 无法在媒体库中找到任何已播放曲目。 单击“确定”继续。</translation> </message> <message> <source>Last.fm is importing your current media library...</source> <translation>Last.fm 正在导入当前媒体库...</translation> </message> <message> <source>Where is Winamp?</source> <translation>Winamp 在何处?</translation> </message> <message> <source>Where is Windows Media Player?</source> <translation>Windows Media Player 在何处?</translation> </message> <message> <source>Media Library Import Complete</source> <translation>媒体库导入完毕</translation> </message> <message> <source>Last.fm has submitted your listening history to the server. Your profile will be updated with the new tracks in a few minutes.</source> <translation>Last.fm 已将您的收听历史提交到服务器。 新曲目将在几分钟后更新到您的专页。</translation> </message> <message> <source>Library Import Failed</source> <translation>媒体库导入失败</translation> </message> <message> <source>Sorry, Last.fm was unable to import your listening history. This is probably because you've already scrobbled too many tracks. Listening history can only be imported to brand new profiles.</source> <translation>真遗憾,Last.fm 未能导入您的收听历史。这可能是由于您已经记录了太多的歌曲。收听历史只能导入到新注册的专页。</translation> </message> </context> <context> <name>PluginsInstallPage</name> <message> <source><p>Please follow the instructions that appear from your operating system to install the plugins.</p><p>Once the plugins have been installed on you computer, click <strong>Continue</strong>.</p></source> <translation><p>请按操作系统的提示安装插件。</p><p>安装完毕后,点击<strong>继续</strong>。</p></translation> </message> <message> <source>Your plugins are now being installed</source> <translation>正在安装插件</translation> </message> <message> <source>Continue</source> <translation>继续</translation> </message> <message> <source><< Back</source> <translation><< 返回</translation> </message> <message> <source>Your plugins haven't been installed</source> <translation>还没安装插件</translation> </message> <message> <source>You can install them later through the file menu</source> <translation>稍后可从文件菜单安装插件</translation> </message> </context> <context> <name>PluginsPage</name> <message> <source><p>Your media players need a special Last.fm plugin to be able to scrobble the music you listen to.</p><p>Please select the media players that you would like to scrobble your music from and click <strong>Install Plugins</strong></p></source> <translation><p>要记录播放的音乐,您的媒体播放器需要一个 Last.fm 专用插件。</p><p>请选择要用来记录音乐的媒体播放器,然后点击<strong>安装插件</strong></p></translation> </message> <message> <source>(newer version)</source> <translation>(更新版本)</translation> </message> <message> <source>(Plugin installed tick to reinstall)</source> <translation>(插件已安装,选择后重新安装)</translation> </message> <message> <source>Next step, install the Last.fm plugins to be able to scrobble the music you listen to.</source> <translation>下一步,请安装 Last.fm 插件,以便记录您收听的音乐。</translation> </message> <message> <source>Install Plugins</source> <translation>安装插件</translation> </message> <message> <source>Continue</source> <translation>继续</translation> </message> <message> <source><< Back</source> <translation><< 返回</translation> </message> <message> <source>Skip >></source> <translation>跳过 >></translation> </message> </context> <context> <name>PreferencesDialog</name> <message> <source>General</source> <translation>常规</translation> </message> <message> <source>Accounts</source> <translation>帐户</translation> </message> <message> <source>Scrobbling</source> <translation>音乐记录</translation> </message> <message> <source>Devices</source> <translation>设备</translation> </message> <message> <source>Advanced</source> <translation>高级</translation> </message> </context> <context> <name>ProfileArtistWidget</name> <message> <source>%1 Radio</source> <translation>%1电台</translation> </message> <message numerus="yes"> <source>%L1 play(s)</source> <translation> <numerusform>%L1 次播放</numerusform> </translation> </message> </context> <context> <name>ProfileWidget</name> <message> <source>Top Artists This Week</source> <translation>本周最佳艺术家</translation> </message> <message> <source>Top Artists Overall</source> <translation>整体最佳艺术家</translation> </message> <message numerus="yes"> <source>Scrobble(s)</source> <translation> <numerusform>次记录</numerusform> </translation> </message> <message numerus="yes"> <source>Loved track(s)</source> <translation> <numerusform>首喜爱曲目</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 artist(s)</source> <translation> <numerusform>%L1 位艺术家</numerusform> </translation> </message> <message numerus="yes"> <source>%L1 track(s)</source> <translation> <numerusform>%L1 首曲目</numerusform> </translation> </message> <message> <source>You have %1 in your library and on average listen to %2 per day.</source> <translation>您的音乐库中有 %1,平均每天收听 %2。</translation> </message> <message numerus="yes"> <source>Scrobble(s) since %1</source> <translation> <numerusform>次记录(自 %1)</numerusform> </translation> </message> </context> <context> <name>ProxyDialog</name> <message> <source>Proxy Settings</source> <translation>代理设置</translation> </message> </context> <context> <name>ProxyWidget</name> <message> <source>Host:</source> <translation>主机:</translation> </message> <message> <source>Username:</source> <translation>用户名:</translation> </message> <message> <source>Port:</source> <translation>端口:</translation> </message> <message> <source>Password:</source> <translation>密码:</translation> </message> </context> <context> <name>QObject</name> <message> <source>unknown media player</source> <translation>无法识别的媒体播放器</translation> </message> <message> <source>Where is your iPod mounted?</source> <translation>您的 iPod 安装在何处?</translation> </message> </context> <context> <name>QuickStartWidget</name> <message> <source>Type an artist or tag and press play</source> <translation>输入艺术家名称或标签,然后按右边的播放按钮</translation> </message> <message> <source>Play</source> <translation>播放</translation> </message> <message> <source>Why not try %1, %2, %3 or %4?</source> <translation>试试%1、%2、%3或%4吧!</translation> </message> <message> <source>Play next</source> <translation>播放下一首</translation> </message> </context> <context> <name>RadioService</name> <message> <source>A Radio Station</source> <translation>某电台</translation> </message> <message> <source>You need to be a subscriber to listen to radio</source> <translation>付费用户才能收听电台</translation> </message> </context> <context> <name>RadioWidget</name> <message> <source>Last Station</source> <translation>上次收听的电台</translation> </message> <message> <source>A Radio Station</source> <translation>某电台</translation> </message> <message> <source>Personal Stations</source> <translation>个人电台</translation> </message> <message> <source>My Library Radio</source> <translation>音乐库电台</translation> </message> <message> <source>Music you know and love</source> <translation>您收听过的和喜爱的音乐</translation> </message> <message> <source>My Mix Radio</source> <translation>混合电台</translation> </message> <message> <source>Your library plus new music</source> <translation>音乐库与新音乐</translation> </message> <message> <source>My Recommended Radio</source> <translation>推荐电台</translation> </message> <message> <source>Subscribe to listen to radio</source> <translation>成为付费用户以收听电台</translation> </message> <message> <source>New music from Last.fm</source> <translation>来自 Last.fm 的新音乐</translation> </message> <message> <source>You need to be a Last.fm subscriber to listen to radio in this app. Subscribe now to start listening and take advantage of other great benefits too!</source> <translation>只有 Last.fm 付费用户才能通过此应用程序收听电台。现在就订购服务吧,马上可以收听电台,同时获得其它更酷的功能!</translation> </message> <message> <source>Network Stations</source> <translation>社交圈电台</translation> </message> <message> <source>Subscribe to Last.fm</source> <translation>成为 Last.fm 付费用户</translation> </message> <message> <source>Listen free on www.last.fm</source> <translation>在 www.last.fm 免费收听</translation> </message> <message> <source>My Friends' Radio</source> <translation>好友电台</translation> </message> <message> <source>Music your friends like</source> <translation>好友喜欢的音乐</translation> </message> <message> <source>My Neighbourhood Radio</source> <translation>邻居电台</translation> </message> <message> <source>Music from listeners like you</source> <translation>来自相似听众的音乐</translation> </message> <message> <source>Recent Stations</source> <translation>最近收听的电台</translation> </message> <message> <source>Now Playing</source> <translation>正在播放</translation> </message> <message> <source>Subscribe to listen to radio, only %1 a month</source> <translation>成为付费用户以收听电台,每月只需 %1</translation> </message> </context> <context> <name>ScrobbleConfirmationDialog</name> <message> <source>Device Scrobbles</source> <translation>设备音乐记录</translation> </message> <message> <source>It looks like you've played these tracks. Would you like to scrobble them?</source> <translation>您好像播放了这些歌曲,想记录到专页吗?</translation> </message> <message> <source>Scrobble devices automatically</source> <translation>自动记录设备播放的音乐</translation> </message> <message> <source>Toggle selection</source> <translation>切换选择</translation> </message> <message numerus="yes"> <source>%n play(s) ha(s|ve) been scrobbled from a device</source> <translation> <numerusform>已从设备记录过 %n 次播放</numerusform> </translation> </message> <message> <source>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ScrobbleControls</name> <message> <source>Love track</source> <translation>标为喜爱</translation> </message> <message> <source>Add tags</source> <translation>添加标签</translation> </message> <message> <source>Love</source> <translation>喜欢</translation> </message> <message> <source>Tag</source> <translation>标签</translation> </message> <message> <source>Share</source> <translation>分享</translation> </message> <message> <source>Share on Last.fm</source> <translation>分享到 Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>分享到 Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>分享到 Facebook</translation> </message> <message> <source>Buy</source> <translation>购买</translation> </message> <message> <source>Unlove track</source> <translation>取消喜爱</translation> </message> </context> <context> <name>ScrobbleSettingsWidget</name> <message> <source>Scrobble at</source> <translation>记录开始于</translation> </message> <message> <source>percent of the track</source> <translation>(占歌曲总长度的百分比)</translation> </message> <message> <source>Enable scrobbling</source> <translation>启用音乐记录</translation> </message> <message> <source>...or at 4 minutes (whichever comes first)</source> <translation>... 或在 4 分钟时(以先到者为准)</translation> </message> <message> <source>Scrobble podcasts</source> <translation>记录播客</translation> </message> <message> <source>Allow Last.fm to fingerprint my tracks</source> <translation>允许 Last.fm 对曲目进行指纹识别</translation> </message> <message> <source>Selected directories will not be scrobbled</source> <translation>不会记录选中目录中的音乐</translation> </message> </context> <context> <name>ScrobblesListWidget</name> <message> <source>More Scrobbles at Last.fm</source> <translation>在 Last.fm 查看更多音乐记录</translation> </message> <message> <source>Refreshing...</source> <translation>正在刷新...</translation> </message> <message> <source>Refresh Scrobbles</source> <translation>刷新音乐记录</translation> </message> </context> <context> <name>ScrobblesModel</name> <message> <source>Artist</source> <translation>艺术家</translation> </message> <message> <source>Title</source> <translation>标题</translation> </message> <message> <source>Album</source> <translation>专辑</translation> </message> <message> <source>Plays</source> <translation>次播放</translation> </message> <message> <source>Last Played</source> <translation>上次播放</translation> </message> <message> <source>Loved</source> <translation>已标为喜爱</translation> </message> <message> <source>This track is under 30 seconds</source> <translation>该曲不足 30 秒</translation> </message> <message> <source>The artist name is missing</source> <translation>缺少艺术家名称</translation> </message> <message> <source>Invalid track title</source> <translation>歌曲名错误</translation> </message> <message> <source>Invalid artist</source> <translation>艺术家无效</translation> </message> <message> <source>There is no timestamp</source> <translation>缺少时间信息</translation> </message> <message> <source>This track is too far in the future</source> <translation>记录时间不应在未来日期</translation> </message> <message> <source>This track was played over two weeks ago</source> <translation>这首歌曲两周前播放过</translation> </message> </context> <context> <name>ScrobblesWidget</name> <message> <source>You haven't scrobbled any music to Last.fm yet.</source> <translation>您还未向 Last.fm 记录过音乐。</translation> </message> <message> <source>Start listening to some music in your media player or start a radio station:</source> <translation>用媒体播放器收听些歌曲,或开始播放电台:</translation> </message> </context> <context> <name>ShareDialog</name> <message> <source>Share with Friends</source> <translation>同好友分享</translation> </message> <message> <source>With:</source> <translation>分享给:</translation> </message> <message> <source>Message (optional):</source> <translation>附言(可选):</translation> </message> <message> <source>include in my recent activity</source> <translation>更新到我的最近活动</translation> </message> <message> <source>A track by %1</source> <translation>由%1演唱的单曲</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>由%1演唱的单曲,来自专辑%2</translation> </message> <message> <source>Check out %1</source> <translation>快来听听%1</translation> </message> </context> <context> <name>SideBar</name> <message> <source>Now Playing</source> <translation>正在播放</translation> </message> <message> <source>Scrobbles</source> <translation>音乐记录</translation> </message> <message> <source>Profile</source> <translation>我</translation> </message> <message> <source>Friends</source> <translation>好友</translation> </message> <message> <source>Radio</source> <translation>电台</translation> </message> <message> <source>Next Section</source> <translation>下一视图</translation> </message> <message> <source>Previous Section</source> <translation>上一视图</translation> </message> </context> <context> <name>StationSearch</name> <message> <source>Could not start radio: %1</source> <translation>无法启动电台:%1</translation> </message> <message> <source>no results for "%1"</source> <translation>未找到有关“%1”的结果</translation> </message> </context> <context> <name>StatusBar</name> <message> <source>Scrobbling is off</source> <translation>音乐记录已关闭</translation> </message> <message> <source>%1 (%2)</source> <translation>%1 (%2)</translation> </message> <message> <source>Online</source> <translation>已连接</translation> </message> <message> <source>Offline</source> <translation>已断开</translation> </message> </context> <context> <name>TagDialog</name> <message> <source>Tag</source> <translation>标签</translation> </message> <message> <source>Choose something to tag:</source> <translation>选择要添加标签的对象:</translation> </message> <message> <source>Track</source> <translation>单曲</translation> </message> <message> <source>Artist</source> <translation>艺术家</translation> </message> <message> <source>Album</source> <translation>专辑</translation> </message> <message> <source>icon</source> <translation>图标</translation> </message> <message> <source>Add tags:</source> <translation>添加标签:</translation> </message> <message> <source>A track by %1</source> <translation>由%1演唱的单曲</translation> </message> <message> <source>A track by %1 from the release %2</source> <translation>由%1演唱的单曲,来自专辑%2</translation> </message> </context> <context> <name>TagIconView</name> <message> <source>Type a tag above, or choose from below</source> <translation>在上面输入标签, 或在下面作出选择</translation> </message> </context> <context> <name>TagListWidget</name> <message> <source>Sort by Popularity</source> <translation>按流行程度排序</translation> </message> <message> <source>Sort Alphabetically</source> <translation>按字母顺序排列</translation> </message> <message> <source>Open Last.fm Page for this Tag</source> <translation>打开此标签的 Last.fm 页面</translation> </message> </context> <context> <name>TourFinishPage</name> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We've also finished importing your listening history and have added it to your Last.fm profile.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation><p>现在一切就绪,可以使用了!请点击<strong>完成</strong>,开始音乐之旅吧。</p><p>我们还导入了您的收听历史,并更新了您的 Last.fm 专页。</p><p>感谢您安装 Last.fm 桌面程序,祝您使用愉快!</p></translation> </message> <message> <source>That's it, you're good to go!</source> <translation>好了,一切就绪!</translation> </message> <message> <source>Finish</source> <translation>完成</translation> </message> <message> <source><< Back</source> <translation><< 返回</translation> </message> <message> <source>there was an upload error</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was denied by Last.fm</source> <translation type="unfinished"></translation> </message> <message> <source>it was detected as spam (too high playcounts?)</source> <translation type="unfinished"></translation> </message> <message> <source>the submission was cancelled</source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Importing your listening history to Last.fm failed because %1. Sorry about that!</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>We're still importing your listening history and it will be added to your Last.fm profile soon.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> <message> <source><p>Now you're ready to get started! Just click <strong>Finish</strong> and start exploring.</p><p>Thanks for installing the Last.fm Desktop App, we hope you enjoy using it!</p></source> <translation type="unfinished"></translation> </message> </context> <context> <name>TourLocationPage</name> <message> <source><p>The red arrow on your screen points to the location of the Last.fm Desktop App in your system tray.</p><p>Click the icon to quickly access radio play controls, share and tag track, edit your preferences and visit your Last.fm profile.</p></source> <translation><p>屏幕上的红色箭头指向 Last.fm 桌面程序在系统栏中的位置。</p><p>点击此图标可快速访问电台播放控制、分享和单曲标签等功能。您还可以编辑偏好设置和访问 Last.fm 专页。</p></translation> </message> <message> <source>The Last.fm Desktop App in your menu bar</source> <translation>菜单栏上的 Last.fm 桌面程序</translation> </message> <message> <source>The Last.fm Desktop App in your system tray</source> <translation>系统托盘中的 Last.fm 桌面程序</translation> </message> <message> <source>Continue</source> <translation>继续</translation> </message> <message> <source><< Back</source> <translation><< 返回</translation> </message> </context> <context> <name>TourMetadataPage</name> <message> <source><p>Find out more about the music you're listening to, including biographies, listening stats, photos and similar artists, as well as the tags listeners use to describe them.</p><p>Check out the <strong>Now Playing</strong> tab, or simply click on any track in your <strong>Scrobbles</strong> tab to learn more.</p></source> <translation><p>进一步了解您在收听的音乐,包括人物传记、收听数据、照片、相似艺术家及听众使用哪些标签来描述这些音乐等。</p><p>请在<strong>正在播放</strong>或<strong>音乐记录</strong>页面点击任何单曲查看更多信息。</p></translation> </message> <message> <source>Discover more about the artists you love</source> <translation>深入了解您喜欢的艺术家</translation> </message> <message> <source>Continue</source> <translation>继续</translation> </message> <message> <source><< Back</source> <translation><< 返回</translation> </message> <message> <source>Skip Tour >></source> <translation>跳过介绍 >></translation> </message> </context> <context> <name>TourRadioPage</name> <message> <source>Listen to non-stop, personalised radio</source> <translation>无间断收听个性化电台</translation> </message> <message> <source><p>Use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>使用 Last.fm 桌面程序收听个性化电台,根据您的喜好播放音乐。</p><p>从艺术家和标签电台,到随您的音乐品味变换的全新推荐,每个 Last.fm 电台播放的音乐都不尽相同。</p></translation> </message> <message> <source>Subscribe and listen to non-stop, personalised radio</source> <translation>成为付费用户,无间断地收听个性化电台</translation> </message> <message> <source><p>Subscribe to Last.fm and use the Last.fm Desktop App to listen to personalised radio based on the music you want to hear.</p><p>Every play of every Last.fm station is totally different, from stations based on artists and tags to brand new recommendations tailored to your music taste.</p></source> <translation><p>订购 Last.fm 服务,使用 Last.fm 桌面程序收听个性化电台,根据您的喜好播放音乐。</p><p>从艺术家和标签电台,到随您的音乐品味变换的全新推荐,每个 Last.fm 电台播放的音乐都不尽相同。</p></translation> </message> <message> <source>Subscribe</source> <translation>订购</translation> </message> <message> <source>Continue</source> <translation>继续</translation> </message> <message> <source><< Back</source> <translation><< 返回</translation> </message> <message> <source>Skip Tour >></source> <translation>跳过介绍 >></translation> </message> </context> <context> <name>TourScrobblesPage</name> <message> <source><p>The desktop client runs in the background, quietly updating your Last.fm profile with the music you're playing, which you can use to get music recommendations, gig tips and more. </p><p>You can also use the Last.fm Desktop App to find out more about the artist you're listening to, and to play personalised radio.</p></source> <translation><p>桌面程序在后台运行,将您播放的音乐更新到 Last.fm 专页,通过这些数据,您可以获得音乐推荐、音乐会信息和其他精彩内容。</p><p>您还可以在收听某位艺术家的同时,使用 Last.fm 桌面程序获取更多相关信息,并可播放个性化电台。</p></translation> </message> <message> <source>Welcome to the Last.fm Desktop App!</source> <translation>欢迎使用 Last.fm 桌面程序!</translation> </message> <message> <source>Continue</source> <translation>继续</translation> </message> <message> <source><< Back</source> <translation> << 返回</translation> </message> <message> <source>Skip Tour >></source> <translation>跳过介绍 >></translation> </message> </context> <context> <name>TrackWidget</name> <message> <source>Track</source> <translation>单曲</translation> </message> <message> <source>Album</source> <translation>专辑</translation> </message> <message> <source>Artist</source> <translation>艺术家</translation> </message> <message> <source>Love</source> <translation>喜欢</translation> </message> <message> <source>Tag</source> <translation>标签</translation> </message> <message> <source>Share</source> <translation>分享</translation> </message> <message> <source>Buy</source> <translation>购买</translation> </message> <message> <source>Delete this scrobble from your profile</source> <translation>删除这条音乐记录</translation> </message> <message> <source>Play %1 Radio</source> <translation>播放%1电台</translation> </message> <message> <source>Cue %1 Radio</source> <translation>接下来播放%1电台</translation> </message> <message> <source>%1 Radio</source> <translation>%1电台</translation> </message> <message> <source>Cached</source> <translation>已缓存</translation> </message> <message> <source>Error: %1</source> <translation>错误:%1</translation> </message> <message> <source>Share on Last.fm</source> <translation>分享到 Last.fm</translation> </message> <message> <source>Share on Twitter</source> <translation>分享到 Twitter</translation> </message> <message> <source>Share on Facebook</source> <translation>分享到 Facebook</translation> </message> <message> <source>Now listening</source> <translation>正在收听</translation> </message> <message> <source>Downloads</source> <translation>下载</translation> </message> <message> <source>Search on %1</source> <translation>在 %1 上搜索</translation> </message> <message> <source>Buy on %1 %2</source> <translation>到 %1 %2 购买</translation> </message> <message> <source>Physical</source> <translation>CD</translation> </message> </context> <context> <name>UserManagerWidget</name> <message> <source>Connected User Accounts:</source> <translation>已连接用户帐户:</translation> </message> <message> <source>Add New User Account</source> <translation>添加新帐户</translation> </message> <message> <source>Add User Error</source> <translation>添加用户出错</translation> </message> <message> <source>This user has already been added.</source> <translation>该用户已被加入。</translation> </message> <message> <source>Removing %1</source> <translation>删除 %1</translation> </message> <message> <source>Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future.</source> <translation>确定要删除此用户吗?删除后,用户设置信息均将清除,以后要进行音乐记录时,需要重新验证。</translation> </message> </context> <context> <name>UserMenu</name> <message> <source>Subscribe</source> <translation>订购</translation> </message> </context> <context> <name>UserRadioButton</name> <message> <source>Remove</source> <translation>删除</translation> </message> <message> <source>(currently logged in)</source> <translation>(当前为登录状态)</translation> </message> </context> <context> <name>audioscrobbler::Application</name> <message> <source>Accounts</source> <translation>帐户</translation> </message> <message> <source>Show Scrobbler</source> <translation>显示 Scrobbler</translation> </message> <message> <source>Love</source> <translation>喜欢</translation> </message> <message> <source>Play</source> <translation>播放</translation> </message> <message> <source>Skip</source> <translation>跳过</translation> </message> <message> <source>Tag</source> <translation>标签</translation> </message> <message> <source>Share</source> <translation>分享</translation> </message> <message> <source>Ban</source> <translation>禁止</translation> </message> <message> <source>Mute</source> <translation>静音</translation> </message> <message> <source>Scrobble iPod...</source> <translation>记录 iPod...</translation> </message> <message> <source>Visit Last.fm profile</source> <translation>访问 Last.fm 专页</translation> </message> <message> <source>Enable Scrobbling</source> <translation>启用音乐记录</translation> </message> <message> <source>Quit %1</source> <translation>退出 %1</translation> </message> <message> <source>from %1</source> <translation>专辑:%1</translation> </message> <message numerus="yes"> <source>You've reached this station's skip limit. Skip again in %n minute(s).</source> <translation> <numerusform>您已达到该电台的跳过次数上限,请在 %n 分钟后再使用跳过功能。</numerusform> </translation> </message> <message numerus="yes"> <source>You have %n skip(s) remaining on this station.</source> <translation> <numerusform>您还可以在该电台跳过 %n 首曲目。</numerusform> </translation> </message> <message> <source>Authentication Required</source> <translation>需要验证</translation> </message> <message> <source><p>The user account <strong>%1</strong> is no longer authenticated with Last.fm.</p><p>Click OK to start the setup process and reauthenticate this account.</p></source> <translation><p>用户帐户 <strong>%1</strong> 需要 Last.fm 重新验证。</p><p>请点击“确定”开始设置流程,重新验证该帐户。</p></translation> </message> <message> <source>Are you sure you want to quit %1?</source> <translation>确定要退出 %1 吗?</translation> </message> <message> <source>%1 is about to quit. Tracks played will not be scrobbled if you continue.</source> <translation>%1 即将退出,如果继续,播放的曲目不会得到记录。</translation> </message> </context> <context> <name>unicorn::Application</name> <message> <source>Changing User</source> <translation>切换用户</translation> </message> <message> <source>%1 will be logged into the Scrobbler and Last.fm Radio. All music will now be scrobbled to this account. Do you want to continue?</source> <translation>%1 将登录到 Scrobbler 和 Last.fm 电台。所有音乐将记录到该帐户,要继续吗?</translation> </message> </context> <context> <name>unicorn::CloseAppsDialog</name> <message> <source>Please close the following apps to continue.</source> <translation>要继续,请先关闭以下应用程序。</translation> </message> </context> <context> <name>unicorn::IPluginInfo</name> <message> <source>Plugin install error</source> <translation>插件安装错误</translation> </message> <message> <source><p>There was an error updating your plugin.</p><p>Please try again later.</p></source> <translation><p>更新插件时发生错误。</p><p>请稍后再试。</p></translation> </message> <message> <source>Plugin installed!</source> <translation>插件安装完毕!</translation> </message> <message> <source><p>The %1 plugin has been installed.</p><p>You're now ready to scrobble with %1.</p></source> <translation><p>%1 插件已安装。</p><p>可以用 %1 记录音乐了。</p></translation> </message> <message> <source>The %1 plugin hasn't been installed</source> <translation>%1 插件未安装</translation> </message> <message> <source>You didn't close %1 so its plugin hasn't been installed.</source> <translation>您未关闭 %1,因此其插件未能安装。</translation> </message> </context> <context> <name>unicorn::ITunesPluginInstaller</name> <message> <source>Close iTunes for plugin update!</source> <translation>请关闭 iTunes 以更新插件!</translation> </message> <message> <source><p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p><p>Please close iTunes now to update.</p></source> <translation><p>您的 iTunes 插件 (%2) 与该版本程序 (%1) 随附的插件不同。</p><p>请关闭 iTunes 进行更新。</p></translation> </message> <message> <source>not installed</source> <translation>未安装</translation> </message> <message> <source>Your plugin hasn't been installed</source> <translation>插件未安装</translation> </message> <message> <source>There was an error while removing the old plugin</source> <translation>移除旧插件时出错</translation> </message> <message> <source>iTunes Plugin installed!</source> <translation>iTunes 插件安装完成!</translation> </message> <message> <source><p>Your iTunes plugin has been installed.</p><p>You're now ready to device scrobble.</p></source> <translation><p>iTunes 插件已安装。</p><p>可以用设备记录音乐了。</p></translation> </message> <message> <source>There was an error while copying the new plugin into place</source> <translation>复制新插件时发生错误</translation> </message> <message> <source>You didn't close iTunes</source> <translation>尚未关闭 iTunes</translation> </message> </context> <context> <name>unicorn::Label</name> <message> <source>Time is broken</source> <translation>时间错乱</translation> </message> <message numerus="yes"> <source>%n minute(s) ago</source> <translation> <numerusform>%n 分钟前</numerusform> </translation> </message> <message numerus="yes"> <source>%n hour(s) ago</source> <translation> <numerusform>%n 小时前</numerusform> </translation> </message> </context> <context> <name>unicorn::LoginProcess</name> <message> <source>There was a network error: %1</source> <translation>发生网络错误:%1</translation> </message> <message> <source>You have not authorised this application</source> <translation>您尚未授权该程序</translation> </message> <message> <source>Authentication Error</source> <translation>验证错误</translation> </message> </context> <context> <name>unicorn::MainWindow</name> <message> <source>Refresh Stylesheet</source> <translation>刷新样式表</translation> </message> </context> <context> <name>unicorn::MessageDialog</name> <message> <source>Don't ask this again</source> <translation>不再询问</translation> </message> </context> <context> <name>unicorn::ProxyWidget</name> <message> <source>Auto-detect</source> <translation>自动检测</translation> </message> <message> <source>No-proxy</source> <translation>无代理</translation> </message> <message> <source>HTTP</source> <translation>HTTP</translation> </message> <message> <source>SOCKS5</source> <translation>SOCKS5</translation> </message> </context> </TS> ================================================ FILE: lib/3rdparty/README ================================================ Windows ======= You need to install: * mad * fftw3 (single precision) * libsamplerate * taglib The fetch.sh will attempt to fetch and build them all. But you need to use MinGW in order to compile fftw and libsamplerate. We have binaries at /svn/clientside/bin/win. Minimal headers are also in lib/3rdparty, though we don't keep these up to date. Mac OS X ======== Use macports to install: * taglib * fftw3-3 * libsamplerate * taglib Linux ===== Install the same as for the Mac with your package manager. ================================================ FILE: lib/3rdparty/fetch.sh ================================================ source ../../common/bash/utils.sh.inc function go { tar=`basename $1` dir=`basename $1 .tar.gz` shift header $dir test -f $tar || wget $1 # test -d $dir || tar xzf $tar } function co { svn co $1 $2 pushd $2 ./configure make popd } ################################################################################ case `uname` CYGWIN_NT_5.1) which make || die "You need to install GNU make" which ld || die "You need to install cygwin binutils" which wget || die "You need to install wget" go http://surfnet.dl.sourceforge.net/sourceforge/mad/libmad-0.15.1b.tar.gz --disable-debug go http://www.fftw.org/fftw-3.1.3.tar.gz --enable-float --disable-debug go http://www.mega-nerd.com/SRC/libsamplerate-0.1.4.tar.gz --disable-debug go http://developer.kde.org/~wheeler/files/src/taglib-1.5.tar.gz --disable-debug ;; *) echo Please refer to the README file. ;; esac #co http://google-breakpad.googlecode.com/svn/trunk/ breakpad header Done! echo "Now do make install in each directory or something" echo ================================================ FILE: lib/3rdparty/fftw3.h ================================================ /* * Copyright (c) 2003, 2006 Matteo Frigo * Copyright (c) 2003, 2006 Massachusetts Institute of Technology * * The following statement of license applies *only* to this header file, * and *not* to the other files distributed with FFTW or derived therefrom: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /***************************** NOTE TO USERS ********************************* * * THIS IS A HEADER FILE, NOT A MANUAL * * If you want to know how to use FFTW, please read the manual, * online at http://www.fftw.org/doc/ and also included with FFTW. * For a quick start, see the manual's tutorial section. * * (Reading header files to learn how to use a library is a habit * stemming from code lacking a proper manual. Arguably, it's a * *bad* habit in most cases, because header files can contain * interfaces that are not part of the public, stable API.) * ****************************************************************************/ /* header file for fftw3 */ /* (The following is the CVS ID for this file, *not* the version number of FFTW:) */ /* $Id: fftw3.h,v 1.90 2006-01-17 04:03:33 stevenj Exp $ */ #ifndef FFTW3_H #define FFTW3_H #include <stdio.h> #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* If <complex.h> is included, use the C99 complex type. Otherwise define a type bit-compatible with C99 complex */ #if !defined(FFTW_NO_Complex) && defined(_Complex_I) && defined(complex) && defined(I) # define FFTW_DEFINE_COMPLEX(R, C) typedef R _Complex C #else # define FFTW_DEFINE_COMPLEX(R, C) typedef R C[2] #endif #define FFTW_CONCAT(prefix, name) prefix ## name #define FFTW_MANGLE_DOUBLE(name) FFTW_CONCAT(fftw_, name) #define FFTW_MANGLE_FLOAT(name) FFTW_CONCAT(fftwf_, name) #define FFTW_MANGLE_LONG_DOUBLE(name) FFTW_CONCAT(fftwl_, name) /* IMPORTANT: for Windows compilers, you should add a line #define FFTW_DLL here and in kernel/ifftw.h if you are compiling/using FFTW as a DLL, in order to do the proper importing/exporting, or alternatively compile with -DFFTW_DLL or the equivalent command-line flag. This is not necessary under MinGW/Cygwin, where libtool does the imports/exports automatically. */ #if defined(FFTW_DLL) && (defined(_WIN32) || defined(__WIN32__)) /* annoying Windows syntax for shared-library declarations */ # if defined(COMPILING_FFTW) /* defined in api.h when compiling FFTW */ # define FFTW_EXTERN extern __declspec(dllexport) # else /* user is calling FFTW; import symbol */ # define FFTW_EXTERN extern __declspec(dllimport) # endif #else # define FFTW_EXTERN extern #endif enum fftw_r2r_kind_do_not_use_me { FFTW_R2HC=0, FFTW_HC2R=1, FFTW_DHT=2, FFTW_REDFT00=3, FFTW_REDFT01=4, FFTW_REDFT10=5, FFTW_REDFT11=6, FFTW_RODFT00=7, FFTW_RODFT01=8, FFTW_RODFT10=9, FFTW_RODFT11=10 }; struct fftw_iodim_do_not_use_me { int n; /* dimension size */ int is; /* input stride */ int os; /* output stride */ }; /* huge second-order macro that defines prototypes for all API functions. We expand this macro for each supported precision X: name-mangling macro R: real data type C: complex data type */ #define FFTW_DEFINE_API(X, R, C) \ \ FFTW_DEFINE_COMPLEX(R, C); \ \ typedef struct X(plan_s) *X(plan); \ \ typedef struct fftw_iodim_do_not_use_me X(iodim); \ \ typedef enum fftw_r2r_kind_do_not_use_me X(r2r_kind); \ \ FFTW_EXTERN void X(execute)(const X(plan) p); \ \ FFTW_EXTERN X(plan) X(plan_dft)(int rank, const int *n, \ C *in, C *out, int sign, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_dft_1d)(int n, C *in, C *out, int sign, \ unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_2d)(int nx, int ny, \ C *in, C *out, int sign, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_3d)(int nx, int ny, int nz, \ C *in, C *out, int sign, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_many_dft)(int rank, const int *n, \ int howmany, \ C *in, const int *inembed, \ int istride, int idist, \ C *out, const int *onembed, \ int ostride, int odist, \ int sign, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru_dft)(int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ C *in, C *out, \ int sign, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_guru_split_dft)(int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ R *ri, R *ii, R *ro, R *io, \ unsigned flags); \ \ FFTW_EXTERN void X(execute_dft)(const X(plan) p, C *in, C *out); \ FFTW_EXTERN void X(execute_split_dft)(const X(plan) p, R *ri, R *ii, \ R *ro, R *io); \ \ FFTW_EXTERN X(plan) X(plan_many_dft_r2c)(int rank, const int *n, \ int howmany, \ R *in, const int *inembed, \ int istride, int idist, \ C *out, const int *onembed, \ int ostride, int odist, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_dft_r2c)(int rank, const int *n, \ R *in, C *out, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_dft_r2c_1d)(int n,R *in,C *out,unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_r2c_2d)(int nx, int ny, \ R *in, C *out, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_r2c_3d)(int nx, int ny, \ int nz, \ R *in, C *out, unsigned flags); \ \ \ FFTW_EXTERN X(plan) X(plan_many_dft_c2r)(int rank, const int *n, \ int howmany, \ C *in, const int *inembed, \ int istride, int idist, \ R *out, const int *onembed, \ int ostride, int odist, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_dft_c2r)(int rank, const int *n, \ C *in, R *out, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_dft_c2r_1d)(int n,C *in,R *out,unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_c2r_2d)(int nx, int ny, \ C *in, R *out, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_c2r_3d)(int nx, int ny, \ int nz, \ C *in, R *out, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru_dft_r2c)(int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ R *in, C *out, \ unsigned flags); \ FFTW_EXTERN X(plan) X(plan_guru_dft_c2r)(int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ C *in, R *out, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru_split_dft_r2c)( \ int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ R *in, R *ro, R *io, \ unsigned flags); \ FFTW_EXTERN X(plan) X(plan_guru_split_dft_c2r)( \ int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ R *ri, R *ii, R *out, \ unsigned flags); \ \ FFTW_EXTERN void X(execute_dft_r2c)(const X(plan) p, R *in, C *out); \ FFTW_EXTERN void X(execute_dft_c2r)(const X(plan) p, C *in, R *out); \ \ FFTW_EXTERN void X(execute_split_dft_r2c)(const X(plan) p, \ R *in, R *ro, R *io); \ FFTW_EXTERN void X(execute_split_dft_c2r)(const X(plan) p, \ R *ri, R *ii, R *out); \ \ FFTW_EXTERN X(plan) X(plan_many_r2r)(int rank, const int *n, \ int howmany, \ R *in, const int *inembed, \ int istride, int idist, \ R *out, const int *onembed, \ int ostride, int odist, \ const X(r2r_kind) *kind, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_r2r)(int rank, const int *n, R *in, R *out, \ const X(r2r_kind) *kind, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_r2r_1d)(int n, R *in, R *out, \ X(r2r_kind) kind, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_r2r_2d)(int nx, int ny, R *in, R *out, \ X(r2r_kind) kindx, X(r2r_kind) kindy, \ unsigned flags); \ FFTW_EXTERN X(plan) X(plan_r2r_3d)(int nx, int ny, int nz, \ R *in, R *out, X(r2r_kind) kindx, \ X(r2r_kind) kindy, X(r2r_kind) kindz, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru_r2r)(int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ R *in, R *out, \ const X(r2r_kind) *kind, unsigned flags); \ FFTW_EXTERN void X(execute_r2r)(const X(plan) p, R *in, R *out); \ \ FFTW_EXTERN void X(destroy_plan)(X(plan) p); \ FFTW_EXTERN void X(forget_wisdom)(void); \ FFTW_EXTERN void X(cleanup)(void); \ \ FFTW_EXTERN void X(set_timelimit)(double); \ \ FFTW_EXTERN void X(plan_with_nthreads)(int nthreads); \ FFTW_EXTERN int X(init_threads)(void); \ FFTW_EXTERN void X(cleanup_threads)(void); \ \ FFTW_EXTERN void X(export_wisdom_to_file)(FILE *output_file); \ FFTW_EXTERN char *X(export_wisdom_to_string)(void); \ FFTW_EXTERN void X(export_wisdom)(void (*write_char)(char c, void *), \ void *data); \ FFTW_EXTERN int X(import_system_wisdom)(void); \ FFTW_EXTERN int X(import_wisdom_from_file)(FILE *input_file); \ FFTW_EXTERN int X(import_wisdom_from_string)(const char *input_string); \ FFTW_EXTERN int X(import_wisdom)(int (*read_char)(void *), void *data); \ \ FFTW_EXTERN void X(fprint_plan)(const X(plan) p, FILE *output_file); \ FFTW_EXTERN void X(print_plan)(const X(plan) p); \ \ FFTW_EXTERN void *X(malloc)(size_t n); \ FFTW_EXTERN void X(free)(void *p); \ \ FFTW_EXTERN void X(flops)(const X(plan) p, \ double *add, double *mul, double *fmas); \ FFTW_EXTERN double X(estimate_cost)(const X(plan) p); \ \ FFTW_EXTERN const char X(version)[]; \ FFTW_EXTERN const char X(cc)[]; \ FFTW_EXTERN const char X(codelet_optim)[]; /* end of FFTW_DEFINE_API macro */ FFTW_DEFINE_API(FFTW_MANGLE_DOUBLE, double, fftw_complex) FFTW_DEFINE_API(FFTW_MANGLE_FLOAT, float, fftwf_complex) FFTW_DEFINE_API(FFTW_MANGLE_LONG_DOUBLE, long double, fftwl_complex) #define FFTW_FORWARD (-1) #define FFTW_BACKWARD (+1) #define FFTW_NO_TIMELIMIT (-1.0) /* documented flags */ #define FFTW_MEASURE (0U) #define FFTW_DESTROY_INPUT (1U << 0) #define FFTW_UNALIGNED (1U << 1) #define FFTW_CONSERVE_MEMORY (1U << 2) #define FFTW_EXHAUSTIVE (1U << 3) /* NO_EXHAUSTIVE is default */ #define FFTW_PRESERVE_INPUT (1U << 4) /* cancels FFTW_DESTROY_INPUT */ #define FFTW_PATIENT (1U << 5) /* IMPATIENT is default */ #define FFTW_ESTIMATE (1U << 6) /* undocumented beyond-guru flags */ #define FFTW_ESTIMATE_PATIENT (1U << 7) #define FFTW_BELIEVE_PCOST (1U << 8) #define FFTW_NO_DFT_R2HC (1U << 9) #define FFTW_NO_NONTHREADED (1U << 10) #define FFTW_NO_BUFFERING (1U << 11) #define FFTW_NO_INDIRECT_OP (1U << 12) #define FFTW_ALLOW_LARGE_GENERIC (1U << 13) /* NO_LARGE_GENERIC is default */ #define FFTW_NO_RANK_SPLITS (1U << 14) #define FFTW_NO_VRANK_SPLITS (1U << 15) #define FFTW_NO_VRECURSE (1U << 16) #define FFTW_NO_SIMD (1U << 17) #define FFTW_NO_SLOW (1U << 18) #define FFTW_NO_FIXED_RADIX_LARGE_N (1U << 19) #define FFTW_ALLOW_PRUNING (1U << 20) #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ #endif /* FFTW3_H */ ================================================ FILE: lib/3rdparty/iTunesCOMAPI/LicenseAgreement.rtf ================================================ {\rtf1\mac\ansicpg10000\cocoartf102 {\fonttbl\f0\fnil\fcharset77 LucidaGrande-Bold;\f1\fnil\fcharset77 LucidaGrande;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww14340\viewh11900\viewkind0 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural \f0\b\fs20 \cf0 ENGLISH \f1\b0 \ \ \f0\b Apple Computer, Inc.\ Software Developer Kit (SDK) Software License Agreement\ \ PLEASE READ THIS SOFTWARE LICENSE AGREEMENT ("LICENSE") BEFORE USING THE SOFTWARE. BY USING THE SOFTWARE, YOU ARE AGREEING TO BE BOUND BY THE TERMS OF THIS LICENSE. IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY, SIGNIFY YOUR AGREEMENT TO BE BOUND BY THE TERMS OF THIS LICENSE BY CLICKING THE "AGREE/ACCEPT" BUTTON. IF YOU DO NOT AGREE TO THE TERMS OF THIS LICENSE, RETURN THE APPLE SOFTWARE TO THE PLACE WHERE YOU OBTAINED IT FOR A REFUND OR, IF THE SOFTWARE WAS ACCESSED ELECTRONICALLY, CLICK "DISAGREE/DECLINE".\ \ IMPORTANT NOTE: To the extent this software may be used to reproduce materials, it is licensed to you only for reproduction of materials you are authorized or legally permitted to reproduce. \ \f1\b0 \ \f0\b 1. License. \f1\b0 Any software, tools, utilities, sample code, documentation, fonts and other materials accompanying this License, whether on disk, print or electronic documentation, in read only memory, or any other media, (collectively, the "Apple Software") are licensed, not sold, to you by Apple Computer, Inc. ("Apple") for use only under the terms of this License, and Apple reserves all rights not expressly granted to you. The rights granted herein are limited to Apple's and its licensors' intellectual property rights in the Apple Software and do not include any other patents or intellectual property rights. You own the media on which the Apple Software is recorded but Apple and/or Apple's licensor(s) retain ownership of the Apple Software itself. The Apple Software in this package and any copies, modifications and derivative works which this License authorizes you to make are subject to this License. \ \ \f0\b 2. Permitted Uses and Restrictions. \f1\b0 You may use the Apple Software to (i) test the Apple Software, and (ii) to develop application software that is compatible with, and runs on the same platform as, the Apple Software. You have no right to modify, incorporate into or compile in combination with your own programs, license or otherwise distribute any portion of the Apple Software unless the Licensing Info folder (if any) included with the Apple Software expressly authorizes you to do so. Any terms in the Licensing Info folder shall be deemed to be terms of this License. Any distribution of any portion of the Apple Software authorized by the Licensing Info folder must be done pursuant to a valid agreement that is at least as protective of Apple's rights in the Apple Software as this License and the terms in the Licensing Info folder. You may make only as many internal use copies of the Apple Software as reasonably necessary to use the Apple Software as permitted in this paragraph and distribute such copies only to your employees whose job duties require them to so use the Apple Software. You must reproduce on each copy of the Apple Software or portion thereof, the Apple copyright notice and any other proprietary legends that were on the original copy of the Apple Software. Except as expressly permitted in this License, you may not decompile, reverse engineer, disassemble, modify, rent, lease, loan, sublicense, distribute or create derivative works based upon the Apple Software in whole or part or transmit the Apple Software over a network or from one computer to another. Your rights under this License will terminate automatically without notice from Apple if you fail to comply with any term(s) of this License. In addition, Apple reserves the right to terminate this License if a new version of Apple's operating system software or the Apple Software is released which is incompatible with the Apple Software.\ \ \f0\b 3. Limited Warranty on Media (if applicable). \f1\b0 Apple warrants the media on which the Apple Software is recorded and delivered by Apple to be free from defects in materials and workmanship under normal use for a period of ninety (90) days from the date of original retail purchase. Your exclusive remedy under this Section shall be, at Apple\'d5s option, a refund of the purchase price of the product containing the Apple Software or replacement of the Apple Software which is returned to Apple or an Apple authorized representative with a copy of the receipt. THIS LIMITED WARRANTY AND ANY IMPLIED WARRANTIES ON THE MEDIA INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, OF SATISFACTORY QUALITY, AND OF FITNESS FOR A PARTICULAR PURPOSE, ARE LIMITED IN DURATION TO NINETY (90) DAYS FROM THE DATE OF ORIGINAL RETAIL PURCHASE. SOME JURISDICTIONS DO NOT ALLOW LIMITATIONS ON HOW LONG AN IMPLIED WARRANTY LASTS, SO THE ABOVE LIMITATION MAY NOT APPLY TO YOU. THE LIMITED WARRANTY SET FORTH HEREIN IS THE ONLY WARRANTY MADE TO YOU AND IS PROVIDED IN LIEU OF ANY OTHER WARRANTIES (IF ANY) CREATED BY ANY DOCUMENTATION OR PACKAGING. THIS LIMITED WARRANTY GIVES YOU SPECIFIC LEGAL RIGHTS, AND YOU MAY ALSO HAVE OTHER RIGHTS WHICH VARY BY JURISDICTION.\ \ \f0\b 4. Disclaimer Of Warranty. \f1\b0 The Apple Software may be "alpha", "beta", "development", pre-release, untested, and/or not fully tested and may contain errors that could cause failures or loss of data, be incomplete or contain inaccuracies. YOU EXPRESSLY ACKNOWLEDGE AND AGREE THAT USE OF THE APPLE SOFTWARE IS AT YOUR SOLE RISK AND THAT THE ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY AND EFFORT IS WITH YOU. EXCEPT FOR THE LIMITED WARRANTY ON MEDIA SET FORTH ABOVE AND TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE APPLE SOFTWARE IS PROVIDED "AS IS", WITH ALL FAULTS AND WITHOUT WARRANTY OF ANY KIND, AND APPLE AND APPLE'S LICENSORS (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE PURPOSES OF SECTIONS 4 AND 5) HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS WITH RESPECT TO THE APPLE SOFTWARE, EITHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NON-INFRINGEMENT OF THIRD PARTY RIGHTS. APPLE DOES NOT WARRANT AGAINST INTERFERENCE WITH YOUR ENJOYMENT OF THE APPLE SOFTWARE, THAT THE FUNCTIONS CONTAINED IN THE APPLE SOFTWARE WILL MEET YOUR REQUIREMENTS, THAT THE OPERATION OF THE APPLE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT DEFECTS IN THE APPLE SOFTWARE WILL BE CORRECTED. NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE OR AN APPLE AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY. SHOULD THE APPLE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE ENTIRE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES OR LIMITATIONS ON APPLICABLE STATUTORY RIGHTS OF A CONSUMER, SO THE ABOVE EXCLUSION AND LIMITATIONS MAY NOT APPLY TO YOU. \ \ \f0\b 5. Limitation Of Liability. \f1\b0 TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT SHALL APPLE BE LIABLE FOR PERSONAL INJURY, OR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES WHATSOEVER, INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF PROFITS, LOSS OF DATA, BUSINESS INTERRUPTION OR ANY OTHER COMMERCIAL DAMAGES OR LOSSES, ARISING OUT OF OR RELATED TO YOUR USE OR INABILITY TO USE THE APPLE SOFTWARE, HOWEVER CAUSED, REGARDLESS OF THE THEORY OF LIABILITY (CONTRACT, TORT OR OTHERWISE) AND EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY FOR PERSONAL INJURY, OR OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY TO YOU. In no event shall Apple's total liability to you for all damages (other than as may be required by applicable law in cases involving personal injury) exceed the amount of fifty dollars ($50.00). The foregoing limitations will apply even if the above stated remedy fails of its essential purpose.\ \ \f0\b 6. Export Law Assurances. \f1\b0 You may not use or otherwise export or reexport the Apple Software except as authorized by United States law and the laws of the jurisdiction in which the Apple Software was obtained. In particular, but without limitation, the Apple Software may not be exported or re-exported (a) into (or to a national or resident of) any U.S. embargoed countries (currently Cuba, Iran, Iraq, Libya, North Korea, Sudan, and Syria), or (b) to anyone on the U.S. Treasury Department's list of Specially Designated Nationals or the U.S. Department of Commerce Denied Person\'d5s List or Entity List. By using the Apple Software, you represent and warrant that you are not located in, under control of, or a national or resident of any such country or on any such list.\ \f0\b \ 7. Government End Users. \f1\b0 The Apple Software and related documentation are "Commercial Items", as that term is defined at 48 C.F.R. \'a42.101, consisting of "Commercial Computer Software" and "Commercial Computer Software Documentation", as such terms are used in 48 C.F.R. \'a412.212 or 48 C.F.R. \'a4227.7202, as applicable. Consistent with 48 C.F.R. \'a412.212 or 48 C.F.R. \'a4227.7202-1through 227.7202-4, as applicable, the Commercial Computer Software and Commercial Computer Software Documentation are being licensed to U.S. Government end users (a) only as Commercial Items and (b) with only those rights as are granted to all other end users pursuant to the terms and conditions herein. Unpublished-rights reserved under the copyright laws of the United States.\ \ \f0\b 8. Controlling Law and Severability. \f1\b0 This License will be governed by and construed in accordance with the laws of the State of California, as applied to agreements entered into and to be performed entirely within California between California residents. This License shall not be governed by the United Nations Convention on Contracts for the International Sale of Goods, the application of which is expressly excluded. If for any reason a court of competent jurisdiction finds any provision, or portion thereof, to be unenforceable, the remainder of this License shall continue in full force and effect. \ \ \f0\b 9. Complete Agreement. \f1\b0 This License constitutes the entire agreement between the parties with respect to the use of the Apple Software licensed hereunder and supersedes all prior or contemporaneous understandings regarding such subject matter. No amendment to or modification of this License will be binding unless in writing and signed by Apple. Any translation of this License is done for local requirements and in the event of a dispute between the English and any non-English versions, the English version of this License shall govern.\ \ EA0139} ================================================ FILE: lib/3rdparty/iTunesCOMAPI/ReadMe.rtf ================================================ {\rtf1\mac\ansicpg10000\cocoartf102 {\fonttbl\f0\fswiss\fcharset77 Helvetica;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww10620\viewh12220\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural \f0\fs24 \cf0 iTunesCOM.chm\ \ Compressed HTML help file containing the documentation on the iTunes COM\ interface. It's built directly from the IDL files, so anything you see\ documented is guaranteed to be available in iTunes.\ \ iTunesCOMInterface.h\ iTunesCOMInterface_i.c\ \ The iTunes COM header file (along with IID constants), needed for building clients in a high level\ language like C++. Note that the type library is built into iTunes.exe,\ you can browse it using OLEView or similar tools.\ \ SampleScripts folder\ \ A few sample JScripts that use the iTunes COM interface:\ \ CreateAlbumPlaylists.js\ \ Iterates over all tracks in your library, creates a new playlist for\ each album, and adds the tracks for that album to the playlist.\ \ RemoveDeadTracks.js\ \ Iterates over your library and removes any tracks that can no longer be\ found on disk.\ \ RemoveUserPlaylists.js\ \ Deletes all non-smart user playlists your library.\ } ================================================ FILE: lib/3rdparty/iTunesCOMAPI/SampleScripts/CreateAlbumPlaylists.js ================================================ /* File: CreateAlbumPlaylists.js Version: 1.0 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apples copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Computer, Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright 2004 Apple Computer, Inc., All Rights Reserved */ var iTunesApp = WScript.CreateObject("iTunes.Application"); var mainLibrary = iTunesApp.LibraryPlaylist; var mainLibrarySource = iTunesApp.LibrarySource; var tracks = mainLibrary.Tracks; var numTracks = tracks.Count; var numPlaylistsCreated = 0; var i; // FIXME take a -v parameter eventually var verbose = false; // first, make an array indexed by album name var albumArray = new Array(); for (i = 1; i <= numTracks; i++) { var currTrack = tracks.Item(i); var album = currTrack.Album; if ((album != undefined) && (album != "")) { if (albumArray[album] == undefined) { if (verbose) WScript.Echo("Adding album " + album); albumArray[album] = new Array(); } // add the track to the entry for this album albumArray[album].push(currTrack); } } for (var albumNameKey in albumArray) { var albumPlayList; var trackArray = albumArray[albumNameKey]; if (verbose) WScript.Echo("Creating playlist " + albumNameKey); numPlaylistsCreated++; albumPlaylist = iTunesApp.CreatePlaylist(albumNameKey); for (var trackIndex in trackArray) { var currTrack = trackArray[trackIndex]; if (verbose) WScript.Echo(" Adding " + currTrack.Name); albumPlaylist.AddTrack(currTrack); } } if (numPlaylistsCreated == 0) { WScript.Echo("No playlists created."); } else if (numPlaylistsCreated == 1) { WScript.Echo("Created 1 playlist."); } else { WScript.Echo("Created " + numPlaylistsCreated + " playlists."); } ================================================ FILE: lib/3rdparty/iTunesCOMAPI/SampleScripts/RemoveDeadTracks.js ================================================ /* File: RemoveDeadTracks.js Version: 1.0 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apples copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Computer, Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright 2004 Apple Computer, Inc., All Rights Reserved */ var ITTrackKindFile = 1; var iTunesApp = WScript.CreateObject("iTunes.Application"); var deletedTracks = 0; var mainLibrary = iTunesApp.LibraryPlaylist; var tracks = mainLibrary.Tracks; var numTracks = tracks.Count; var i; while (numTracks != 0) { var currTrack = tracks.Item(numTracks); // is this a file track? if (currTrack.Kind == ITTrackKindFile) { // yes, does it have an empty location? if (currTrack.Location == "") { // yes, delete it currTrack.Delete(); deletedTracks++; } } numTracks--; } if (deletedTracks > 0) { if (deletedTracks == 1) { WScript.Echo("Removed 1 dead track."); } else { WScript.Echo("Removed " + deletedTracks + " dead tracks."); } } else { WScript.Echo("No dead tracks were found."); } ================================================ FILE: lib/3rdparty/iTunesCOMAPI/SampleScripts/RemoveUserPlaylists.js ================================================ /* File: RemoveUserPlaylists.js Version: 1.0 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apples copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Computer, Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright 2004 Apple Computer, Inc., All Rights Reserved */ var ITPlaylistKindUser = 2; var iTunesApp = WScript.CreateObject("iTunes.Application"); var deletedPlaylists = 0; var mainLibrary = iTunesApp.LibrarySource; var playlists = mainLibrary.Playlists; var numPlaylists = playlists.Count; while (numPlaylists != 0) { var currPlaylist = playlists.Item(numPlaylists); // is this a user playlist? if (currPlaylist.Kind == ITPlaylistKindUser) { // yes, is it a dumb playlist? if (!currPlaylist.Smart) { try { // yes, delete it currPlaylist.Delete(); deletedPlaylists++; } catch (exception) { // ignore errors (e.g. trying to delete a locked playlist) } } } numPlaylists--; } if (deletedPlaylists > 0) { if (deletedPlaylists == 1) { WScript.Echo("Removed 1 user playlist."); } else { WScript.Echo("Removed " + deletedPlaylists + " user playlists."); } } else { WScript.Echo("No user playlists were removed."); } ================================================ FILE: lib/3rdparty/iTunesCOMAPI/iTunesCOMInterface.h ================================================ /* this ALWAYS GENERATED file contains the definitions for the interfaces */ /* File created by MIDL compiler version 6.00.0366 */ /* at Wed Jun 25 17:02:20 2008 */ /* Compiler settings for iTunesCOMInterface.idl: Oicf, W1, Zp8, env=Win32 (32b run) protocol : dce , ms_ext, c_ext, robust error checks: allocation ref bounds_check enum stub_data VC __declspec() decoration level: __declspec(uuid()), __declspec(selectany), __declspec(novtable) DECLSPEC_UUID(), MIDL_INTERFACE() */ //@@MIDL_FILE_HEADING( ) #pragma warning( disable: 4049 ) /* more than 64k source lines */ /* verify that the <rpcndr.h> version is high enough to compile this file*/ #ifndef __REQUIRED_RPCNDR_H_VERSION__ #define __REQUIRED_RPCNDR_H_VERSION__ 475 #endif #include "rpc.h" #include "rpcndr.h" #ifndef __RPCNDR_H_VERSION__ #error this stub requires an updated version of <rpcndr.h> #endif // __RPCNDR_H_VERSION__ #ifndef __iTunesCOMInterface_h__ #define __iTunesCOMInterface_h__ #if defined(_MSC_VER) && (_MSC_VER >= 1020) #pragma once #endif /* Forward Declarations */ #ifndef __IITObject_FWD_DEFINED__ #define __IITObject_FWD_DEFINED__ typedef interface IITObject IITObject; #endif /* __IITObject_FWD_DEFINED__ */ #ifndef __IITSource_FWD_DEFINED__ #define __IITSource_FWD_DEFINED__ typedef interface IITSource IITSource; #endif /* __IITSource_FWD_DEFINED__ */ #ifndef __IITSourceCollection_FWD_DEFINED__ #define __IITSourceCollection_FWD_DEFINED__ typedef interface IITSourceCollection IITSourceCollection; #endif /* __IITSourceCollection_FWD_DEFINED__ */ #ifndef __IITEncoder_FWD_DEFINED__ #define __IITEncoder_FWD_DEFINED__ typedef interface IITEncoder IITEncoder; #endif /* __IITEncoder_FWD_DEFINED__ */ #ifndef __IITEncoderCollection_FWD_DEFINED__ #define __IITEncoderCollection_FWD_DEFINED__ typedef interface IITEncoderCollection IITEncoderCollection; #endif /* __IITEncoderCollection_FWD_DEFINED__ */ #ifndef __IITEQPreset_FWD_DEFINED__ #define __IITEQPreset_FWD_DEFINED__ typedef interface IITEQPreset IITEQPreset; #endif /* __IITEQPreset_FWD_DEFINED__ */ #ifndef __IITEQPresetCollection_FWD_DEFINED__ #define __IITEQPresetCollection_FWD_DEFINED__ typedef interface IITEQPresetCollection IITEQPresetCollection; #endif /* __IITEQPresetCollection_FWD_DEFINED__ */ #ifndef __IITPlaylist_FWD_DEFINED__ #define __IITPlaylist_FWD_DEFINED__ typedef interface IITPlaylist IITPlaylist; #endif /* __IITPlaylist_FWD_DEFINED__ */ #ifndef __IITOperationStatus_FWD_DEFINED__ #define __IITOperationStatus_FWD_DEFINED__ typedef interface IITOperationStatus IITOperationStatus; #endif /* __IITOperationStatus_FWD_DEFINED__ */ #ifndef __IITConvertOperationStatus_FWD_DEFINED__ #define __IITConvertOperationStatus_FWD_DEFINED__ typedef interface IITConvertOperationStatus IITConvertOperationStatus; #endif /* __IITConvertOperationStatus_FWD_DEFINED__ */ #ifndef __IITLibraryPlaylist_FWD_DEFINED__ #define __IITLibraryPlaylist_FWD_DEFINED__ typedef interface IITLibraryPlaylist IITLibraryPlaylist; #endif /* __IITLibraryPlaylist_FWD_DEFINED__ */ #ifndef __IITUserPlaylist_FWD_DEFINED__ #define __IITUserPlaylist_FWD_DEFINED__ typedef interface IITUserPlaylist IITUserPlaylist; #endif /* __IITUserPlaylist_FWD_DEFINED__ */ #ifndef __IITTrack_FWD_DEFINED__ #define __IITTrack_FWD_DEFINED__ typedef interface IITTrack IITTrack; #endif /* __IITTrack_FWD_DEFINED__ */ #ifndef __IITTrackCollection_FWD_DEFINED__ #define __IITTrackCollection_FWD_DEFINED__ typedef interface IITTrackCollection IITTrackCollection; #endif /* __IITTrackCollection_FWD_DEFINED__ */ #ifndef __IITVisual_FWD_DEFINED__ #define __IITVisual_FWD_DEFINED__ typedef interface IITVisual IITVisual; #endif /* __IITVisual_FWD_DEFINED__ */ #ifndef __IITVisualCollection_FWD_DEFINED__ #define __IITVisualCollection_FWD_DEFINED__ typedef interface IITVisualCollection IITVisualCollection; #endif /* __IITVisualCollection_FWD_DEFINED__ */ #ifndef __IITWindow_FWD_DEFINED__ #define __IITWindow_FWD_DEFINED__ typedef interface IITWindow IITWindow; #endif /* __IITWindow_FWD_DEFINED__ */ #ifndef __IITBrowserWindow_FWD_DEFINED__ #define __IITBrowserWindow_FWD_DEFINED__ typedef interface IITBrowserWindow IITBrowserWindow; #endif /* __IITBrowserWindow_FWD_DEFINED__ */ #ifndef __IITWindowCollection_FWD_DEFINED__ #define __IITWindowCollection_FWD_DEFINED__ typedef interface IITWindowCollection IITWindowCollection; #endif /* __IITWindowCollection_FWD_DEFINED__ */ #ifndef __IiTunes_FWD_DEFINED__ #define __IiTunes_FWD_DEFINED__ typedef interface IiTunes IiTunes; #endif /* __IiTunes_FWD_DEFINED__ */ #ifndef ___IiTunesEvents_FWD_DEFINED__ #define ___IiTunesEvents_FWD_DEFINED__ typedef interface _IiTunesEvents _IiTunesEvents; #endif /* ___IiTunesEvents_FWD_DEFINED__ */ #ifndef ___IITConvertOperationStatusEvents_FWD_DEFINED__ #define ___IITConvertOperationStatusEvents_FWD_DEFINED__ typedef interface _IITConvertOperationStatusEvents _IITConvertOperationStatusEvents; #endif /* ___IITConvertOperationStatusEvents_FWD_DEFINED__ */ #ifndef __iTunesApp_FWD_DEFINED__ #define __iTunesApp_FWD_DEFINED__ #ifdef __cplusplus typedef class iTunesApp iTunesApp; #else typedef struct iTunesApp iTunesApp; #endif /* __cplusplus */ #endif /* __iTunesApp_FWD_DEFINED__ */ #ifndef __iTunesConvertOperationStatus_FWD_DEFINED__ #define __iTunesConvertOperationStatus_FWD_DEFINED__ #ifdef __cplusplus typedef class iTunesConvertOperationStatus iTunesConvertOperationStatus; #else typedef struct iTunesConvertOperationStatus iTunesConvertOperationStatus; #endif /* __cplusplus */ #endif /* __iTunesConvertOperationStatus_FWD_DEFINED__ */ #ifndef __IITArtwork_FWD_DEFINED__ #define __IITArtwork_FWD_DEFINED__ typedef interface IITArtwork IITArtwork; #endif /* __IITArtwork_FWD_DEFINED__ */ #ifndef __IITArtworkCollection_FWD_DEFINED__ #define __IITArtworkCollection_FWD_DEFINED__ typedef interface IITArtworkCollection IITArtworkCollection; #endif /* __IITArtworkCollection_FWD_DEFINED__ */ #ifndef __IITURLTrack_FWD_DEFINED__ #define __IITURLTrack_FWD_DEFINED__ typedef interface IITURLTrack IITURLTrack; #endif /* __IITURLTrack_FWD_DEFINED__ */ #ifndef __IITAudioCDPlaylist_FWD_DEFINED__ #define __IITAudioCDPlaylist_FWD_DEFINED__ typedef interface IITAudioCDPlaylist IITAudioCDPlaylist; #endif /* __IITAudioCDPlaylist_FWD_DEFINED__ */ #ifndef __IITPlaylistCollection_FWD_DEFINED__ #define __IITPlaylistCollection_FWD_DEFINED__ typedef interface IITPlaylistCollection IITPlaylistCollection; #endif /* __IITPlaylistCollection_FWD_DEFINED__ */ #ifndef __IITIPodSource_FWD_DEFINED__ #define __IITIPodSource_FWD_DEFINED__ typedef interface IITIPodSource IITIPodSource; #endif /* __IITIPodSource_FWD_DEFINED__ */ #ifndef __IITFileOrCDTrack_FWD_DEFINED__ #define __IITFileOrCDTrack_FWD_DEFINED__ typedef interface IITFileOrCDTrack IITFileOrCDTrack; #endif /* __IITFileOrCDTrack_FWD_DEFINED__ */ #ifndef __IITPlaylistWindow_FWD_DEFINED__ #define __IITPlaylistWindow_FWD_DEFINED__ typedef interface IITPlaylistWindow IITPlaylistWindow; #endif /* __IITPlaylistWindow_FWD_DEFINED__ */ /* header files for imported files */ #include "oaidl.h" #include "ocidl.h" #include "DispEx.h" #ifdef __cplusplus extern "C"{ #endif void * __RPC_USER MIDL_user_allocate(size_t); void __RPC_USER MIDL_user_free( void * ); /* interface __MIDL_itf_iTunesCOMInterface_0000 */ /* [local] */ typedef /* [public][v1_enum][uuid] */ DECLSPEC_UUID("4B73428D-2F56-4833-8E5D-65590E45FEAD") enum __MIDL___MIDL_itf_iTunesCOMInterface_0000_0001 { kITTypeLibrary_MajorVersion = 1, kITTypeLibrary_MinorVersion = 11 } ITVersion; typedef /* [public][v1_enum][uuid] */ DECLSPEC_UUID("4C25623B-F990-4ebd-8970-F29A70084B8C") enum __MIDL___MIDL_itf_iTunesCOMInterface_0000_0002 { ITUNES_E_USERCANCEL = 0xa0040201, ITUNES_E_OBJECTDELETED = 0xa0040202, ITUNES_E_OBJECTLOCKED = 0xa0040203, ITUNES_E_CONVERSIONINPROGRESS = 0xa0040204, ITUNES_E_MUSICSTOREDISABLED = 0xa0040205, ITUNES_E_OBJECTEXISTS = 0xa0040206, ITUNES_E_PODCASTSDISABLED = 0xa0040207 } ITErrors; extern RPC_IF_HANDLE __MIDL_itf_iTunesCOMInterface_0000_v0_0_c_ifspec; extern RPC_IF_HANDLE __MIDL_itf_iTunesCOMInterface_0000_v0_0_s_ifspec; #ifndef __iTunesLib_LIBRARY_DEFINED__ #define __iTunesLib_LIBRARY_DEFINED__ /* library iTunesLib */ /* [helpstring][uuid][version] */ typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("3D502ACA-B474-4640-A2A4-C149538345EC") enum __MIDL___MIDL_itf_iTunesCOMInterface_0272_0001 { ITPlayerStateStopped = 0, ITPlayerStatePlaying = ITPlayerStateStopped + 1, ITPlayerStateFastForward = ITPlayerStatePlaying + 1, ITPlayerStateRewind = ITPlayerStateFastForward + 1 } ITPlayerState; typedef /* [public][public][public][v1_enum][uuid] */ DECLSPEC_UUID("5319FADA-0F39-4015-82A0-48B8B871C63C") enum __MIDL___MIDL_itf_iTunesCOMInterface_0272_0002 { ITVisualSizeSmall = 0, ITVisualSizeMedium = ITVisualSizeSmall + 1, ITVisualSizeLarge = ITVisualSizeMedium + 1 } ITVisualSize; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("C8128C8D-EDE0-4f0e-AEB1-08D24A91C551") enum __MIDL___MIDL_itf_iTunesCOMInterface_0272_0003 { ITCOMDisabledReasonOther = 0, ITCOMDisabledReasonDialog = ITCOMDisabledReasonOther + 1, ITCOMDisabledReasonQuitting = ITCOMDisabledReasonDialog + 1 } ITCOMDisabledReason; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("6B1BD814-CA6E-4063-9EDA-4128D31068C1") enum __MIDL___MIDL_itf_iTunesCOMInterface_0272_0004 { ITPlayButtonStatePlayDisabled = 0, ITPlayButtonStatePlayEnabled = ITPlayButtonStatePlayDisabled + 1, ITPlayButtonStatePauseEnabled = ITPlayButtonStatePlayEnabled + 1, ITPlayButtonStatePauseDisabled = ITPlayButtonStatePauseEnabled + 1, ITPlayButtonStateStopEnabled = ITPlayButtonStatePauseDisabled + 1, ITPlayButtonStateStopDisabled = ITPlayButtonStateStopEnabled + 1 } ITPlayButtonState; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("8AF85488-2154-4e46-B65B-1972A43493EF") enum __MIDL___MIDL_itf_iTunesCOMInterface_0272_0005 { ITPlayerButtonPrevious = 0, ITPlayerButtonPlay = ITPlayerButtonPrevious + 1, ITPlayerButtonNext = ITPlayerButtonPlay + 1 } ITPlayerButton; typedef /* [public][v1_enum][uuid] */ DECLSPEC_UUID("2129AB11-F23F-485e-B15A-3F8573294F9A") enum __MIDL___MIDL_itf_iTunesCOMInterface_0272_0006 { ITPlayerButtonModifierKeyNone = 0, ITPlayerButtonModifierKeyShift = 1, ITPlayerButtonModifierKeyControl = 2, ITPlayerButtonModifierKeyAlt = 4, ITPlayerButtonModifierKeyCapsLock = 8 } ITPlayerButtonModifierKey; typedef /* [public][v1_enum][uuid] */ DECLSPEC_UUID("3194F5F4-8F52-41e6-AB8E-4221CFE29550") enum __MIDL___MIDL_itf_iTunesCOMInterface_0275_0001 { ITEventDatabaseChanged = 1, ITEventPlayerPlay = 2, ITEventPlayerStop = 3, ITEventPlayerPlayingTrackChanged = 4, ITEventUserInterfaceEnabled = 5, ITEventCOMCallsDisabled = 6, ITEventCOMCallsEnabled = 7, ITEventQuitting = 8, ITEventAboutToPromptUserToQuit = 9, ITEventSoundVolumeChanged = 10 } ITEvent; typedef /* [public][v1_enum][uuid] */ DECLSPEC_UUID("2E4D55FA-1CD3-4831-8751-0C11EC4FF6FD") enum __MIDL___MIDL_itf_iTunesCOMInterface_0276_0001 { ITConvertOperationStatusChanged = 1, ITConvertOperationComplete = 2 } ITConvertOperationStatusEvent; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("269E36A5-1728-46e4-BF04-93032C3DD51C") enum __MIDL___MIDL_itf_iTunesCOMInterface_0277_0001 { ITArtworkFormatUnknown = 0, ITArtworkFormatJPEG = ITArtworkFormatUnknown + 1, ITArtworkFormatPNG = ITArtworkFormatJPEG + 1, ITArtworkFormatBMP = ITArtworkFormatPNG + 1 } ITArtworkFormat; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("DDE76D6E-5F8C-4bda-AFA6-69E82218CFF3") enum __MIDL___MIDL_itf_iTunesCOMInterface_0283_0001 { ITPlaylistKindUnknown = 0, ITPlaylistKindLibrary = ITPlaylistKindUnknown + 1, ITPlaylistKindUser = ITPlaylistKindLibrary + 1, ITPlaylistKindCD = ITPlaylistKindUser + 1, ITPlaylistKindDevice = ITPlaylistKindCD + 1, ITPlaylistKindRadioTuner = ITPlaylistKindDevice + 1 } ITPlaylistKind; typedef /* [public][public][public][v1_enum][uuid] */ DECLSPEC_UUID("4E1D67A4-6C7A-4c7d-821C-03AF7EB10C35") enum __MIDL___MIDL_itf_iTunesCOMInterface_0283_0002 { ITPlaylistRepeatModeOff = 0, ITPlaylistRepeatModeOne = ITPlaylistRepeatModeOff + 1, ITPlaylistRepeatModeAll = ITPlaylistRepeatModeOne + 1 } ITPlaylistRepeatMode; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("BB8E7701-1E77-4972-B6C4-C70AC216F468") enum __MIDL___MIDL_itf_iTunesCOMInterface_0283_0003 { ITPlaylistPrintKindPlaylist = 0, ITPlaylistPrintKindAlbumlist = ITPlaylistPrintKindPlaylist + 1, ITPlaylistPrintKindInsert = ITPlaylistPrintKindAlbumlist + 1 } ITPlaylistPrintKind; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("58765E77-E34A-4d67-AC12-5B5BA33EA08F") enum __MIDL___MIDL_itf_iTunesCOMInterface_0283_0004 { ITPlaylistSearchFieldAll = 0, ITPlaylistSearchFieldVisible = ITPlaylistSearchFieldAll + 1, ITPlaylistSearchFieldArtists = ITPlaylistSearchFieldVisible + 1, ITPlaylistSearchFieldAlbums = ITPlaylistSearchFieldArtists + 1, ITPlaylistSearchFieldComposers = ITPlaylistSearchFieldAlbums + 1, ITPlaylistSearchFieldSongNames = ITPlaylistSearchFieldComposers + 1 } ITPlaylistSearchField; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("62BC24E6-5C77-4fb7-AA6C-B7FA40C6095D") enum __MIDL___MIDL_itf_iTunesCOMInterface_0285_0001 { ITUserPlaylistSpecialKindNone = 0, ITUserPlaylistSpecialKindPurchasedMusic = ITUserPlaylistSpecialKindNone + 1, ITUserPlaylistSpecialKindPartyShuffle = ITUserPlaylistSpecialKindPurchasedMusic + 1, ITUserPlaylistSpecialKindPodcasts = ITUserPlaylistSpecialKindPartyShuffle + 1, ITUserPlaylistSpecialKindFolder = ITUserPlaylistSpecialKindPodcasts + 1, ITUserPlaylistSpecialKindVideos = ITUserPlaylistSpecialKindFolder + 1, ITUserPlaylistSpecialKindMusic = ITUserPlaylistSpecialKindVideos + 1, ITUserPlaylistSpecialKindMovies = ITUserPlaylistSpecialKindMusic + 1, ITUserPlaylistSpecialKindTVShows = ITUserPlaylistSpecialKindMovies + 1, ITUserPlaylistSpecialKindAudiobooks = ITUserPlaylistSpecialKindTVShows + 1 } ITUserPlaylistSpecialKind; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("5F35912B-E633-4930-9E25-09489BAED75A") enum __MIDL___MIDL_itf_iTunesCOMInterface_0288_0001 { ITSourceKindUnknown = 0, ITSourceKindLibrary = ITSourceKindUnknown + 1, ITSourceKindIPod = ITSourceKindLibrary + 1, ITSourceKindAudioCD = ITSourceKindIPod + 1, ITSourceKindMP3CD = ITSourceKindAudioCD + 1, ITSourceKindDevice = ITSourceKindMP3CD + 1, ITSourceKindRadioTuner = ITSourceKindDevice + 1, ITSourceKindSharedLibrary = ITSourceKindRadioTuner + 1 } ITSourceKind; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("ACA133C5-4697-4d5f-98B1-D9881B85FE98") enum __MIDL___MIDL_itf_iTunesCOMInterface_0291_0001 { ITTrackKindUnknown = 0, ITTrackKindFile = ITTrackKindUnknown + 1, ITTrackKindCD = ITTrackKindFile + 1, ITTrackKindURL = ITTrackKindCD + 1, ITTrackKindDevice = ITTrackKindURL + 1, ITTrackKindSharedLibrary = ITTrackKindDevice + 1 } ITTrackKind; typedef /* [public][public][public][v1_enum][uuid] */ DECLSPEC_UUID("735ECC17-38CC-4d4d-A838-24AF7DCB440E") enum __MIDL___MIDL_itf_iTunesCOMInterface_0291_0002 { ITVideoKindNone = 0, ITVideoKindMovie = ITVideoKindNone + 1, ITVideoKindMusicVideo = ITVideoKindMovie + 1, ITVideoKindTVShow = ITVideoKindMusicVideo + 1 } ITVideoKind; typedef /* [public][public][public][public][public][v1_enum][uuid] */ DECLSPEC_UUID("5C75B72C-D066-4faa-8732-D9ED71A6CBD9") enum __MIDL___MIDL_itf_iTunesCOMInterface_0291_0003 { ITRatingKindUser = 0, ITRatingKindComputed = ITRatingKindUser + 1 } ITRatingKind; typedef /* [public][public][v1_enum][uuid] */ DECLSPEC_UUID("C20CE920-EFD9-4c1a-8036-95A895741214") enum __MIDL___MIDL_itf_iTunesCOMInterface_0297_0001 { ITWindowKindUnknown = 0, ITWindowKindBrowser = ITWindowKindUnknown + 1, ITWindowKindPlaylist = ITWindowKindBrowser + 1, ITWindowKindEQ = ITWindowKindPlaylist + 1, ITWindowKindArtwork = ITWindowKindEQ + 1, ITWindowKindNowPlaying = ITWindowKindArtwork + 1 } ITWindowKind; EXTERN_C const IID LIBID_iTunesLib; #ifndef __IITObject_INTERFACE_DEFINED__ #define __IITObject_INTERFACE_DEFINED__ /* interface IITObject */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITObject; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("9FAB0E27-70D7-4e3a-9965-B0C8B8869BB6") IITObject : public IDispatch { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetITObjectIDs( /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Name( /* [retval][out] */ BSTR *name) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Name( /* [in] */ BSTR name) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Index( /* [retval][out] */ long *index) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SourceID( /* [retval][out] */ long *sourceID) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_PlaylistID( /* [retval][out] */ long *playlistID) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_TrackID( /* [retval][out] */ long *trackID) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_TrackDatabaseID( /* [retval][out] */ long *databaseID) = 0; }; #else /* C style interface */ typedef struct IITObjectVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITObject * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITObject * This); ULONG ( STDMETHODCALLTYPE *Release )( IITObject * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITObject * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITObject * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITObject * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITObject * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectIDs )( IITObject * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITObject * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Name )( IITObject * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Index )( IITObject * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SourceID )( IITObject * This, /* [retval][out] */ long *sourceID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlaylistID )( IITObject * This, /* [retval][out] */ long *playlistID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackID )( IITObject * This, /* [retval][out] */ long *trackID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackDatabaseID )( IITObject * This, /* [retval][out] */ long *databaseID); END_INTERFACE } IITObjectVtbl; interface IITObject { CONST_VTBL struct IITObjectVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITObject_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITObject_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITObject_Release(This) \ (This)->lpVtbl -> Release(This) #define IITObject_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITObject_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITObject_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITObject_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITObject_GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) \ (This)->lpVtbl -> GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) #define IITObject_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITObject_put_Name(This,name) \ (This)->lpVtbl -> put_Name(This,name) #define IITObject_get_Index(This,index) \ (This)->lpVtbl -> get_Index(This,index) #define IITObject_get_SourceID(This,sourceID) \ (This)->lpVtbl -> get_SourceID(This,sourceID) #define IITObject_get_PlaylistID(This,playlistID) \ (This)->lpVtbl -> get_PlaylistID(This,playlistID) #define IITObject_get_TrackID(This,trackID) \ (This)->lpVtbl -> get_TrackID(This,trackID) #define IITObject_get_TrackDatabaseID(This,databaseID) \ (This)->lpVtbl -> get_TrackDatabaseID(This,databaseID) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITObject_GetITObjectIDs_Proxy( IITObject * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); void __RPC_STUB IITObject_GetITObjectIDs_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITObject_get_Name_Proxy( IITObject * This, /* [retval][out] */ BSTR *name); void __RPC_STUB IITObject_get_Name_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITObject_put_Name_Proxy( IITObject * This, /* [in] */ BSTR name); void __RPC_STUB IITObject_put_Name_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITObject_get_Index_Proxy( IITObject * This, /* [retval][out] */ long *index); void __RPC_STUB IITObject_get_Index_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITObject_get_SourceID_Proxy( IITObject * This, /* [retval][out] */ long *sourceID); void __RPC_STUB IITObject_get_SourceID_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITObject_get_PlaylistID_Proxy( IITObject * This, /* [retval][out] */ long *playlistID); void __RPC_STUB IITObject_get_PlaylistID_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITObject_get_TrackID_Proxy( IITObject * This, /* [retval][out] */ long *trackID); void __RPC_STUB IITObject_get_TrackID_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITObject_get_TrackDatabaseID_Proxy( IITObject * This, /* [retval][out] */ long *databaseID); void __RPC_STUB IITObject_get_TrackDatabaseID_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITObject_INTERFACE_DEFINED__ */ #ifndef __IITSource_INTERFACE_DEFINED__ #define __IITSource_INTERFACE_DEFINED__ /* interface IITSource */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITSource; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("AEC1C4D3-AEF1-4255-B892-3E3D13ADFDF9") IITSource : public IITObject { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Kind( /* [retval][out] */ ITSourceKind *kind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Capacity( /* [retval][out] */ double *capacity) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_FreeSpace( /* [retval][out] */ double *freespace) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Playlists( /* [retval][out] */ IITPlaylistCollection **iPlaylistCollection) = 0; }; #else /* C style interface */ typedef struct IITSourceVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITSource * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITSource * This); ULONG ( STDMETHODCALLTYPE *Release )( IITSource * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITSource * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITSource * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITSource * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITSource * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectIDs )( IITSource * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITSource * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Name )( IITSource * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Index )( IITSource * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SourceID )( IITSource * This, /* [retval][out] */ long *sourceID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlaylistID )( IITSource * This, /* [retval][out] */ long *playlistID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackID )( IITSource * This, /* [retval][out] */ long *trackID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackDatabaseID )( IITSource * This, /* [retval][out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITSource * This, /* [retval][out] */ ITSourceKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Capacity )( IITSource * This, /* [retval][out] */ double *capacity); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_FreeSpace )( IITSource * This, /* [retval][out] */ double *freespace); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Playlists )( IITSource * This, /* [retval][out] */ IITPlaylistCollection **iPlaylistCollection); END_INTERFACE } IITSourceVtbl; interface IITSource { CONST_VTBL struct IITSourceVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITSource_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITSource_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITSource_Release(This) \ (This)->lpVtbl -> Release(This) #define IITSource_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITSource_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITSource_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITSource_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITSource_GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) \ (This)->lpVtbl -> GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) #define IITSource_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITSource_put_Name(This,name) \ (This)->lpVtbl -> put_Name(This,name) #define IITSource_get_Index(This,index) \ (This)->lpVtbl -> get_Index(This,index) #define IITSource_get_SourceID(This,sourceID) \ (This)->lpVtbl -> get_SourceID(This,sourceID) #define IITSource_get_PlaylistID(This,playlistID) \ (This)->lpVtbl -> get_PlaylistID(This,playlistID) #define IITSource_get_TrackID(This,trackID) \ (This)->lpVtbl -> get_TrackID(This,trackID) #define IITSource_get_TrackDatabaseID(This,databaseID) \ (This)->lpVtbl -> get_TrackDatabaseID(This,databaseID) #define IITSource_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITSource_get_Capacity(This,capacity) \ (This)->lpVtbl -> get_Capacity(This,capacity) #define IITSource_get_FreeSpace(This,freespace) \ (This)->lpVtbl -> get_FreeSpace(This,freespace) #define IITSource_get_Playlists(This,iPlaylistCollection) \ (This)->lpVtbl -> get_Playlists(This,iPlaylistCollection) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITSource_get_Kind_Proxy( IITSource * This, /* [retval][out] */ ITSourceKind *kind); void __RPC_STUB IITSource_get_Kind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITSource_get_Capacity_Proxy( IITSource * This, /* [retval][out] */ double *capacity); void __RPC_STUB IITSource_get_Capacity_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITSource_get_FreeSpace_Proxy( IITSource * This, /* [retval][out] */ double *freespace); void __RPC_STUB IITSource_get_FreeSpace_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITSource_get_Playlists_Proxy( IITSource * This, /* [retval][out] */ IITPlaylistCollection **iPlaylistCollection); void __RPC_STUB IITSource_get_Playlists_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITSource_INTERFACE_DEFINED__ */ #ifndef __IITSourceCollection_INTERFACE_DEFINED__ #define __IITSourceCollection_INTERFACE_DEFINED__ /* interface IITSourceCollection */ /* [unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITSourceCollection; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("2FF6CE20-FF87-4183-B0B3-F323D047AF41") IITSourceCollection : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Count( /* [retval][out] */ long *count) = 0; virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Item( /* [in] */ long index, /* [retval][out] */ IITSource **iSource) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByName( /* [in] */ BSTR name, /* [retval][out] */ IITSource **iSource) = 0; virtual /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE get__NewEnum( /* [retval][out] */ IUnknown **iEnumerator) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByPersistentID( /* [in] */ long highID, /* [in] */ long lowID, /* [retval][out] */ IITSource **iSource) = 0; }; #else /* C style interface */ typedef struct IITSourceCollectionVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITSourceCollection * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITSourceCollection * This); ULONG ( STDMETHODCALLTYPE *Release )( IITSourceCollection * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITSourceCollection * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITSourceCollection * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITSourceCollection * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITSourceCollection * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Count )( IITSourceCollection * This, /* [retval][out] */ long *count); /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Item )( IITSourceCollection * This, /* [in] */ long index, /* [retval][out] */ IITSource **iSource); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByName )( IITSourceCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITSource **iSource); /* [helpstring][restricted][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get__NewEnum )( IITSourceCollection * This, /* [retval][out] */ IUnknown **iEnumerator); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByPersistentID )( IITSourceCollection * This, /* [in] */ long highID, /* [in] */ long lowID, /* [retval][out] */ IITSource **iSource); END_INTERFACE } IITSourceCollectionVtbl; interface IITSourceCollection { CONST_VTBL struct IITSourceCollectionVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITSourceCollection_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITSourceCollection_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITSourceCollection_Release(This) \ (This)->lpVtbl -> Release(This) #define IITSourceCollection_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITSourceCollection_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITSourceCollection_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITSourceCollection_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITSourceCollection_get_Count(This,count) \ (This)->lpVtbl -> get_Count(This,count) #define IITSourceCollection_get_Item(This,index,iSource) \ (This)->lpVtbl -> get_Item(This,index,iSource) #define IITSourceCollection_get_ItemByName(This,name,iSource) \ (This)->lpVtbl -> get_ItemByName(This,name,iSource) #define IITSourceCollection_get__NewEnum(This,iEnumerator) \ (This)->lpVtbl -> get__NewEnum(This,iEnumerator) #define IITSourceCollection_get_ItemByPersistentID(This,highID,lowID,iSource) \ (This)->lpVtbl -> get_ItemByPersistentID(This,highID,lowID,iSource) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITSourceCollection_get_Count_Proxy( IITSourceCollection * This, /* [retval][out] */ long *count); void __RPC_STUB IITSourceCollection_get_Count_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IITSourceCollection_get_Item_Proxy( IITSourceCollection * This, /* [in] */ long index, /* [retval][out] */ IITSource **iSource); void __RPC_STUB IITSourceCollection_get_Item_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITSourceCollection_get_ItemByName_Proxy( IITSourceCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITSource **iSource); void __RPC_STUB IITSourceCollection_get_ItemByName_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE IITSourceCollection_get__NewEnum_Proxy( IITSourceCollection * This, /* [retval][out] */ IUnknown **iEnumerator); void __RPC_STUB IITSourceCollection_get__NewEnum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITSourceCollection_get_ItemByPersistentID_Proxy( IITSourceCollection * This, /* [in] */ long highID, /* [in] */ long lowID, /* [retval][out] */ IITSource **iSource); void __RPC_STUB IITSourceCollection_get_ItemByPersistentID_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITSourceCollection_INTERFACE_DEFINED__ */ #ifndef __IITEncoder_INTERFACE_DEFINED__ #define __IITEncoder_INTERFACE_DEFINED__ /* interface IITEncoder */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITEncoder; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("1CF95A1C-55FE-4f45-A2D3-85AC6C504A73") IITEncoder : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Name( /* [retval][out] */ BSTR *name) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Format( /* [retval][out] */ BSTR *format) = 0; }; #else /* C style interface */ typedef struct IITEncoderVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITEncoder * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITEncoder * This); ULONG ( STDMETHODCALLTYPE *Release )( IITEncoder * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITEncoder * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITEncoder * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITEncoder * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITEncoder * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITEncoder * This, /* [retval][out] */ BSTR *name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Format )( IITEncoder * This, /* [retval][out] */ BSTR *format); END_INTERFACE } IITEncoderVtbl; interface IITEncoder { CONST_VTBL struct IITEncoderVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITEncoder_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITEncoder_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITEncoder_Release(This) \ (This)->lpVtbl -> Release(This) #define IITEncoder_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITEncoder_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITEncoder_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITEncoder_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITEncoder_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITEncoder_get_Format(This,format) \ (This)->lpVtbl -> get_Format(This,format) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEncoder_get_Name_Proxy( IITEncoder * This, /* [retval][out] */ BSTR *name); void __RPC_STUB IITEncoder_get_Name_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEncoder_get_Format_Proxy( IITEncoder * This, /* [retval][out] */ BSTR *format); void __RPC_STUB IITEncoder_get_Format_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITEncoder_INTERFACE_DEFINED__ */ #ifndef __IITEncoderCollection_INTERFACE_DEFINED__ #define __IITEncoderCollection_INTERFACE_DEFINED__ /* interface IITEncoderCollection */ /* [unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITEncoderCollection; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("8862BCA9-168D-4549-A9D5-ADB35E553BA6") IITEncoderCollection : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Count( /* [retval][out] */ long *count) = 0; virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Item( /* [in] */ long index, /* [retval][out] */ IITEncoder **iEncoder) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByName( /* [in] */ BSTR name, /* [retval][out] */ IITEncoder **iEncoder) = 0; virtual /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE get__NewEnum( /* [retval][out] */ IUnknown **iEnumerator) = 0; }; #else /* C style interface */ typedef struct IITEncoderCollectionVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITEncoderCollection * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITEncoderCollection * This); ULONG ( STDMETHODCALLTYPE *Release )( IITEncoderCollection * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITEncoderCollection * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITEncoderCollection * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITEncoderCollection * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITEncoderCollection * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Count )( IITEncoderCollection * This, /* [retval][out] */ long *count); /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Item )( IITEncoderCollection * This, /* [in] */ long index, /* [retval][out] */ IITEncoder **iEncoder); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByName )( IITEncoderCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITEncoder **iEncoder); /* [helpstring][restricted][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get__NewEnum )( IITEncoderCollection * This, /* [retval][out] */ IUnknown **iEnumerator); END_INTERFACE } IITEncoderCollectionVtbl; interface IITEncoderCollection { CONST_VTBL struct IITEncoderCollectionVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITEncoderCollection_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITEncoderCollection_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITEncoderCollection_Release(This) \ (This)->lpVtbl -> Release(This) #define IITEncoderCollection_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITEncoderCollection_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITEncoderCollection_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITEncoderCollection_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITEncoderCollection_get_Count(This,count) \ (This)->lpVtbl -> get_Count(This,count) #define IITEncoderCollection_get_Item(This,index,iEncoder) \ (This)->lpVtbl -> get_Item(This,index,iEncoder) #define IITEncoderCollection_get_ItemByName(This,name,iEncoder) \ (This)->lpVtbl -> get_ItemByName(This,name,iEncoder) #define IITEncoderCollection_get__NewEnum(This,iEnumerator) \ (This)->lpVtbl -> get__NewEnum(This,iEnumerator) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEncoderCollection_get_Count_Proxy( IITEncoderCollection * This, /* [retval][out] */ long *count); void __RPC_STUB IITEncoderCollection_get_Count_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IITEncoderCollection_get_Item_Proxy( IITEncoderCollection * This, /* [in] */ long index, /* [retval][out] */ IITEncoder **iEncoder); void __RPC_STUB IITEncoderCollection_get_Item_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEncoderCollection_get_ItemByName_Proxy( IITEncoderCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITEncoder **iEncoder); void __RPC_STUB IITEncoderCollection_get_ItemByName_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE IITEncoderCollection_get__NewEnum_Proxy( IITEncoderCollection * This, /* [retval][out] */ IUnknown **iEnumerator); void __RPC_STUB IITEncoderCollection_get__NewEnum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITEncoderCollection_INTERFACE_DEFINED__ */ #ifndef __IITEQPreset_INTERFACE_DEFINED__ #define __IITEQPreset_INTERFACE_DEFINED__ /* interface IITEQPreset */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITEQPreset; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("5BE75F4F-68FA-4212-ACB7-BE44EA569759") IITEQPreset : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Name( /* [retval][out] */ BSTR *name) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Modifiable( /* [retval][out] */ VARIANT_BOOL *isModifiable) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Preamp( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Preamp( /* [in] */ double level) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Band1( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Band1( /* [in] */ double level) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Band2( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Band2( /* [in] */ double level) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Band3( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Band3( /* [in] */ double level) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Band4( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Band4( /* [in] */ double level) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Band5( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Band5( /* [in] */ double level) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Band6( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Band6( /* [in] */ double level) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Band7( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Band7( /* [in] */ double level) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Band8( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Band8( /* [in] */ double level) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Band9( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Band9( /* [in] */ double level) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Band10( /* [retval][out] */ double *level) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Band10( /* [in] */ double level) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Delete( /* [in] */ VARIANT_BOOL updateAllTracks) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Rename( /* [in] */ BSTR newName, /* [in] */ VARIANT_BOOL updateAllTracks) = 0; }; #else /* C style interface */ typedef struct IITEQPresetVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITEQPreset * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITEQPreset * This); ULONG ( STDMETHODCALLTYPE *Release )( IITEQPreset * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITEQPreset * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITEQPreset * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITEQPreset * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITEQPreset * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITEQPreset * This, /* [retval][out] */ BSTR *name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Modifiable )( IITEQPreset * This, /* [retval][out] */ VARIANT_BOOL *isModifiable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Preamp )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Preamp )( IITEQPreset * This, /* [in] */ double level); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Band1 )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Band1 )( IITEQPreset * This, /* [in] */ double level); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Band2 )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Band2 )( IITEQPreset * This, /* [in] */ double level); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Band3 )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Band3 )( IITEQPreset * This, /* [in] */ double level); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Band4 )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Band4 )( IITEQPreset * This, /* [in] */ double level); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Band5 )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Band5 )( IITEQPreset * This, /* [in] */ double level); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Band6 )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Band6 )( IITEQPreset * This, /* [in] */ double level); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Band7 )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Band7 )( IITEQPreset * This, /* [in] */ double level); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Band8 )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Band8 )( IITEQPreset * This, /* [in] */ double level); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Band9 )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Band9 )( IITEQPreset * This, /* [in] */ double level); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Band10 )( IITEQPreset * This, /* [retval][out] */ double *level); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Band10 )( IITEQPreset * This, /* [in] */ double level); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Delete )( IITEQPreset * This, /* [in] */ VARIANT_BOOL updateAllTracks); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Rename )( IITEQPreset * This, /* [in] */ BSTR newName, /* [in] */ VARIANT_BOOL updateAllTracks); END_INTERFACE } IITEQPresetVtbl; interface IITEQPreset { CONST_VTBL struct IITEQPresetVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITEQPreset_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITEQPreset_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITEQPreset_Release(This) \ (This)->lpVtbl -> Release(This) #define IITEQPreset_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITEQPreset_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITEQPreset_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITEQPreset_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITEQPreset_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITEQPreset_get_Modifiable(This,isModifiable) \ (This)->lpVtbl -> get_Modifiable(This,isModifiable) #define IITEQPreset_get_Preamp(This,level) \ (This)->lpVtbl -> get_Preamp(This,level) #define IITEQPreset_put_Preamp(This,level) \ (This)->lpVtbl -> put_Preamp(This,level) #define IITEQPreset_get_Band1(This,level) \ (This)->lpVtbl -> get_Band1(This,level) #define IITEQPreset_put_Band1(This,level) \ (This)->lpVtbl -> put_Band1(This,level) #define IITEQPreset_get_Band2(This,level) \ (This)->lpVtbl -> get_Band2(This,level) #define IITEQPreset_put_Band2(This,level) \ (This)->lpVtbl -> put_Band2(This,level) #define IITEQPreset_get_Band3(This,level) \ (This)->lpVtbl -> get_Band3(This,level) #define IITEQPreset_put_Band3(This,level) \ (This)->lpVtbl -> put_Band3(This,level) #define IITEQPreset_get_Band4(This,level) \ (This)->lpVtbl -> get_Band4(This,level) #define IITEQPreset_put_Band4(This,level) \ (This)->lpVtbl -> put_Band4(This,level) #define IITEQPreset_get_Band5(This,level) \ (This)->lpVtbl -> get_Band5(This,level) #define IITEQPreset_put_Band5(This,level) \ (This)->lpVtbl -> put_Band5(This,level) #define IITEQPreset_get_Band6(This,level) \ (This)->lpVtbl -> get_Band6(This,level) #define IITEQPreset_put_Band6(This,level) \ (This)->lpVtbl -> put_Band6(This,level) #define IITEQPreset_get_Band7(This,level) \ (This)->lpVtbl -> get_Band7(This,level) #define IITEQPreset_put_Band7(This,level) \ (This)->lpVtbl -> put_Band7(This,level) #define IITEQPreset_get_Band8(This,level) \ (This)->lpVtbl -> get_Band8(This,level) #define IITEQPreset_put_Band8(This,level) \ (This)->lpVtbl -> put_Band8(This,level) #define IITEQPreset_get_Band9(This,level) \ (This)->lpVtbl -> get_Band9(This,level) #define IITEQPreset_put_Band9(This,level) \ (This)->lpVtbl -> put_Band9(This,level) #define IITEQPreset_get_Band10(This,level) \ (This)->lpVtbl -> get_Band10(This,level) #define IITEQPreset_put_Band10(This,level) \ (This)->lpVtbl -> put_Band10(This,level) #define IITEQPreset_Delete(This,updateAllTracks) \ (This)->lpVtbl -> Delete(This,updateAllTracks) #define IITEQPreset_Rename(This,newName,updateAllTracks) \ (This)->lpVtbl -> Rename(This,newName,updateAllTracks) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Name_Proxy( IITEQPreset * This, /* [retval][out] */ BSTR *name); void __RPC_STUB IITEQPreset_get_Name_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Modifiable_Proxy( IITEQPreset * This, /* [retval][out] */ VARIANT_BOOL *isModifiable); void __RPC_STUB IITEQPreset_get_Modifiable_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Preamp_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Preamp_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Preamp_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Preamp_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Band1_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Band1_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Band1_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Band1_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Band2_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Band2_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Band2_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Band2_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Band3_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Band3_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Band3_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Band3_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Band4_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Band4_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Band4_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Band4_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Band5_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Band5_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Band5_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Band5_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Band6_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Band6_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Band6_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Band6_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Band7_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Band7_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Band7_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Band7_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Band8_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Band8_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Band8_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Band8_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Band9_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Band9_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Band9_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Band9_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPreset_get_Band10_Proxy( IITEQPreset * This, /* [retval][out] */ double *level); void __RPC_STUB IITEQPreset_get_Band10_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITEQPreset_put_Band10_Proxy( IITEQPreset * This, /* [in] */ double level); void __RPC_STUB IITEQPreset_put_Band10_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITEQPreset_Delete_Proxy( IITEQPreset * This, /* [in] */ VARIANT_BOOL updateAllTracks); void __RPC_STUB IITEQPreset_Delete_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITEQPreset_Rename_Proxy( IITEQPreset * This, /* [in] */ BSTR newName, /* [in] */ VARIANT_BOOL updateAllTracks); void __RPC_STUB IITEQPreset_Rename_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITEQPreset_INTERFACE_DEFINED__ */ #ifndef __IITEQPresetCollection_INTERFACE_DEFINED__ #define __IITEQPresetCollection_INTERFACE_DEFINED__ /* interface IITEQPresetCollection */ /* [unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITEQPresetCollection; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("AEF4D111-3331-48da-B0C2-B468D5D61D08") IITEQPresetCollection : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Count( /* [retval][out] */ long *count) = 0; virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Item( /* [in] */ long index, /* [retval][out] */ IITEQPreset **iEQPreset) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByName( /* [in] */ BSTR name, /* [retval][out] */ IITEQPreset **iEQPreset) = 0; virtual /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE get__NewEnum( /* [retval][out] */ IUnknown **iEnumerator) = 0; }; #else /* C style interface */ typedef struct IITEQPresetCollectionVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITEQPresetCollection * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITEQPresetCollection * This); ULONG ( STDMETHODCALLTYPE *Release )( IITEQPresetCollection * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITEQPresetCollection * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITEQPresetCollection * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITEQPresetCollection * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITEQPresetCollection * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Count )( IITEQPresetCollection * This, /* [retval][out] */ long *count); /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Item )( IITEQPresetCollection * This, /* [in] */ long index, /* [retval][out] */ IITEQPreset **iEQPreset); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByName )( IITEQPresetCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITEQPreset **iEQPreset); /* [helpstring][restricted][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get__NewEnum )( IITEQPresetCollection * This, /* [retval][out] */ IUnknown **iEnumerator); END_INTERFACE } IITEQPresetCollectionVtbl; interface IITEQPresetCollection { CONST_VTBL struct IITEQPresetCollectionVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITEQPresetCollection_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITEQPresetCollection_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITEQPresetCollection_Release(This) \ (This)->lpVtbl -> Release(This) #define IITEQPresetCollection_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITEQPresetCollection_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITEQPresetCollection_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITEQPresetCollection_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITEQPresetCollection_get_Count(This,count) \ (This)->lpVtbl -> get_Count(This,count) #define IITEQPresetCollection_get_Item(This,index,iEQPreset) \ (This)->lpVtbl -> get_Item(This,index,iEQPreset) #define IITEQPresetCollection_get_ItemByName(This,name,iEQPreset) \ (This)->lpVtbl -> get_ItemByName(This,name,iEQPreset) #define IITEQPresetCollection_get__NewEnum(This,iEnumerator) \ (This)->lpVtbl -> get__NewEnum(This,iEnumerator) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPresetCollection_get_Count_Proxy( IITEQPresetCollection * This, /* [retval][out] */ long *count); void __RPC_STUB IITEQPresetCollection_get_Count_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IITEQPresetCollection_get_Item_Proxy( IITEQPresetCollection * This, /* [in] */ long index, /* [retval][out] */ IITEQPreset **iEQPreset); void __RPC_STUB IITEQPresetCollection_get_Item_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITEQPresetCollection_get_ItemByName_Proxy( IITEQPresetCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITEQPreset **iEQPreset); void __RPC_STUB IITEQPresetCollection_get_ItemByName_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE IITEQPresetCollection_get__NewEnum_Proxy( IITEQPresetCollection * This, /* [retval][out] */ IUnknown **iEnumerator); void __RPC_STUB IITEQPresetCollection_get__NewEnum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITEQPresetCollection_INTERFACE_DEFINED__ */ #ifndef __IITPlaylist_INTERFACE_DEFINED__ #define __IITPlaylist_INTERFACE_DEFINED__ /* interface IITPlaylist */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITPlaylist; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("3D5E072F-2A77-4b17-9E73-E03B77CCCCA9") IITPlaylist : public IITObject { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Delete( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE PlayFirstTrack( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Print( /* [in] */ VARIANT_BOOL showPrintDialog, /* [in] */ ITPlaylistPrintKind printKind, /* [in] */ BSTR theme) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Search( /* [in] */ BSTR searchText, /* [in] */ ITPlaylistSearchField searchFields, /* [retval][out] */ IITTrackCollection **iTrackCollection) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Kind( /* [retval][out] */ ITPlaylistKind *kind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Source( /* [retval][out] */ IITSource **iSource) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Duration( /* [retval][out] */ long *duration) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Shuffle( /* [retval][out] */ VARIANT_BOOL *isShuffle) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Shuffle( /* [in] */ VARIANT_BOOL shouldShuffle) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Size( /* [retval][out] */ double *size) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SongRepeat( /* [retval][out] */ ITPlaylistRepeatMode *repeatMode) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SongRepeat( /* [in] */ ITPlaylistRepeatMode repeatMode) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Time( /* [retval][out] */ BSTR *time) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Visible( /* [retval][out] */ VARIANT_BOOL *isVisible) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Tracks( /* [retval][out] */ IITTrackCollection **iTrackCollection) = 0; }; #else /* C style interface */ typedef struct IITPlaylistVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITPlaylist * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITPlaylist * This); ULONG ( STDMETHODCALLTYPE *Release )( IITPlaylist * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITPlaylist * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITPlaylist * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITPlaylist * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITPlaylist * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectIDs )( IITPlaylist * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITPlaylist * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Name )( IITPlaylist * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Index )( IITPlaylist * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SourceID )( IITPlaylist * This, /* [retval][out] */ long *sourceID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlaylistID )( IITPlaylist * This, /* [retval][out] */ long *playlistID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackID )( IITPlaylist * This, /* [retval][out] */ long *trackID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackDatabaseID )( IITPlaylist * This, /* [retval][out] */ long *databaseID); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Delete )( IITPlaylist * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *PlayFirstTrack )( IITPlaylist * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Print )( IITPlaylist * This, /* [in] */ VARIANT_BOOL showPrintDialog, /* [in] */ ITPlaylistPrintKind printKind, /* [in] */ BSTR theme); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Search )( IITPlaylist * This, /* [in] */ BSTR searchText, /* [in] */ ITPlaylistSearchField searchFields, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITPlaylist * This, /* [retval][out] */ ITPlaylistKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Source )( IITPlaylist * This, /* [retval][out] */ IITSource **iSource); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Duration )( IITPlaylist * This, /* [retval][out] */ long *duration); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Shuffle )( IITPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isShuffle); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Shuffle )( IITPlaylist * This, /* [in] */ VARIANT_BOOL shouldShuffle); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Size )( IITPlaylist * This, /* [retval][out] */ double *size); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SongRepeat )( IITPlaylist * This, /* [retval][out] */ ITPlaylistRepeatMode *repeatMode); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SongRepeat )( IITPlaylist * This, /* [in] */ ITPlaylistRepeatMode repeatMode); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Time )( IITPlaylist * This, /* [retval][out] */ BSTR *time); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Visible )( IITPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isVisible); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Tracks )( IITPlaylist * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); END_INTERFACE } IITPlaylistVtbl; interface IITPlaylist { CONST_VTBL struct IITPlaylistVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITPlaylist_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITPlaylist_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITPlaylist_Release(This) \ (This)->lpVtbl -> Release(This) #define IITPlaylist_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITPlaylist_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITPlaylist_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITPlaylist_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITPlaylist_GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) \ (This)->lpVtbl -> GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) #define IITPlaylist_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITPlaylist_put_Name(This,name) \ (This)->lpVtbl -> put_Name(This,name) #define IITPlaylist_get_Index(This,index) \ (This)->lpVtbl -> get_Index(This,index) #define IITPlaylist_get_SourceID(This,sourceID) \ (This)->lpVtbl -> get_SourceID(This,sourceID) #define IITPlaylist_get_PlaylistID(This,playlistID) \ (This)->lpVtbl -> get_PlaylistID(This,playlistID) #define IITPlaylist_get_TrackID(This,trackID) \ (This)->lpVtbl -> get_TrackID(This,trackID) #define IITPlaylist_get_TrackDatabaseID(This,databaseID) \ (This)->lpVtbl -> get_TrackDatabaseID(This,databaseID) #define IITPlaylist_Delete(This) \ (This)->lpVtbl -> Delete(This) #define IITPlaylist_PlayFirstTrack(This) \ (This)->lpVtbl -> PlayFirstTrack(This) #define IITPlaylist_Print(This,showPrintDialog,printKind,theme) \ (This)->lpVtbl -> Print(This,showPrintDialog,printKind,theme) #define IITPlaylist_Search(This,searchText,searchFields,iTrackCollection) \ (This)->lpVtbl -> Search(This,searchText,searchFields,iTrackCollection) #define IITPlaylist_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITPlaylist_get_Source(This,iSource) \ (This)->lpVtbl -> get_Source(This,iSource) #define IITPlaylist_get_Duration(This,duration) \ (This)->lpVtbl -> get_Duration(This,duration) #define IITPlaylist_get_Shuffle(This,isShuffle) \ (This)->lpVtbl -> get_Shuffle(This,isShuffle) #define IITPlaylist_put_Shuffle(This,shouldShuffle) \ (This)->lpVtbl -> put_Shuffle(This,shouldShuffle) #define IITPlaylist_get_Size(This,size) \ (This)->lpVtbl -> get_Size(This,size) #define IITPlaylist_get_SongRepeat(This,repeatMode) \ (This)->lpVtbl -> get_SongRepeat(This,repeatMode) #define IITPlaylist_put_SongRepeat(This,repeatMode) \ (This)->lpVtbl -> put_SongRepeat(This,repeatMode) #define IITPlaylist_get_Time(This,time) \ (This)->lpVtbl -> get_Time(This,time) #define IITPlaylist_get_Visible(This,isVisible) \ (This)->lpVtbl -> get_Visible(This,isVisible) #define IITPlaylist_get_Tracks(This,iTrackCollection) \ (This)->lpVtbl -> get_Tracks(This,iTrackCollection) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITPlaylist_Delete_Proxy( IITPlaylist * This); void __RPC_STUB IITPlaylist_Delete_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITPlaylist_PlayFirstTrack_Proxy( IITPlaylist * This); void __RPC_STUB IITPlaylist_PlayFirstTrack_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITPlaylist_Print_Proxy( IITPlaylist * This, /* [in] */ VARIANT_BOOL showPrintDialog, /* [in] */ ITPlaylistPrintKind printKind, /* [in] */ BSTR theme); void __RPC_STUB IITPlaylist_Print_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITPlaylist_Search_Proxy( IITPlaylist * This, /* [in] */ BSTR searchText, /* [in] */ ITPlaylistSearchField searchFields, /* [retval][out] */ IITTrackCollection **iTrackCollection); void __RPC_STUB IITPlaylist_Search_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylist_get_Kind_Proxy( IITPlaylist * This, /* [retval][out] */ ITPlaylistKind *kind); void __RPC_STUB IITPlaylist_get_Kind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylist_get_Source_Proxy( IITPlaylist * This, /* [retval][out] */ IITSource **iSource); void __RPC_STUB IITPlaylist_get_Source_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylist_get_Duration_Proxy( IITPlaylist * This, /* [retval][out] */ long *duration); void __RPC_STUB IITPlaylist_get_Duration_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylist_get_Shuffle_Proxy( IITPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isShuffle); void __RPC_STUB IITPlaylist_get_Shuffle_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITPlaylist_put_Shuffle_Proxy( IITPlaylist * This, /* [in] */ VARIANT_BOOL shouldShuffle); void __RPC_STUB IITPlaylist_put_Shuffle_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylist_get_Size_Proxy( IITPlaylist * This, /* [retval][out] */ double *size); void __RPC_STUB IITPlaylist_get_Size_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylist_get_SongRepeat_Proxy( IITPlaylist * This, /* [retval][out] */ ITPlaylistRepeatMode *repeatMode); void __RPC_STUB IITPlaylist_get_SongRepeat_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITPlaylist_put_SongRepeat_Proxy( IITPlaylist * This, /* [in] */ ITPlaylistRepeatMode repeatMode); void __RPC_STUB IITPlaylist_put_SongRepeat_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylist_get_Time_Proxy( IITPlaylist * This, /* [retval][out] */ BSTR *time); void __RPC_STUB IITPlaylist_get_Time_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylist_get_Visible_Proxy( IITPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isVisible); void __RPC_STUB IITPlaylist_get_Visible_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylist_get_Tracks_Proxy( IITPlaylist * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); void __RPC_STUB IITPlaylist_get_Tracks_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITPlaylist_INTERFACE_DEFINED__ */ #ifndef __IITOperationStatus_INTERFACE_DEFINED__ #define __IITOperationStatus_INTERFACE_DEFINED__ /* interface IITOperationStatus */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITOperationStatus; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("206479C9-FE32-4f9b-A18A-475AC939B479") IITOperationStatus : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_InProgress( /* [retval][out] */ VARIANT_BOOL *isInProgress) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Tracks( /* [retval][out] */ IITTrackCollection **iTrackCollection) = 0; }; #else /* C style interface */ typedef struct IITOperationStatusVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITOperationStatus * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITOperationStatus * This); ULONG ( STDMETHODCALLTYPE *Release )( IITOperationStatus * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITOperationStatus * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITOperationStatus * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITOperationStatus * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITOperationStatus * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_InProgress )( IITOperationStatus * This, /* [retval][out] */ VARIANT_BOOL *isInProgress); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Tracks )( IITOperationStatus * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); END_INTERFACE } IITOperationStatusVtbl; interface IITOperationStatus { CONST_VTBL struct IITOperationStatusVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITOperationStatus_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITOperationStatus_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITOperationStatus_Release(This) \ (This)->lpVtbl -> Release(This) #define IITOperationStatus_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITOperationStatus_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITOperationStatus_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITOperationStatus_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITOperationStatus_get_InProgress(This,isInProgress) \ (This)->lpVtbl -> get_InProgress(This,isInProgress) #define IITOperationStatus_get_Tracks(This,iTrackCollection) \ (This)->lpVtbl -> get_Tracks(This,iTrackCollection) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITOperationStatus_get_InProgress_Proxy( IITOperationStatus * This, /* [retval][out] */ VARIANT_BOOL *isInProgress); void __RPC_STUB IITOperationStatus_get_InProgress_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITOperationStatus_get_Tracks_Proxy( IITOperationStatus * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); void __RPC_STUB IITOperationStatus_get_Tracks_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITOperationStatus_INTERFACE_DEFINED__ */ #ifndef __IITConvertOperationStatus_INTERFACE_DEFINED__ #define __IITConvertOperationStatus_INTERFACE_DEFINED__ /* interface IITConvertOperationStatus */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITConvertOperationStatus; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("7063AAF6-ABA0-493b-B4FC-920A9F105875") IITConvertOperationStatus : public IITOperationStatus { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetConversionStatus( /* [out] */ BSTR *trackName, /* [out] */ long *progressValue, /* [out] */ long *maxProgressValue) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE StopConversion( void) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_TrackName( /* [retval][out] */ BSTR *trackName) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ProgressValue( /* [retval][out] */ long *progressValue) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_MaxProgressValue( /* [retval][out] */ long *maxProgressValue) = 0; }; #else /* C style interface */ typedef struct IITConvertOperationStatusVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITConvertOperationStatus * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITConvertOperationStatus * This); ULONG ( STDMETHODCALLTYPE *Release )( IITConvertOperationStatus * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITConvertOperationStatus * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITConvertOperationStatus * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITConvertOperationStatus * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITConvertOperationStatus * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_InProgress )( IITConvertOperationStatus * This, /* [retval][out] */ VARIANT_BOOL *isInProgress); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Tracks )( IITConvertOperationStatus * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetConversionStatus )( IITConvertOperationStatus * This, /* [out] */ BSTR *trackName, /* [out] */ long *progressValue, /* [out] */ long *maxProgressValue); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *StopConversion )( IITConvertOperationStatus * This); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackName )( IITConvertOperationStatus * This, /* [retval][out] */ BSTR *trackName); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ProgressValue )( IITConvertOperationStatus * This, /* [retval][out] */ long *progressValue); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_MaxProgressValue )( IITConvertOperationStatus * This, /* [retval][out] */ long *maxProgressValue); END_INTERFACE } IITConvertOperationStatusVtbl; interface IITConvertOperationStatus { CONST_VTBL struct IITConvertOperationStatusVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITConvertOperationStatus_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITConvertOperationStatus_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITConvertOperationStatus_Release(This) \ (This)->lpVtbl -> Release(This) #define IITConvertOperationStatus_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITConvertOperationStatus_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITConvertOperationStatus_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITConvertOperationStatus_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITConvertOperationStatus_get_InProgress(This,isInProgress) \ (This)->lpVtbl -> get_InProgress(This,isInProgress) #define IITConvertOperationStatus_get_Tracks(This,iTrackCollection) \ (This)->lpVtbl -> get_Tracks(This,iTrackCollection) #define IITConvertOperationStatus_GetConversionStatus(This,trackName,progressValue,maxProgressValue) \ (This)->lpVtbl -> GetConversionStatus(This,trackName,progressValue,maxProgressValue) #define IITConvertOperationStatus_StopConversion(This) \ (This)->lpVtbl -> StopConversion(This) #define IITConvertOperationStatus_get_TrackName(This,trackName) \ (This)->lpVtbl -> get_TrackName(This,trackName) #define IITConvertOperationStatus_get_ProgressValue(This,progressValue) \ (This)->lpVtbl -> get_ProgressValue(This,progressValue) #define IITConvertOperationStatus_get_MaxProgressValue(This,maxProgressValue) \ (This)->lpVtbl -> get_MaxProgressValue(This,maxProgressValue) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITConvertOperationStatus_GetConversionStatus_Proxy( IITConvertOperationStatus * This, /* [out] */ BSTR *trackName, /* [out] */ long *progressValue, /* [out] */ long *maxProgressValue); void __RPC_STUB IITConvertOperationStatus_GetConversionStatus_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITConvertOperationStatus_StopConversion_Proxy( IITConvertOperationStatus * This); void __RPC_STUB IITConvertOperationStatus_StopConversion_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITConvertOperationStatus_get_TrackName_Proxy( IITConvertOperationStatus * This, /* [retval][out] */ BSTR *trackName); void __RPC_STUB IITConvertOperationStatus_get_TrackName_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITConvertOperationStatus_get_ProgressValue_Proxy( IITConvertOperationStatus * This, /* [retval][out] */ long *progressValue); void __RPC_STUB IITConvertOperationStatus_get_ProgressValue_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITConvertOperationStatus_get_MaxProgressValue_Proxy( IITConvertOperationStatus * This, /* [retval][out] */ long *maxProgressValue); void __RPC_STUB IITConvertOperationStatus_get_MaxProgressValue_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITConvertOperationStatus_INTERFACE_DEFINED__ */ #ifndef __IITLibraryPlaylist_INTERFACE_DEFINED__ #define __IITLibraryPlaylist_INTERFACE_DEFINED__ /* interface IITLibraryPlaylist */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITLibraryPlaylist; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("53AE1704-491C-4289-94A0-958815675A3D") IITLibraryPlaylist : public IITPlaylist { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE AddFile( /* [in] */ BSTR filePath, /* [retval][out] */ IITOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE AddFiles( /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE AddURL( /* [in] */ BSTR url, /* [retval][out] */ IITURLTrack **iURLTrack) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE AddTrack( /* [in] */ VARIANT *iTrackToAdd, /* [retval][out] */ IITTrack **iAddedTrack) = 0; }; #else /* C style interface */ typedef struct IITLibraryPlaylistVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITLibraryPlaylist * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITLibraryPlaylist * This); ULONG ( STDMETHODCALLTYPE *Release )( IITLibraryPlaylist * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITLibraryPlaylist * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITLibraryPlaylist * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITLibraryPlaylist * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITLibraryPlaylist * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectIDs )( IITLibraryPlaylist * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITLibraryPlaylist * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Name )( IITLibraryPlaylist * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Index )( IITLibraryPlaylist * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SourceID )( IITLibraryPlaylist * This, /* [retval][out] */ long *sourceID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlaylistID )( IITLibraryPlaylist * This, /* [retval][out] */ long *playlistID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackID )( IITLibraryPlaylist * This, /* [retval][out] */ long *trackID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackDatabaseID )( IITLibraryPlaylist * This, /* [retval][out] */ long *databaseID); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Delete )( IITLibraryPlaylist * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *PlayFirstTrack )( IITLibraryPlaylist * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Print )( IITLibraryPlaylist * This, /* [in] */ VARIANT_BOOL showPrintDialog, /* [in] */ ITPlaylistPrintKind printKind, /* [in] */ BSTR theme); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Search )( IITLibraryPlaylist * This, /* [in] */ BSTR searchText, /* [in] */ ITPlaylistSearchField searchFields, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITLibraryPlaylist * This, /* [retval][out] */ ITPlaylistKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Source )( IITLibraryPlaylist * This, /* [retval][out] */ IITSource **iSource); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Duration )( IITLibraryPlaylist * This, /* [retval][out] */ long *duration); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Shuffle )( IITLibraryPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isShuffle); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Shuffle )( IITLibraryPlaylist * This, /* [in] */ VARIANT_BOOL shouldShuffle); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Size )( IITLibraryPlaylist * This, /* [retval][out] */ double *size); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SongRepeat )( IITLibraryPlaylist * This, /* [retval][out] */ ITPlaylistRepeatMode *repeatMode); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SongRepeat )( IITLibraryPlaylist * This, /* [in] */ ITPlaylistRepeatMode repeatMode); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Time )( IITLibraryPlaylist * This, /* [retval][out] */ BSTR *time); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Visible )( IITLibraryPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isVisible); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Tracks )( IITLibraryPlaylist * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddFile )( IITLibraryPlaylist * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddFiles )( IITLibraryPlaylist * This, /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddURL )( IITLibraryPlaylist * This, /* [in] */ BSTR url, /* [retval][out] */ IITURLTrack **iURLTrack); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddTrack )( IITLibraryPlaylist * This, /* [in] */ VARIANT *iTrackToAdd, /* [retval][out] */ IITTrack **iAddedTrack); END_INTERFACE } IITLibraryPlaylistVtbl; interface IITLibraryPlaylist { CONST_VTBL struct IITLibraryPlaylistVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITLibraryPlaylist_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITLibraryPlaylist_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITLibraryPlaylist_Release(This) \ (This)->lpVtbl -> Release(This) #define IITLibraryPlaylist_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITLibraryPlaylist_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITLibraryPlaylist_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITLibraryPlaylist_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITLibraryPlaylist_GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) \ (This)->lpVtbl -> GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) #define IITLibraryPlaylist_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITLibraryPlaylist_put_Name(This,name) \ (This)->lpVtbl -> put_Name(This,name) #define IITLibraryPlaylist_get_Index(This,index) \ (This)->lpVtbl -> get_Index(This,index) #define IITLibraryPlaylist_get_SourceID(This,sourceID) \ (This)->lpVtbl -> get_SourceID(This,sourceID) #define IITLibraryPlaylist_get_PlaylistID(This,playlistID) \ (This)->lpVtbl -> get_PlaylistID(This,playlistID) #define IITLibraryPlaylist_get_TrackID(This,trackID) \ (This)->lpVtbl -> get_TrackID(This,trackID) #define IITLibraryPlaylist_get_TrackDatabaseID(This,databaseID) \ (This)->lpVtbl -> get_TrackDatabaseID(This,databaseID) #define IITLibraryPlaylist_Delete(This) \ (This)->lpVtbl -> Delete(This) #define IITLibraryPlaylist_PlayFirstTrack(This) \ (This)->lpVtbl -> PlayFirstTrack(This) #define IITLibraryPlaylist_Print(This,showPrintDialog,printKind,theme) \ (This)->lpVtbl -> Print(This,showPrintDialog,printKind,theme) #define IITLibraryPlaylist_Search(This,searchText,searchFields,iTrackCollection) \ (This)->lpVtbl -> Search(This,searchText,searchFields,iTrackCollection) #define IITLibraryPlaylist_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITLibraryPlaylist_get_Source(This,iSource) \ (This)->lpVtbl -> get_Source(This,iSource) #define IITLibraryPlaylist_get_Duration(This,duration) \ (This)->lpVtbl -> get_Duration(This,duration) #define IITLibraryPlaylist_get_Shuffle(This,isShuffle) \ (This)->lpVtbl -> get_Shuffle(This,isShuffle) #define IITLibraryPlaylist_put_Shuffle(This,shouldShuffle) \ (This)->lpVtbl -> put_Shuffle(This,shouldShuffle) #define IITLibraryPlaylist_get_Size(This,size) \ (This)->lpVtbl -> get_Size(This,size) #define IITLibraryPlaylist_get_SongRepeat(This,repeatMode) \ (This)->lpVtbl -> get_SongRepeat(This,repeatMode) #define IITLibraryPlaylist_put_SongRepeat(This,repeatMode) \ (This)->lpVtbl -> put_SongRepeat(This,repeatMode) #define IITLibraryPlaylist_get_Time(This,time) \ (This)->lpVtbl -> get_Time(This,time) #define IITLibraryPlaylist_get_Visible(This,isVisible) \ (This)->lpVtbl -> get_Visible(This,isVisible) #define IITLibraryPlaylist_get_Tracks(This,iTrackCollection) \ (This)->lpVtbl -> get_Tracks(This,iTrackCollection) #define IITLibraryPlaylist_AddFile(This,filePath,iStatus) \ (This)->lpVtbl -> AddFile(This,filePath,iStatus) #define IITLibraryPlaylist_AddFiles(This,filePaths,iStatus) \ (This)->lpVtbl -> AddFiles(This,filePaths,iStatus) #define IITLibraryPlaylist_AddURL(This,url,iURLTrack) \ (This)->lpVtbl -> AddURL(This,url,iURLTrack) #define IITLibraryPlaylist_AddTrack(This,iTrackToAdd,iAddedTrack) \ (This)->lpVtbl -> AddTrack(This,iTrackToAdd,iAddedTrack) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITLibraryPlaylist_AddFile_Proxy( IITLibraryPlaylist * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITOperationStatus **iStatus); void __RPC_STUB IITLibraryPlaylist_AddFile_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITLibraryPlaylist_AddFiles_Proxy( IITLibraryPlaylist * This, /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITOperationStatus **iStatus); void __RPC_STUB IITLibraryPlaylist_AddFiles_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITLibraryPlaylist_AddURL_Proxy( IITLibraryPlaylist * This, /* [in] */ BSTR url, /* [retval][out] */ IITURLTrack **iURLTrack); void __RPC_STUB IITLibraryPlaylist_AddURL_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITLibraryPlaylist_AddTrack_Proxy( IITLibraryPlaylist * This, /* [in] */ VARIANT *iTrackToAdd, /* [retval][out] */ IITTrack **iAddedTrack); void __RPC_STUB IITLibraryPlaylist_AddTrack_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITLibraryPlaylist_INTERFACE_DEFINED__ */ #ifndef __IITUserPlaylist_INTERFACE_DEFINED__ #define __IITUserPlaylist_INTERFACE_DEFINED__ /* interface IITUserPlaylist */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITUserPlaylist; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("0A504DED-A0B5-465a-8A94-50E20D7DF692") IITUserPlaylist : public IITPlaylist { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE AddFile( /* [in] */ BSTR filePath, /* [retval][out] */ IITOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE AddFiles( /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE AddURL( /* [in] */ BSTR url, /* [retval][out] */ IITURLTrack **iURLTrack) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE AddTrack( /* [in] */ VARIANT *iTrackToAdd, /* [retval][out] */ IITTrack **iAddedTrack) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Shared( /* [retval][out] */ VARIANT_BOOL *isShared) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Shared( /* [in] */ VARIANT_BOOL shouldBeShared) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Smart( /* [retval][out] */ VARIANT_BOOL *isSmart) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SpecialKind( /* [retval][out] */ ITUserPlaylistSpecialKind *specialKind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Parent( /* [retval][out] */ IITUserPlaylist **iParentPlayList) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE CreatePlaylist( /* [in] */ BSTR playlistName, /* [retval][out] */ IITPlaylist **iPlaylist) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE CreateFolder( /* [in] */ BSTR folderName, /* [retval][out] */ IITPlaylist **iFolder) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Parent( /* [in] */ VARIANT *iParent) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Reveal( void) = 0; }; #else /* C style interface */ typedef struct IITUserPlaylistVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITUserPlaylist * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITUserPlaylist * This); ULONG ( STDMETHODCALLTYPE *Release )( IITUserPlaylist * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITUserPlaylist * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITUserPlaylist * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITUserPlaylist * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITUserPlaylist * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectIDs )( IITUserPlaylist * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITUserPlaylist * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Name )( IITUserPlaylist * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Index )( IITUserPlaylist * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SourceID )( IITUserPlaylist * This, /* [retval][out] */ long *sourceID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlaylistID )( IITUserPlaylist * This, /* [retval][out] */ long *playlistID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackID )( IITUserPlaylist * This, /* [retval][out] */ long *trackID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackDatabaseID )( IITUserPlaylist * This, /* [retval][out] */ long *databaseID); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Delete )( IITUserPlaylist * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *PlayFirstTrack )( IITUserPlaylist * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Print )( IITUserPlaylist * This, /* [in] */ VARIANT_BOOL showPrintDialog, /* [in] */ ITPlaylistPrintKind printKind, /* [in] */ BSTR theme); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Search )( IITUserPlaylist * This, /* [in] */ BSTR searchText, /* [in] */ ITPlaylistSearchField searchFields, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITUserPlaylist * This, /* [retval][out] */ ITPlaylistKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Source )( IITUserPlaylist * This, /* [retval][out] */ IITSource **iSource); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Duration )( IITUserPlaylist * This, /* [retval][out] */ long *duration); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Shuffle )( IITUserPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isShuffle); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Shuffle )( IITUserPlaylist * This, /* [in] */ VARIANT_BOOL shouldShuffle); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Size )( IITUserPlaylist * This, /* [retval][out] */ double *size); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SongRepeat )( IITUserPlaylist * This, /* [retval][out] */ ITPlaylistRepeatMode *repeatMode); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SongRepeat )( IITUserPlaylist * This, /* [in] */ ITPlaylistRepeatMode repeatMode); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Time )( IITUserPlaylist * This, /* [retval][out] */ BSTR *time); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Visible )( IITUserPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isVisible); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Tracks )( IITUserPlaylist * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddFile )( IITUserPlaylist * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddFiles )( IITUserPlaylist * This, /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddURL )( IITUserPlaylist * This, /* [in] */ BSTR url, /* [retval][out] */ IITURLTrack **iURLTrack); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddTrack )( IITUserPlaylist * This, /* [in] */ VARIANT *iTrackToAdd, /* [retval][out] */ IITTrack **iAddedTrack); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Shared )( IITUserPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isShared); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Shared )( IITUserPlaylist * This, /* [in] */ VARIANT_BOOL shouldBeShared); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Smart )( IITUserPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isSmart); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SpecialKind )( IITUserPlaylist * This, /* [retval][out] */ ITUserPlaylistSpecialKind *specialKind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Parent )( IITUserPlaylist * This, /* [retval][out] */ IITUserPlaylist **iParentPlayList); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *CreatePlaylist )( IITUserPlaylist * This, /* [in] */ BSTR playlistName, /* [retval][out] */ IITPlaylist **iPlaylist); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *CreateFolder )( IITUserPlaylist * This, /* [in] */ BSTR folderName, /* [retval][out] */ IITPlaylist **iFolder); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Parent )( IITUserPlaylist * This, /* [in] */ VARIANT *iParent); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Reveal )( IITUserPlaylist * This); END_INTERFACE } IITUserPlaylistVtbl; interface IITUserPlaylist { CONST_VTBL struct IITUserPlaylistVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITUserPlaylist_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITUserPlaylist_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITUserPlaylist_Release(This) \ (This)->lpVtbl -> Release(This) #define IITUserPlaylist_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITUserPlaylist_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITUserPlaylist_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITUserPlaylist_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITUserPlaylist_GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) \ (This)->lpVtbl -> GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) #define IITUserPlaylist_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITUserPlaylist_put_Name(This,name) \ (This)->lpVtbl -> put_Name(This,name) #define IITUserPlaylist_get_Index(This,index) \ (This)->lpVtbl -> get_Index(This,index) #define IITUserPlaylist_get_SourceID(This,sourceID) \ (This)->lpVtbl -> get_SourceID(This,sourceID) #define IITUserPlaylist_get_PlaylistID(This,playlistID) \ (This)->lpVtbl -> get_PlaylistID(This,playlistID) #define IITUserPlaylist_get_TrackID(This,trackID) \ (This)->lpVtbl -> get_TrackID(This,trackID) #define IITUserPlaylist_get_TrackDatabaseID(This,databaseID) \ (This)->lpVtbl -> get_TrackDatabaseID(This,databaseID) #define IITUserPlaylist_Delete(This) \ (This)->lpVtbl -> Delete(This) #define IITUserPlaylist_PlayFirstTrack(This) \ (This)->lpVtbl -> PlayFirstTrack(This) #define IITUserPlaylist_Print(This,showPrintDialog,printKind,theme) \ (This)->lpVtbl -> Print(This,showPrintDialog,printKind,theme) #define IITUserPlaylist_Search(This,searchText,searchFields,iTrackCollection) \ (This)->lpVtbl -> Search(This,searchText,searchFields,iTrackCollection) #define IITUserPlaylist_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITUserPlaylist_get_Source(This,iSource) \ (This)->lpVtbl -> get_Source(This,iSource) #define IITUserPlaylist_get_Duration(This,duration) \ (This)->lpVtbl -> get_Duration(This,duration) #define IITUserPlaylist_get_Shuffle(This,isShuffle) \ (This)->lpVtbl -> get_Shuffle(This,isShuffle) #define IITUserPlaylist_put_Shuffle(This,shouldShuffle) \ (This)->lpVtbl -> put_Shuffle(This,shouldShuffle) #define IITUserPlaylist_get_Size(This,size) \ (This)->lpVtbl -> get_Size(This,size) #define IITUserPlaylist_get_SongRepeat(This,repeatMode) \ (This)->lpVtbl -> get_SongRepeat(This,repeatMode) #define IITUserPlaylist_put_SongRepeat(This,repeatMode) \ (This)->lpVtbl -> put_SongRepeat(This,repeatMode) #define IITUserPlaylist_get_Time(This,time) \ (This)->lpVtbl -> get_Time(This,time) #define IITUserPlaylist_get_Visible(This,isVisible) \ (This)->lpVtbl -> get_Visible(This,isVisible) #define IITUserPlaylist_get_Tracks(This,iTrackCollection) \ (This)->lpVtbl -> get_Tracks(This,iTrackCollection) #define IITUserPlaylist_AddFile(This,filePath,iStatus) \ (This)->lpVtbl -> AddFile(This,filePath,iStatus) #define IITUserPlaylist_AddFiles(This,filePaths,iStatus) \ (This)->lpVtbl -> AddFiles(This,filePaths,iStatus) #define IITUserPlaylist_AddURL(This,url,iURLTrack) \ (This)->lpVtbl -> AddURL(This,url,iURLTrack) #define IITUserPlaylist_AddTrack(This,iTrackToAdd,iAddedTrack) \ (This)->lpVtbl -> AddTrack(This,iTrackToAdd,iAddedTrack) #define IITUserPlaylist_get_Shared(This,isShared) \ (This)->lpVtbl -> get_Shared(This,isShared) #define IITUserPlaylist_put_Shared(This,shouldBeShared) \ (This)->lpVtbl -> put_Shared(This,shouldBeShared) #define IITUserPlaylist_get_Smart(This,isSmart) \ (This)->lpVtbl -> get_Smart(This,isSmart) #define IITUserPlaylist_get_SpecialKind(This,specialKind) \ (This)->lpVtbl -> get_SpecialKind(This,specialKind) #define IITUserPlaylist_get_Parent(This,iParentPlayList) \ (This)->lpVtbl -> get_Parent(This,iParentPlayList) #define IITUserPlaylist_CreatePlaylist(This,playlistName,iPlaylist) \ (This)->lpVtbl -> CreatePlaylist(This,playlistName,iPlaylist) #define IITUserPlaylist_CreateFolder(This,folderName,iFolder) \ (This)->lpVtbl -> CreateFolder(This,folderName,iFolder) #define IITUserPlaylist_put_Parent(This,iParent) \ (This)->lpVtbl -> put_Parent(This,iParent) #define IITUserPlaylist_Reveal(This) \ (This)->lpVtbl -> Reveal(This) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_AddFile_Proxy( IITUserPlaylist * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITOperationStatus **iStatus); void __RPC_STUB IITUserPlaylist_AddFile_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_AddFiles_Proxy( IITUserPlaylist * This, /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITOperationStatus **iStatus); void __RPC_STUB IITUserPlaylist_AddFiles_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_AddURL_Proxy( IITUserPlaylist * This, /* [in] */ BSTR url, /* [retval][out] */ IITURLTrack **iURLTrack); void __RPC_STUB IITUserPlaylist_AddURL_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_AddTrack_Proxy( IITUserPlaylist * This, /* [in] */ VARIANT *iTrackToAdd, /* [retval][out] */ IITTrack **iAddedTrack); void __RPC_STUB IITUserPlaylist_AddTrack_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_get_Shared_Proxy( IITUserPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isShared); void __RPC_STUB IITUserPlaylist_get_Shared_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_put_Shared_Proxy( IITUserPlaylist * This, /* [in] */ VARIANT_BOOL shouldBeShared); void __RPC_STUB IITUserPlaylist_put_Shared_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_get_Smart_Proxy( IITUserPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isSmart); void __RPC_STUB IITUserPlaylist_get_Smart_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_get_SpecialKind_Proxy( IITUserPlaylist * This, /* [retval][out] */ ITUserPlaylistSpecialKind *specialKind); void __RPC_STUB IITUserPlaylist_get_SpecialKind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_get_Parent_Proxy( IITUserPlaylist * This, /* [retval][out] */ IITUserPlaylist **iParentPlayList); void __RPC_STUB IITUserPlaylist_get_Parent_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_CreatePlaylist_Proxy( IITUserPlaylist * This, /* [in] */ BSTR playlistName, /* [retval][out] */ IITPlaylist **iPlaylist); void __RPC_STUB IITUserPlaylist_CreatePlaylist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_CreateFolder_Proxy( IITUserPlaylist * This, /* [in] */ BSTR folderName, /* [retval][out] */ IITPlaylist **iFolder); void __RPC_STUB IITUserPlaylist_CreateFolder_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_put_Parent_Proxy( IITUserPlaylist * This, /* [in] */ VARIANT *iParent); void __RPC_STUB IITUserPlaylist_put_Parent_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITUserPlaylist_Reveal_Proxy( IITUserPlaylist * This); void __RPC_STUB IITUserPlaylist_Reveal_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITUserPlaylist_INTERFACE_DEFINED__ */ #ifndef __IITTrack_INTERFACE_DEFINED__ #define __IITTrack_INTERFACE_DEFINED__ /* interface IITTrack */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITTrack; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("4CB0915D-1E54-4727-BAF3-CE6CC9A225A1") IITTrack : public IITObject { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Delete( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Play( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE AddArtworkFromFile( /* [in] */ BSTR filePath, /* [retval][out] */ IITArtwork **iArtwork) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Kind( /* [retval][out] */ ITTrackKind *kind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Playlist( /* [retval][out] */ IITPlaylist **iPlaylist) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Album( /* [retval][out] */ BSTR *album) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Album( /* [in] */ BSTR album) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Artist( /* [retval][out] */ BSTR *artist) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Artist( /* [in] */ BSTR artist) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_BitRate( /* [retval][out] */ long *bitrate) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_BPM( /* [retval][out] */ long *beatsPerMinute) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_BPM( /* [in] */ long beatsPerMinute) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Comment( /* [retval][out] */ BSTR *comment) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Comment( /* [in] */ BSTR comment) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Compilation( /* [retval][out] */ VARIANT_BOOL *isCompilation) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Compilation( /* [in] */ VARIANT_BOOL shouldBeCompilation) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Composer( /* [retval][out] */ BSTR *composer) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Composer( /* [in] */ BSTR composer) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_DateAdded( /* [retval][out] */ DATE *dateAdded) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_DiscCount( /* [retval][out] */ long *discCount) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_DiscCount( /* [in] */ long discCount) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_DiscNumber( /* [retval][out] */ long *discNumber) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_DiscNumber( /* [in] */ long discNumber) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Duration( /* [retval][out] */ long *duration) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Enabled( /* [retval][out] */ VARIANT_BOOL *isEnabled) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Enabled( /* [in] */ VARIANT_BOOL shouldBeEnabled) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_EQ( /* [retval][out] */ BSTR *eq) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_EQ( /* [in] */ BSTR eq) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Finish( /* [in] */ long finish) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Finish( /* [retval][out] */ long *finish) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Genre( /* [retval][out] */ BSTR *genre) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Genre( /* [in] */ BSTR genre) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Grouping( /* [retval][out] */ BSTR *grouping) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Grouping( /* [in] */ BSTR grouping) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_KindAsString( /* [retval][out] */ BSTR *kind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ModificationDate( /* [retval][out] */ DATE *dateModified) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_PlayedCount( /* [retval][out] */ long *playedCount) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_PlayedCount( /* [in] */ long playedCount) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_PlayedDate( /* [retval][out] */ DATE *playedDate) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_PlayedDate( /* [in] */ DATE playedDate) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_PlayOrderIndex( /* [retval][out] */ long *index) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Rating( /* [retval][out] */ long *rating) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Rating( /* [in] */ long rating) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SampleRate( /* [retval][out] */ long *sampleRate) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Size( /* [retval][out] */ long *size) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Start( /* [retval][out] */ long *start) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Start( /* [in] */ long start) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Time( /* [retval][out] */ BSTR *time) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_TrackCount( /* [retval][out] */ long *trackCount) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_TrackCount( /* [in] */ long trackCount) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_TrackNumber( /* [retval][out] */ long *trackNumber) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_TrackNumber( /* [in] */ long trackNumber) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_VolumeAdjustment( /* [retval][out] */ long *volumeAdjustment) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_VolumeAdjustment( /* [in] */ long volumeAdjustment) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Year( /* [retval][out] */ long *year) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Year( /* [in] */ long year) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Artwork( /* [retval][out] */ IITArtworkCollection **iArtworkCollection) = 0; }; #else /* C style interface */ typedef struct IITTrackVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITTrack * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITTrack * This); ULONG ( STDMETHODCALLTYPE *Release )( IITTrack * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITTrack * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITTrack * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITTrack * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITTrack * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectIDs )( IITTrack * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITTrack * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Name )( IITTrack * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Index )( IITTrack * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SourceID )( IITTrack * This, /* [retval][out] */ long *sourceID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlaylistID )( IITTrack * This, /* [retval][out] */ long *playlistID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackID )( IITTrack * This, /* [retval][out] */ long *trackID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackDatabaseID )( IITTrack * This, /* [retval][out] */ long *databaseID); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Delete )( IITTrack * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Play )( IITTrack * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddArtworkFromFile )( IITTrack * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITArtwork **iArtwork); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITTrack * This, /* [retval][out] */ ITTrackKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Playlist )( IITTrack * This, /* [retval][out] */ IITPlaylist **iPlaylist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Album )( IITTrack * This, /* [retval][out] */ BSTR *album); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Album )( IITTrack * This, /* [in] */ BSTR album); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Artist )( IITTrack * This, /* [retval][out] */ BSTR *artist); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Artist )( IITTrack * This, /* [in] */ BSTR artist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_BitRate )( IITTrack * This, /* [retval][out] */ long *bitrate); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_BPM )( IITTrack * This, /* [retval][out] */ long *beatsPerMinute); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_BPM )( IITTrack * This, /* [in] */ long beatsPerMinute); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Comment )( IITTrack * This, /* [retval][out] */ BSTR *comment); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Comment )( IITTrack * This, /* [in] */ BSTR comment); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Compilation )( IITTrack * This, /* [retval][out] */ VARIANT_BOOL *isCompilation); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Compilation )( IITTrack * This, /* [in] */ VARIANT_BOOL shouldBeCompilation); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Composer )( IITTrack * This, /* [retval][out] */ BSTR *composer); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Composer )( IITTrack * This, /* [in] */ BSTR composer); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DateAdded )( IITTrack * This, /* [retval][out] */ DATE *dateAdded); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DiscCount )( IITTrack * This, /* [retval][out] */ long *discCount); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_DiscCount )( IITTrack * This, /* [in] */ long discCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DiscNumber )( IITTrack * This, /* [retval][out] */ long *discNumber); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_DiscNumber )( IITTrack * This, /* [in] */ long discNumber); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Duration )( IITTrack * This, /* [retval][out] */ long *duration); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Enabled )( IITTrack * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Enabled )( IITTrack * This, /* [in] */ VARIANT_BOOL shouldBeEnabled); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_EQ )( IITTrack * This, /* [retval][out] */ BSTR *eq); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_EQ )( IITTrack * This, /* [in] */ BSTR eq); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Finish )( IITTrack * This, /* [in] */ long finish); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Finish )( IITTrack * This, /* [retval][out] */ long *finish); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Genre )( IITTrack * This, /* [retval][out] */ BSTR *genre); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Genre )( IITTrack * This, /* [in] */ BSTR genre); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Grouping )( IITTrack * This, /* [retval][out] */ BSTR *grouping); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Grouping )( IITTrack * This, /* [in] */ BSTR grouping); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_KindAsString )( IITTrack * This, /* [retval][out] */ BSTR *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ModificationDate )( IITTrack * This, /* [retval][out] */ DATE *dateModified); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayedCount )( IITTrack * This, /* [retval][out] */ long *playedCount); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_PlayedCount )( IITTrack * This, /* [in] */ long playedCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayedDate )( IITTrack * This, /* [retval][out] */ DATE *playedDate); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_PlayedDate )( IITTrack * This, /* [in] */ DATE playedDate); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayOrderIndex )( IITTrack * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Rating )( IITTrack * This, /* [retval][out] */ long *rating); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Rating )( IITTrack * This, /* [in] */ long rating); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SampleRate )( IITTrack * This, /* [retval][out] */ long *sampleRate); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Size )( IITTrack * This, /* [retval][out] */ long *size); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Start )( IITTrack * This, /* [retval][out] */ long *start); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Start )( IITTrack * This, /* [in] */ long start); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Time )( IITTrack * This, /* [retval][out] */ BSTR *time); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackCount )( IITTrack * This, /* [retval][out] */ long *trackCount); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_TrackCount )( IITTrack * This, /* [in] */ long trackCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackNumber )( IITTrack * This, /* [retval][out] */ long *trackNumber); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_TrackNumber )( IITTrack * This, /* [in] */ long trackNumber); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_VolumeAdjustment )( IITTrack * This, /* [retval][out] */ long *volumeAdjustment); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_VolumeAdjustment )( IITTrack * This, /* [in] */ long volumeAdjustment); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Year )( IITTrack * This, /* [retval][out] */ long *year); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Year )( IITTrack * This, /* [in] */ long year); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Artwork )( IITTrack * This, /* [retval][out] */ IITArtworkCollection **iArtworkCollection); END_INTERFACE } IITTrackVtbl; interface IITTrack { CONST_VTBL struct IITTrackVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITTrack_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITTrack_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITTrack_Release(This) \ (This)->lpVtbl -> Release(This) #define IITTrack_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITTrack_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITTrack_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITTrack_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITTrack_GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) \ (This)->lpVtbl -> GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) #define IITTrack_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITTrack_put_Name(This,name) \ (This)->lpVtbl -> put_Name(This,name) #define IITTrack_get_Index(This,index) \ (This)->lpVtbl -> get_Index(This,index) #define IITTrack_get_SourceID(This,sourceID) \ (This)->lpVtbl -> get_SourceID(This,sourceID) #define IITTrack_get_PlaylistID(This,playlistID) \ (This)->lpVtbl -> get_PlaylistID(This,playlistID) #define IITTrack_get_TrackID(This,trackID) \ (This)->lpVtbl -> get_TrackID(This,trackID) #define IITTrack_get_TrackDatabaseID(This,databaseID) \ (This)->lpVtbl -> get_TrackDatabaseID(This,databaseID) #define IITTrack_Delete(This) \ (This)->lpVtbl -> Delete(This) #define IITTrack_Play(This) \ (This)->lpVtbl -> Play(This) #define IITTrack_AddArtworkFromFile(This,filePath,iArtwork) \ (This)->lpVtbl -> AddArtworkFromFile(This,filePath,iArtwork) #define IITTrack_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITTrack_get_Playlist(This,iPlaylist) \ (This)->lpVtbl -> get_Playlist(This,iPlaylist) #define IITTrack_get_Album(This,album) \ (This)->lpVtbl -> get_Album(This,album) #define IITTrack_put_Album(This,album) \ (This)->lpVtbl -> put_Album(This,album) #define IITTrack_get_Artist(This,artist) \ (This)->lpVtbl -> get_Artist(This,artist) #define IITTrack_put_Artist(This,artist) \ (This)->lpVtbl -> put_Artist(This,artist) #define IITTrack_get_BitRate(This,bitrate) \ (This)->lpVtbl -> get_BitRate(This,bitrate) #define IITTrack_get_BPM(This,beatsPerMinute) \ (This)->lpVtbl -> get_BPM(This,beatsPerMinute) #define IITTrack_put_BPM(This,beatsPerMinute) \ (This)->lpVtbl -> put_BPM(This,beatsPerMinute) #define IITTrack_get_Comment(This,comment) \ (This)->lpVtbl -> get_Comment(This,comment) #define IITTrack_put_Comment(This,comment) \ (This)->lpVtbl -> put_Comment(This,comment) #define IITTrack_get_Compilation(This,isCompilation) \ (This)->lpVtbl -> get_Compilation(This,isCompilation) #define IITTrack_put_Compilation(This,shouldBeCompilation) \ (This)->lpVtbl -> put_Compilation(This,shouldBeCompilation) #define IITTrack_get_Composer(This,composer) \ (This)->lpVtbl -> get_Composer(This,composer) #define IITTrack_put_Composer(This,composer) \ (This)->lpVtbl -> put_Composer(This,composer) #define IITTrack_get_DateAdded(This,dateAdded) \ (This)->lpVtbl -> get_DateAdded(This,dateAdded) #define IITTrack_get_DiscCount(This,discCount) \ (This)->lpVtbl -> get_DiscCount(This,discCount) #define IITTrack_put_DiscCount(This,discCount) \ (This)->lpVtbl -> put_DiscCount(This,discCount) #define IITTrack_get_DiscNumber(This,discNumber) \ (This)->lpVtbl -> get_DiscNumber(This,discNumber) #define IITTrack_put_DiscNumber(This,discNumber) \ (This)->lpVtbl -> put_DiscNumber(This,discNumber) #define IITTrack_get_Duration(This,duration) \ (This)->lpVtbl -> get_Duration(This,duration) #define IITTrack_get_Enabled(This,isEnabled) \ (This)->lpVtbl -> get_Enabled(This,isEnabled) #define IITTrack_put_Enabled(This,shouldBeEnabled) \ (This)->lpVtbl -> put_Enabled(This,shouldBeEnabled) #define IITTrack_get_EQ(This,eq) \ (This)->lpVtbl -> get_EQ(This,eq) #define IITTrack_put_EQ(This,eq) \ (This)->lpVtbl -> put_EQ(This,eq) #define IITTrack_put_Finish(This,finish) \ (This)->lpVtbl -> put_Finish(This,finish) #define IITTrack_get_Finish(This,finish) \ (This)->lpVtbl -> get_Finish(This,finish) #define IITTrack_get_Genre(This,genre) \ (This)->lpVtbl -> get_Genre(This,genre) #define IITTrack_put_Genre(This,genre) \ (This)->lpVtbl -> put_Genre(This,genre) #define IITTrack_get_Grouping(This,grouping) \ (This)->lpVtbl -> get_Grouping(This,grouping) #define IITTrack_put_Grouping(This,grouping) \ (This)->lpVtbl -> put_Grouping(This,grouping) #define IITTrack_get_KindAsString(This,kind) \ (This)->lpVtbl -> get_KindAsString(This,kind) #define IITTrack_get_ModificationDate(This,dateModified) \ (This)->lpVtbl -> get_ModificationDate(This,dateModified) #define IITTrack_get_PlayedCount(This,playedCount) \ (This)->lpVtbl -> get_PlayedCount(This,playedCount) #define IITTrack_put_PlayedCount(This,playedCount) \ (This)->lpVtbl -> put_PlayedCount(This,playedCount) #define IITTrack_get_PlayedDate(This,playedDate) \ (This)->lpVtbl -> get_PlayedDate(This,playedDate) #define IITTrack_put_PlayedDate(This,playedDate) \ (This)->lpVtbl -> put_PlayedDate(This,playedDate) #define IITTrack_get_PlayOrderIndex(This,index) \ (This)->lpVtbl -> get_PlayOrderIndex(This,index) #define IITTrack_get_Rating(This,rating) \ (This)->lpVtbl -> get_Rating(This,rating) #define IITTrack_put_Rating(This,rating) \ (This)->lpVtbl -> put_Rating(This,rating) #define IITTrack_get_SampleRate(This,sampleRate) \ (This)->lpVtbl -> get_SampleRate(This,sampleRate) #define IITTrack_get_Size(This,size) \ (This)->lpVtbl -> get_Size(This,size) #define IITTrack_get_Start(This,start) \ (This)->lpVtbl -> get_Start(This,start) #define IITTrack_put_Start(This,start) \ (This)->lpVtbl -> put_Start(This,start) #define IITTrack_get_Time(This,time) \ (This)->lpVtbl -> get_Time(This,time) #define IITTrack_get_TrackCount(This,trackCount) \ (This)->lpVtbl -> get_TrackCount(This,trackCount) #define IITTrack_put_TrackCount(This,trackCount) \ (This)->lpVtbl -> put_TrackCount(This,trackCount) #define IITTrack_get_TrackNumber(This,trackNumber) \ (This)->lpVtbl -> get_TrackNumber(This,trackNumber) #define IITTrack_put_TrackNumber(This,trackNumber) \ (This)->lpVtbl -> put_TrackNumber(This,trackNumber) #define IITTrack_get_VolumeAdjustment(This,volumeAdjustment) \ (This)->lpVtbl -> get_VolumeAdjustment(This,volumeAdjustment) #define IITTrack_put_VolumeAdjustment(This,volumeAdjustment) \ (This)->lpVtbl -> put_VolumeAdjustment(This,volumeAdjustment) #define IITTrack_get_Year(This,year) \ (This)->lpVtbl -> get_Year(This,year) #define IITTrack_put_Year(This,year) \ (This)->lpVtbl -> put_Year(This,year) #define IITTrack_get_Artwork(This,iArtworkCollection) \ (This)->lpVtbl -> get_Artwork(This,iArtworkCollection) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITTrack_Delete_Proxy( IITTrack * This); void __RPC_STUB IITTrack_Delete_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITTrack_Play_Proxy( IITTrack * This); void __RPC_STUB IITTrack_Play_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITTrack_AddArtworkFromFile_Proxy( IITTrack * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITArtwork **iArtwork); void __RPC_STUB IITTrack_AddArtworkFromFile_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Kind_Proxy( IITTrack * This, /* [retval][out] */ ITTrackKind *kind); void __RPC_STUB IITTrack_get_Kind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Playlist_Proxy( IITTrack * This, /* [retval][out] */ IITPlaylist **iPlaylist); void __RPC_STUB IITTrack_get_Playlist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Album_Proxy( IITTrack * This, /* [retval][out] */ BSTR *album); void __RPC_STUB IITTrack_get_Album_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Album_Proxy( IITTrack * This, /* [in] */ BSTR album); void __RPC_STUB IITTrack_put_Album_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Artist_Proxy( IITTrack * This, /* [retval][out] */ BSTR *artist); void __RPC_STUB IITTrack_get_Artist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Artist_Proxy( IITTrack * This, /* [in] */ BSTR artist); void __RPC_STUB IITTrack_put_Artist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_BitRate_Proxy( IITTrack * This, /* [retval][out] */ long *bitrate); void __RPC_STUB IITTrack_get_BitRate_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_BPM_Proxy( IITTrack * This, /* [retval][out] */ long *beatsPerMinute); void __RPC_STUB IITTrack_get_BPM_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_BPM_Proxy( IITTrack * This, /* [in] */ long beatsPerMinute); void __RPC_STUB IITTrack_put_BPM_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Comment_Proxy( IITTrack * This, /* [retval][out] */ BSTR *comment); void __RPC_STUB IITTrack_get_Comment_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Comment_Proxy( IITTrack * This, /* [in] */ BSTR comment); void __RPC_STUB IITTrack_put_Comment_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Compilation_Proxy( IITTrack * This, /* [retval][out] */ VARIANT_BOOL *isCompilation); void __RPC_STUB IITTrack_get_Compilation_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Compilation_Proxy( IITTrack * This, /* [in] */ VARIANT_BOOL shouldBeCompilation); void __RPC_STUB IITTrack_put_Compilation_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Composer_Proxy( IITTrack * This, /* [retval][out] */ BSTR *composer); void __RPC_STUB IITTrack_get_Composer_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Composer_Proxy( IITTrack * This, /* [in] */ BSTR composer); void __RPC_STUB IITTrack_put_Composer_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_DateAdded_Proxy( IITTrack * This, /* [retval][out] */ DATE *dateAdded); void __RPC_STUB IITTrack_get_DateAdded_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_DiscCount_Proxy( IITTrack * This, /* [retval][out] */ long *discCount); void __RPC_STUB IITTrack_get_DiscCount_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_DiscCount_Proxy( IITTrack * This, /* [in] */ long discCount); void __RPC_STUB IITTrack_put_DiscCount_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_DiscNumber_Proxy( IITTrack * This, /* [retval][out] */ long *discNumber); void __RPC_STUB IITTrack_get_DiscNumber_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_DiscNumber_Proxy( IITTrack * This, /* [in] */ long discNumber); void __RPC_STUB IITTrack_put_DiscNumber_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Duration_Proxy( IITTrack * This, /* [retval][out] */ long *duration); void __RPC_STUB IITTrack_get_Duration_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Enabled_Proxy( IITTrack * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); void __RPC_STUB IITTrack_get_Enabled_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Enabled_Proxy( IITTrack * This, /* [in] */ VARIANT_BOOL shouldBeEnabled); void __RPC_STUB IITTrack_put_Enabled_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_EQ_Proxy( IITTrack * This, /* [retval][out] */ BSTR *eq); void __RPC_STUB IITTrack_get_EQ_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_EQ_Proxy( IITTrack * This, /* [in] */ BSTR eq); void __RPC_STUB IITTrack_put_EQ_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Finish_Proxy( IITTrack * This, /* [in] */ long finish); void __RPC_STUB IITTrack_put_Finish_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Finish_Proxy( IITTrack * This, /* [retval][out] */ long *finish); void __RPC_STUB IITTrack_get_Finish_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Genre_Proxy( IITTrack * This, /* [retval][out] */ BSTR *genre); void __RPC_STUB IITTrack_get_Genre_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Genre_Proxy( IITTrack * This, /* [in] */ BSTR genre); void __RPC_STUB IITTrack_put_Genre_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Grouping_Proxy( IITTrack * This, /* [retval][out] */ BSTR *grouping); void __RPC_STUB IITTrack_get_Grouping_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Grouping_Proxy( IITTrack * This, /* [in] */ BSTR grouping); void __RPC_STUB IITTrack_put_Grouping_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_KindAsString_Proxy( IITTrack * This, /* [retval][out] */ BSTR *kind); void __RPC_STUB IITTrack_get_KindAsString_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_ModificationDate_Proxy( IITTrack * This, /* [retval][out] */ DATE *dateModified); void __RPC_STUB IITTrack_get_ModificationDate_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_PlayedCount_Proxy( IITTrack * This, /* [retval][out] */ long *playedCount); void __RPC_STUB IITTrack_get_PlayedCount_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_PlayedCount_Proxy( IITTrack * This, /* [in] */ long playedCount); void __RPC_STUB IITTrack_put_PlayedCount_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_PlayedDate_Proxy( IITTrack * This, /* [retval][out] */ DATE *playedDate); void __RPC_STUB IITTrack_get_PlayedDate_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_PlayedDate_Proxy( IITTrack * This, /* [in] */ DATE playedDate); void __RPC_STUB IITTrack_put_PlayedDate_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_PlayOrderIndex_Proxy( IITTrack * This, /* [retval][out] */ long *index); void __RPC_STUB IITTrack_get_PlayOrderIndex_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Rating_Proxy( IITTrack * This, /* [retval][out] */ long *rating); void __RPC_STUB IITTrack_get_Rating_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Rating_Proxy( IITTrack * This, /* [in] */ long rating); void __RPC_STUB IITTrack_put_Rating_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_SampleRate_Proxy( IITTrack * This, /* [retval][out] */ long *sampleRate); void __RPC_STUB IITTrack_get_SampleRate_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Size_Proxy( IITTrack * This, /* [retval][out] */ long *size); void __RPC_STUB IITTrack_get_Size_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Start_Proxy( IITTrack * This, /* [retval][out] */ long *start); void __RPC_STUB IITTrack_get_Start_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Start_Proxy( IITTrack * This, /* [in] */ long start); void __RPC_STUB IITTrack_put_Start_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Time_Proxy( IITTrack * This, /* [retval][out] */ BSTR *time); void __RPC_STUB IITTrack_get_Time_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_TrackCount_Proxy( IITTrack * This, /* [retval][out] */ long *trackCount); void __RPC_STUB IITTrack_get_TrackCount_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_TrackCount_Proxy( IITTrack * This, /* [in] */ long trackCount); void __RPC_STUB IITTrack_put_TrackCount_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_TrackNumber_Proxy( IITTrack * This, /* [retval][out] */ long *trackNumber); void __RPC_STUB IITTrack_get_TrackNumber_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_TrackNumber_Proxy( IITTrack * This, /* [in] */ long trackNumber); void __RPC_STUB IITTrack_put_TrackNumber_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_VolumeAdjustment_Proxy( IITTrack * This, /* [retval][out] */ long *volumeAdjustment); void __RPC_STUB IITTrack_get_VolumeAdjustment_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_VolumeAdjustment_Proxy( IITTrack * This, /* [in] */ long volumeAdjustment); void __RPC_STUB IITTrack_put_VolumeAdjustment_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Year_Proxy( IITTrack * This, /* [retval][out] */ long *year); void __RPC_STUB IITTrack_get_Year_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITTrack_put_Year_Proxy( IITTrack * This, /* [in] */ long year); void __RPC_STUB IITTrack_put_Year_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrack_get_Artwork_Proxy( IITTrack * This, /* [retval][out] */ IITArtworkCollection **iArtworkCollection); void __RPC_STUB IITTrack_get_Artwork_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITTrack_INTERFACE_DEFINED__ */ #ifndef __IITTrackCollection_INTERFACE_DEFINED__ #define __IITTrackCollection_INTERFACE_DEFINED__ /* interface IITTrackCollection */ /* [unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITTrackCollection; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("755D76F1-6B85-4ce4-8F5F-F88D9743DCD8") IITTrackCollection : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Count( /* [retval][out] */ long *count) = 0; virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Item( /* [in] */ long index, /* [retval][out] */ IITTrack **iTrack) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByPlayOrder( /* [in] */ long index, /* [retval][out] */ IITTrack **iTrack) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByName( /* [in] */ BSTR name, /* [retval][out] */ IITTrack **iTrack) = 0; virtual /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE get__NewEnum( /* [retval][out] */ IUnknown **iEnumerator) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByPersistentID( /* [in] */ long highID, /* [in] */ long lowID, /* [retval][out] */ IITTrack **iTrack) = 0; }; #else /* C style interface */ typedef struct IITTrackCollectionVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITTrackCollection * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITTrackCollection * This); ULONG ( STDMETHODCALLTYPE *Release )( IITTrackCollection * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITTrackCollection * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITTrackCollection * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITTrackCollection * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITTrackCollection * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Count )( IITTrackCollection * This, /* [retval][out] */ long *count); /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Item )( IITTrackCollection * This, /* [in] */ long index, /* [retval][out] */ IITTrack **iTrack); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByPlayOrder )( IITTrackCollection * This, /* [in] */ long index, /* [retval][out] */ IITTrack **iTrack); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByName )( IITTrackCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITTrack **iTrack); /* [helpstring][restricted][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get__NewEnum )( IITTrackCollection * This, /* [retval][out] */ IUnknown **iEnumerator); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByPersistentID )( IITTrackCollection * This, /* [in] */ long highID, /* [in] */ long lowID, /* [retval][out] */ IITTrack **iTrack); END_INTERFACE } IITTrackCollectionVtbl; interface IITTrackCollection { CONST_VTBL struct IITTrackCollectionVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITTrackCollection_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITTrackCollection_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITTrackCollection_Release(This) \ (This)->lpVtbl -> Release(This) #define IITTrackCollection_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITTrackCollection_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITTrackCollection_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITTrackCollection_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITTrackCollection_get_Count(This,count) \ (This)->lpVtbl -> get_Count(This,count) #define IITTrackCollection_get_Item(This,index,iTrack) \ (This)->lpVtbl -> get_Item(This,index,iTrack) #define IITTrackCollection_get_ItemByPlayOrder(This,index,iTrack) \ (This)->lpVtbl -> get_ItemByPlayOrder(This,index,iTrack) #define IITTrackCollection_get_ItemByName(This,name,iTrack) \ (This)->lpVtbl -> get_ItemByName(This,name,iTrack) #define IITTrackCollection_get__NewEnum(This,iEnumerator) \ (This)->lpVtbl -> get__NewEnum(This,iEnumerator) #define IITTrackCollection_get_ItemByPersistentID(This,highID,lowID,iTrack) \ (This)->lpVtbl -> get_ItemByPersistentID(This,highID,lowID,iTrack) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrackCollection_get_Count_Proxy( IITTrackCollection * This, /* [retval][out] */ long *count); void __RPC_STUB IITTrackCollection_get_Count_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IITTrackCollection_get_Item_Proxy( IITTrackCollection * This, /* [in] */ long index, /* [retval][out] */ IITTrack **iTrack); void __RPC_STUB IITTrackCollection_get_Item_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrackCollection_get_ItemByPlayOrder_Proxy( IITTrackCollection * This, /* [in] */ long index, /* [retval][out] */ IITTrack **iTrack); void __RPC_STUB IITTrackCollection_get_ItemByPlayOrder_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrackCollection_get_ItemByName_Proxy( IITTrackCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITTrack **iTrack); void __RPC_STUB IITTrackCollection_get_ItemByName_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE IITTrackCollection_get__NewEnum_Proxy( IITTrackCollection * This, /* [retval][out] */ IUnknown **iEnumerator); void __RPC_STUB IITTrackCollection_get__NewEnum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITTrackCollection_get_ItemByPersistentID_Proxy( IITTrackCollection * This, /* [in] */ long highID, /* [in] */ long lowID, /* [retval][out] */ IITTrack **iTrack); void __RPC_STUB IITTrackCollection_get_ItemByPersistentID_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITTrackCollection_INTERFACE_DEFINED__ */ #ifndef __IITVisual_INTERFACE_DEFINED__ #define __IITVisual_INTERFACE_DEFINED__ /* interface IITVisual */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITVisual; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("340F3315-ED72-4c09-9ACF-21EB4BDF9931") IITVisual : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Name( /* [retval][out] */ BSTR *name) = 0; }; #else /* C style interface */ typedef struct IITVisualVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITVisual * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITVisual * This); ULONG ( STDMETHODCALLTYPE *Release )( IITVisual * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITVisual * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITVisual * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITVisual * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITVisual * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITVisual * This, /* [retval][out] */ BSTR *name); END_INTERFACE } IITVisualVtbl; interface IITVisual { CONST_VTBL struct IITVisualVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITVisual_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITVisual_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITVisual_Release(This) \ (This)->lpVtbl -> Release(This) #define IITVisual_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITVisual_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITVisual_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITVisual_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITVisual_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITVisual_get_Name_Proxy( IITVisual * This, /* [retval][out] */ BSTR *name); void __RPC_STUB IITVisual_get_Name_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITVisual_INTERFACE_DEFINED__ */ #ifndef __IITVisualCollection_INTERFACE_DEFINED__ #define __IITVisualCollection_INTERFACE_DEFINED__ /* interface IITVisualCollection */ /* [unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITVisualCollection; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("88A4CCDD-114F-4043-B69B-84D4E6274957") IITVisualCollection : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Count( /* [retval][out] */ long *count) = 0; virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Item( /* [in] */ long index, /* [retval][out] */ IITVisual **iVisual) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByName( /* [in] */ BSTR name, /* [retval][out] */ IITVisual **iVisual) = 0; virtual /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE get__NewEnum( /* [retval][out] */ IUnknown **iEnumerator) = 0; }; #else /* C style interface */ typedef struct IITVisualCollectionVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITVisualCollection * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITVisualCollection * This); ULONG ( STDMETHODCALLTYPE *Release )( IITVisualCollection * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITVisualCollection * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITVisualCollection * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITVisualCollection * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITVisualCollection * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Count )( IITVisualCollection * This, /* [retval][out] */ long *count); /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Item )( IITVisualCollection * This, /* [in] */ long index, /* [retval][out] */ IITVisual **iVisual); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByName )( IITVisualCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITVisual **iVisual); /* [helpstring][restricted][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get__NewEnum )( IITVisualCollection * This, /* [retval][out] */ IUnknown **iEnumerator); END_INTERFACE } IITVisualCollectionVtbl; interface IITVisualCollection { CONST_VTBL struct IITVisualCollectionVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITVisualCollection_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITVisualCollection_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITVisualCollection_Release(This) \ (This)->lpVtbl -> Release(This) #define IITVisualCollection_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITVisualCollection_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITVisualCollection_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITVisualCollection_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITVisualCollection_get_Count(This,count) \ (This)->lpVtbl -> get_Count(This,count) #define IITVisualCollection_get_Item(This,index,iVisual) \ (This)->lpVtbl -> get_Item(This,index,iVisual) #define IITVisualCollection_get_ItemByName(This,name,iVisual) \ (This)->lpVtbl -> get_ItemByName(This,name,iVisual) #define IITVisualCollection_get__NewEnum(This,iEnumerator) \ (This)->lpVtbl -> get__NewEnum(This,iEnumerator) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITVisualCollection_get_Count_Proxy( IITVisualCollection * This, /* [retval][out] */ long *count); void __RPC_STUB IITVisualCollection_get_Count_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IITVisualCollection_get_Item_Proxy( IITVisualCollection * This, /* [in] */ long index, /* [retval][out] */ IITVisual **iVisual); void __RPC_STUB IITVisualCollection_get_Item_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITVisualCollection_get_ItemByName_Proxy( IITVisualCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITVisual **iVisual); void __RPC_STUB IITVisualCollection_get_ItemByName_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE IITVisualCollection_get__NewEnum_Proxy( IITVisualCollection * This, /* [retval][out] */ IUnknown **iEnumerator); void __RPC_STUB IITVisualCollection_get__NewEnum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITVisualCollection_INTERFACE_DEFINED__ */ #ifndef __IITWindow_INTERFACE_DEFINED__ #define __IITWindow_INTERFACE_DEFINED__ /* interface IITWindow */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITWindow; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("370D7BE0-3A89-4a42-B902-C75FC138BE09") IITWindow : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Name( /* [retval][out] */ BSTR *name) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Kind( /* [retval][out] */ ITWindowKind *kind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Visible( /* [retval][out] */ VARIANT_BOOL *isVisible) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Visible( /* [in] */ VARIANT_BOOL shouldBeVisible) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Resizable( /* [retval][out] */ VARIANT_BOOL *isResizable) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Minimized( /* [retval][out] */ VARIANT_BOOL *isMinimized) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Minimized( /* [in] */ VARIANT_BOOL shouldBeMinimized) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Maximizable( /* [retval][out] */ VARIANT_BOOL *isMaximizable) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Maximized( /* [retval][out] */ VARIANT_BOOL *isMaximized) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Maximized( /* [in] */ VARIANT_BOOL shouldBeMaximized) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Zoomable( /* [retval][out] */ VARIANT_BOOL *isZoomable) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Zoomed( /* [retval][out] */ VARIANT_BOOL *isZoomed) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Zoomed( /* [in] */ VARIANT_BOOL shouldBeZoomed) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Top( /* [retval][out] */ long *top) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Top( /* [in] */ long top) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Left( /* [retval][out] */ long *left) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Left( /* [in] */ long left) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Bottom( /* [retval][out] */ long *bottom) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Bottom( /* [in] */ long bottom) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Right( /* [retval][out] */ long *right) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Right( /* [in] */ long right) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Width( /* [retval][out] */ long *width) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Width( /* [in] */ long width) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Height( /* [retval][out] */ long *height) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Height( /* [in] */ long height) = 0; }; #else /* C style interface */ typedef struct IITWindowVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITWindow * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITWindow * This); ULONG ( STDMETHODCALLTYPE *Release )( IITWindow * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITWindow * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITWindow * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITWindow * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITWindow * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITWindow * This, /* [retval][out] */ BSTR *name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITWindow * This, /* [retval][out] */ ITWindowKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Visible )( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isVisible); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Visible )( IITWindow * This, /* [in] */ VARIANT_BOOL shouldBeVisible); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Resizable )( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isResizable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Minimized )( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isMinimized); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Minimized )( IITWindow * This, /* [in] */ VARIANT_BOOL shouldBeMinimized); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Maximizable )( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isMaximizable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Maximized )( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isMaximized); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Maximized )( IITWindow * This, /* [in] */ VARIANT_BOOL shouldBeMaximized); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Zoomable )( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isZoomable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Zoomed )( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isZoomed); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Zoomed )( IITWindow * This, /* [in] */ VARIANT_BOOL shouldBeZoomed); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Top )( IITWindow * This, /* [retval][out] */ long *top); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Top )( IITWindow * This, /* [in] */ long top); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Left )( IITWindow * This, /* [retval][out] */ long *left); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Left )( IITWindow * This, /* [in] */ long left); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Bottom )( IITWindow * This, /* [retval][out] */ long *bottom); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Bottom )( IITWindow * This, /* [in] */ long bottom); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Right )( IITWindow * This, /* [retval][out] */ long *right); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Right )( IITWindow * This, /* [in] */ long right); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Width )( IITWindow * This, /* [retval][out] */ long *width); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Width )( IITWindow * This, /* [in] */ long width); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Height )( IITWindow * This, /* [retval][out] */ long *height); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Height )( IITWindow * This, /* [in] */ long height); END_INTERFACE } IITWindowVtbl; interface IITWindow { CONST_VTBL struct IITWindowVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITWindow_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITWindow_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITWindow_Release(This) \ (This)->lpVtbl -> Release(This) #define IITWindow_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITWindow_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITWindow_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITWindow_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITWindow_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITWindow_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITWindow_get_Visible(This,isVisible) \ (This)->lpVtbl -> get_Visible(This,isVisible) #define IITWindow_put_Visible(This,shouldBeVisible) \ (This)->lpVtbl -> put_Visible(This,shouldBeVisible) #define IITWindow_get_Resizable(This,isResizable) \ (This)->lpVtbl -> get_Resizable(This,isResizable) #define IITWindow_get_Minimized(This,isMinimized) \ (This)->lpVtbl -> get_Minimized(This,isMinimized) #define IITWindow_put_Minimized(This,shouldBeMinimized) \ (This)->lpVtbl -> put_Minimized(This,shouldBeMinimized) #define IITWindow_get_Maximizable(This,isMaximizable) \ (This)->lpVtbl -> get_Maximizable(This,isMaximizable) #define IITWindow_get_Maximized(This,isMaximized) \ (This)->lpVtbl -> get_Maximized(This,isMaximized) #define IITWindow_put_Maximized(This,shouldBeMaximized) \ (This)->lpVtbl -> put_Maximized(This,shouldBeMaximized) #define IITWindow_get_Zoomable(This,isZoomable) \ (This)->lpVtbl -> get_Zoomable(This,isZoomable) #define IITWindow_get_Zoomed(This,isZoomed) \ (This)->lpVtbl -> get_Zoomed(This,isZoomed) #define IITWindow_put_Zoomed(This,shouldBeZoomed) \ (This)->lpVtbl -> put_Zoomed(This,shouldBeZoomed) #define IITWindow_get_Top(This,top) \ (This)->lpVtbl -> get_Top(This,top) #define IITWindow_put_Top(This,top) \ (This)->lpVtbl -> put_Top(This,top) #define IITWindow_get_Left(This,left) \ (This)->lpVtbl -> get_Left(This,left) #define IITWindow_put_Left(This,left) \ (This)->lpVtbl -> put_Left(This,left) #define IITWindow_get_Bottom(This,bottom) \ (This)->lpVtbl -> get_Bottom(This,bottom) #define IITWindow_put_Bottom(This,bottom) \ (This)->lpVtbl -> put_Bottom(This,bottom) #define IITWindow_get_Right(This,right) \ (This)->lpVtbl -> get_Right(This,right) #define IITWindow_put_Right(This,right) \ (This)->lpVtbl -> put_Right(This,right) #define IITWindow_get_Width(This,width) \ (This)->lpVtbl -> get_Width(This,width) #define IITWindow_put_Width(This,width) \ (This)->lpVtbl -> put_Width(This,width) #define IITWindow_get_Height(This,height) \ (This)->lpVtbl -> get_Height(This,height) #define IITWindow_put_Height(This,height) \ (This)->lpVtbl -> put_Height(This,height) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Name_Proxy( IITWindow * This, /* [retval][out] */ BSTR *name); void __RPC_STUB IITWindow_get_Name_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Kind_Proxy( IITWindow * This, /* [retval][out] */ ITWindowKind *kind); void __RPC_STUB IITWindow_get_Kind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Visible_Proxy( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isVisible); void __RPC_STUB IITWindow_get_Visible_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITWindow_put_Visible_Proxy( IITWindow * This, /* [in] */ VARIANT_BOOL shouldBeVisible); void __RPC_STUB IITWindow_put_Visible_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Resizable_Proxy( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isResizable); void __RPC_STUB IITWindow_get_Resizable_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Minimized_Proxy( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isMinimized); void __RPC_STUB IITWindow_get_Minimized_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITWindow_put_Minimized_Proxy( IITWindow * This, /* [in] */ VARIANT_BOOL shouldBeMinimized); void __RPC_STUB IITWindow_put_Minimized_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Maximizable_Proxy( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isMaximizable); void __RPC_STUB IITWindow_get_Maximizable_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Maximized_Proxy( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isMaximized); void __RPC_STUB IITWindow_get_Maximized_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITWindow_put_Maximized_Proxy( IITWindow * This, /* [in] */ VARIANT_BOOL shouldBeMaximized); void __RPC_STUB IITWindow_put_Maximized_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Zoomable_Proxy( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isZoomable); void __RPC_STUB IITWindow_get_Zoomable_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Zoomed_Proxy( IITWindow * This, /* [retval][out] */ VARIANT_BOOL *isZoomed); void __RPC_STUB IITWindow_get_Zoomed_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITWindow_put_Zoomed_Proxy( IITWindow * This, /* [in] */ VARIANT_BOOL shouldBeZoomed); void __RPC_STUB IITWindow_put_Zoomed_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Top_Proxy( IITWindow * This, /* [retval][out] */ long *top); void __RPC_STUB IITWindow_get_Top_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITWindow_put_Top_Proxy( IITWindow * This, /* [in] */ long top); void __RPC_STUB IITWindow_put_Top_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Left_Proxy( IITWindow * This, /* [retval][out] */ long *left); void __RPC_STUB IITWindow_get_Left_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITWindow_put_Left_Proxy( IITWindow * This, /* [in] */ long left); void __RPC_STUB IITWindow_put_Left_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Bottom_Proxy( IITWindow * This, /* [retval][out] */ long *bottom); void __RPC_STUB IITWindow_get_Bottom_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITWindow_put_Bottom_Proxy( IITWindow * This, /* [in] */ long bottom); void __RPC_STUB IITWindow_put_Bottom_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Right_Proxy( IITWindow * This, /* [retval][out] */ long *right); void __RPC_STUB IITWindow_get_Right_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITWindow_put_Right_Proxy( IITWindow * This, /* [in] */ long right); void __RPC_STUB IITWindow_put_Right_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Width_Proxy( IITWindow * This, /* [retval][out] */ long *width); void __RPC_STUB IITWindow_get_Width_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITWindow_put_Width_Proxy( IITWindow * This, /* [in] */ long width); void __RPC_STUB IITWindow_put_Width_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindow_get_Height_Proxy( IITWindow * This, /* [retval][out] */ long *height); void __RPC_STUB IITWindow_get_Height_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITWindow_put_Height_Proxy( IITWindow * This, /* [in] */ long height); void __RPC_STUB IITWindow_put_Height_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITWindow_INTERFACE_DEFINED__ */ #ifndef __IITBrowserWindow_INTERFACE_DEFINED__ #define __IITBrowserWindow_INTERFACE_DEFINED__ /* interface IITBrowserWindow */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITBrowserWindow; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("C999F455-C4D5-4aa4-8277-F99753699974") IITBrowserWindow : public IITWindow { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_MiniPlayer( /* [retval][out] */ VARIANT_BOOL *isMiniPlayer) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_MiniPlayer( /* [in] */ VARIANT_BOOL shouldBeMiniPlayer) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SelectedTracks( /* [retval][out] */ IITTrackCollection **iTrackCollection) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SelectedPlaylist( /* [retval][out] */ IITPlaylist **iPlaylist) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SelectedPlaylist( /* [in] */ VARIANT *iPlaylist) = 0; }; #else /* C style interface */ typedef struct IITBrowserWindowVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITBrowserWindow * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITBrowserWindow * This); ULONG ( STDMETHODCALLTYPE *Release )( IITBrowserWindow * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITBrowserWindow * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITBrowserWindow * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITBrowserWindow * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITBrowserWindow * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITBrowserWindow * This, /* [retval][out] */ BSTR *name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITBrowserWindow * This, /* [retval][out] */ ITWindowKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Visible )( IITBrowserWindow * This, /* [retval][out] */ VARIANT_BOOL *isVisible); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Visible )( IITBrowserWindow * This, /* [in] */ VARIANT_BOOL shouldBeVisible); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Resizable )( IITBrowserWindow * This, /* [retval][out] */ VARIANT_BOOL *isResizable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Minimized )( IITBrowserWindow * This, /* [retval][out] */ VARIANT_BOOL *isMinimized); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Minimized )( IITBrowserWindow * This, /* [in] */ VARIANT_BOOL shouldBeMinimized); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Maximizable )( IITBrowserWindow * This, /* [retval][out] */ VARIANT_BOOL *isMaximizable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Maximized )( IITBrowserWindow * This, /* [retval][out] */ VARIANT_BOOL *isMaximized); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Maximized )( IITBrowserWindow * This, /* [in] */ VARIANT_BOOL shouldBeMaximized); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Zoomable )( IITBrowserWindow * This, /* [retval][out] */ VARIANT_BOOL *isZoomable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Zoomed )( IITBrowserWindow * This, /* [retval][out] */ VARIANT_BOOL *isZoomed); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Zoomed )( IITBrowserWindow * This, /* [in] */ VARIANT_BOOL shouldBeZoomed); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Top )( IITBrowserWindow * This, /* [retval][out] */ long *top); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Top )( IITBrowserWindow * This, /* [in] */ long top); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Left )( IITBrowserWindow * This, /* [retval][out] */ long *left); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Left )( IITBrowserWindow * This, /* [in] */ long left); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Bottom )( IITBrowserWindow * This, /* [retval][out] */ long *bottom); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Bottom )( IITBrowserWindow * This, /* [in] */ long bottom); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Right )( IITBrowserWindow * This, /* [retval][out] */ long *right); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Right )( IITBrowserWindow * This, /* [in] */ long right); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Width )( IITBrowserWindow * This, /* [retval][out] */ long *width); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Width )( IITBrowserWindow * This, /* [in] */ long width); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Height )( IITBrowserWindow * This, /* [retval][out] */ long *height); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Height )( IITBrowserWindow * This, /* [in] */ long height); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_MiniPlayer )( IITBrowserWindow * This, /* [retval][out] */ VARIANT_BOOL *isMiniPlayer); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_MiniPlayer )( IITBrowserWindow * This, /* [in] */ VARIANT_BOOL shouldBeMiniPlayer); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SelectedTracks )( IITBrowserWindow * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SelectedPlaylist )( IITBrowserWindow * This, /* [retval][out] */ IITPlaylist **iPlaylist); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SelectedPlaylist )( IITBrowserWindow * This, /* [in] */ VARIANT *iPlaylist); END_INTERFACE } IITBrowserWindowVtbl; interface IITBrowserWindow { CONST_VTBL struct IITBrowserWindowVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITBrowserWindow_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITBrowserWindow_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITBrowserWindow_Release(This) \ (This)->lpVtbl -> Release(This) #define IITBrowserWindow_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITBrowserWindow_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITBrowserWindow_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITBrowserWindow_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITBrowserWindow_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITBrowserWindow_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITBrowserWindow_get_Visible(This,isVisible) \ (This)->lpVtbl -> get_Visible(This,isVisible) #define IITBrowserWindow_put_Visible(This,shouldBeVisible) \ (This)->lpVtbl -> put_Visible(This,shouldBeVisible) #define IITBrowserWindow_get_Resizable(This,isResizable) \ (This)->lpVtbl -> get_Resizable(This,isResizable) #define IITBrowserWindow_get_Minimized(This,isMinimized) \ (This)->lpVtbl -> get_Minimized(This,isMinimized) #define IITBrowserWindow_put_Minimized(This,shouldBeMinimized) \ (This)->lpVtbl -> put_Minimized(This,shouldBeMinimized) #define IITBrowserWindow_get_Maximizable(This,isMaximizable) \ (This)->lpVtbl -> get_Maximizable(This,isMaximizable) #define IITBrowserWindow_get_Maximized(This,isMaximized) \ (This)->lpVtbl -> get_Maximized(This,isMaximized) #define IITBrowserWindow_put_Maximized(This,shouldBeMaximized) \ (This)->lpVtbl -> put_Maximized(This,shouldBeMaximized) #define IITBrowserWindow_get_Zoomable(This,isZoomable) \ (This)->lpVtbl -> get_Zoomable(This,isZoomable) #define IITBrowserWindow_get_Zoomed(This,isZoomed) \ (This)->lpVtbl -> get_Zoomed(This,isZoomed) #define IITBrowserWindow_put_Zoomed(This,shouldBeZoomed) \ (This)->lpVtbl -> put_Zoomed(This,shouldBeZoomed) #define IITBrowserWindow_get_Top(This,top) \ (This)->lpVtbl -> get_Top(This,top) #define IITBrowserWindow_put_Top(This,top) \ (This)->lpVtbl -> put_Top(This,top) #define IITBrowserWindow_get_Left(This,left) \ (This)->lpVtbl -> get_Left(This,left) #define IITBrowserWindow_put_Left(This,left) \ (This)->lpVtbl -> put_Left(This,left) #define IITBrowserWindow_get_Bottom(This,bottom) \ (This)->lpVtbl -> get_Bottom(This,bottom) #define IITBrowserWindow_put_Bottom(This,bottom) \ (This)->lpVtbl -> put_Bottom(This,bottom) #define IITBrowserWindow_get_Right(This,right) \ (This)->lpVtbl -> get_Right(This,right) #define IITBrowserWindow_put_Right(This,right) \ (This)->lpVtbl -> put_Right(This,right) #define IITBrowserWindow_get_Width(This,width) \ (This)->lpVtbl -> get_Width(This,width) #define IITBrowserWindow_put_Width(This,width) \ (This)->lpVtbl -> put_Width(This,width) #define IITBrowserWindow_get_Height(This,height) \ (This)->lpVtbl -> get_Height(This,height) #define IITBrowserWindow_put_Height(This,height) \ (This)->lpVtbl -> put_Height(This,height) #define IITBrowserWindow_get_MiniPlayer(This,isMiniPlayer) \ (This)->lpVtbl -> get_MiniPlayer(This,isMiniPlayer) #define IITBrowserWindow_put_MiniPlayer(This,shouldBeMiniPlayer) \ (This)->lpVtbl -> put_MiniPlayer(This,shouldBeMiniPlayer) #define IITBrowserWindow_get_SelectedTracks(This,iTrackCollection) \ (This)->lpVtbl -> get_SelectedTracks(This,iTrackCollection) #define IITBrowserWindow_get_SelectedPlaylist(This,iPlaylist) \ (This)->lpVtbl -> get_SelectedPlaylist(This,iPlaylist) #define IITBrowserWindow_put_SelectedPlaylist(This,iPlaylist) \ (This)->lpVtbl -> put_SelectedPlaylist(This,iPlaylist) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITBrowserWindow_get_MiniPlayer_Proxy( IITBrowserWindow * This, /* [retval][out] */ VARIANT_BOOL *isMiniPlayer); void __RPC_STUB IITBrowserWindow_get_MiniPlayer_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITBrowserWindow_put_MiniPlayer_Proxy( IITBrowserWindow * This, /* [in] */ VARIANT_BOOL shouldBeMiniPlayer); void __RPC_STUB IITBrowserWindow_put_MiniPlayer_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITBrowserWindow_get_SelectedTracks_Proxy( IITBrowserWindow * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); void __RPC_STUB IITBrowserWindow_get_SelectedTracks_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITBrowserWindow_get_SelectedPlaylist_Proxy( IITBrowserWindow * This, /* [retval][out] */ IITPlaylist **iPlaylist); void __RPC_STUB IITBrowserWindow_get_SelectedPlaylist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITBrowserWindow_put_SelectedPlaylist_Proxy( IITBrowserWindow * This, /* [in] */ VARIANT *iPlaylist); void __RPC_STUB IITBrowserWindow_put_SelectedPlaylist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITBrowserWindow_INTERFACE_DEFINED__ */ #ifndef __IITWindowCollection_INTERFACE_DEFINED__ #define __IITWindowCollection_INTERFACE_DEFINED__ /* interface IITWindowCollection */ /* [unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITWindowCollection; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("3D8DE381-6C0E-481f-A865-E2385F59FA43") IITWindowCollection : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Count( /* [retval][out] */ long *count) = 0; virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Item( /* [in] */ long index, /* [retval][out] */ IITWindow **iWindow) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByName( /* [in] */ BSTR name, /* [retval][out] */ IITWindow **iWindow) = 0; virtual /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE get__NewEnum( /* [retval][out] */ IUnknown **iEnumerator) = 0; }; #else /* C style interface */ typedef struct IITWindowCollectionVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITWindowCollection * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITWindowCollection * This); ULONG ( STDMETHODCALLTYPE *Release )( IITWindowCollection * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITWindowCollection * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITWindowCollection * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITWindowCollection * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITWindowCollection * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Count )( IITWindowCollection * This, /* [retval][out] */ long *count); /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Item )( IITWindowCollection * This, /* [in] */ long index, /* [retval][out] */ IITWindow **iWindow); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByName )( IITWindowCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITWindow **iWindow); /* [helpstring][restricted][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get__NewEnum )( IITWindowCollection * This, /* [retval][out] */ IUnknown **iEnumerator); END_INTERFACE } IITWindowCollectionVtbl; interface IITWindowCollection { CONST_VTBL struct IITWindowCollectionVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITWindowCollection_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITWindowCollection_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITWindowCollection_Release(This) \ (This)->lpVtbl -> Release(This) #define IITWindowCollection_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITWindowCollection_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITWindowCollection_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITWindowCollection_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITWindowCollection_get_Count(This,count) \ (This)->lpVtbl -> get_Count(This,count) #define IITWindowCollection_get_Item(This,index,iWindow) \ (This)->lpVtbl -> get_Item(This,index,iWindow) #define IITWindowCollection_get_ItemByName(This,name,iWindow) \ (This)->lpVtbl -> get_ItemByName(This,name,iWindow) #define IITWindowCollection_get__NewEnum(This,iEnumerator) \ (This)->lpVtbl -> get__NewEnum(This,iEnumerator) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindowCollection_get_Count_Proxy( IITWindowCollection * This, /* [retval][out] */ long *count); void __RPC_STUB IITWindowCollection_get_Count_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IITWindowCollection_get_Item_Proxy( IITWindowCollection * This, /* [in] */ long index, /* [retval][out] */ IITWindow **iWindow); void __RPC_STUB IITWindowCollection_get_Item_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITWindowCollection_get_ItemByName_Proxy( IITWindowCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITWindow **iWindow); void __RPC_STUB IITWindowCollection_get_ItemByName_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE IITWindowCollection_get__NewEnum_Proxy( IITWindowCollection * This, /* [retval][out] */ IUnknown **iEnumerator); void __RPC_STUB IITWindowCollection_get__NewEnum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITWindowCollection_INTERFACE_DEFINED__ */ #ifndef __IiTunes_INTERFACE_DEFINED__ #define __IiTunes_INTERFACE_DEFINED__ /* interface IiTunes */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IiTunes; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("9DD6680B-3EDC-40db-A771-E6FE4832E34A") IiTunes : public IDispatch { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE BackTrack( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE FastForward( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE NextTrack( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Pause( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Play( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE PlayFile( /* [in] */ BSTR filePath) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE PlayPause( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE PreviousTrack( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Resume( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Rewind( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Stop( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ConvertFile( /* [in] */ BSTR filePath, /* [retval][out] */ IITOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ConvertFiles( /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ConvertTrack( /* [in] */ VARIANT *iTrackToConvert, /* [retval][out] */ IITOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ConvertTracks( /* [in] */ VARIANT *iTracksToConvert, /* [retval][out] */ IITOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE CheckVersion( /* [in] */ long majorVersion, /* [in] */ long minorVersion, /* [retval][out] */ VARIANT_BOOL *isCompatible) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetITObjectByID( /* [in] */ long sourceID, /* [in] */ long playlistID, /* [in] */ long trackID, /* [in] */ long databaseID, /* [retval][out] */ IITObject **iObject) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE CreatePlaylist( /* [in] */ BSTR playlistName, /* [retval][out] */ IITPlaylist **iPlaylist) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE OpenURL( /* [in] */ BSTR url) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GotoMusicStoreHomePage( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE UpdateIPod( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Authorize( /* [in] */ long numElems, /* [size_is][in] */ VARIANT data[ ], /* [size_is][in] */ BSTR names[ ]) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Quit( void) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Sources( /* [retval][out] */ IITSourceCollection **iSourceCollection) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Encoders( /* [retval][out] */ IITEncoderCollection **iEncoderCollection) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_EQPresets( /* [retval][out] */ IITEQPresetCollection **iEQPresetCollection) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Visuals( /* [retval][out] */ IITVisualCollection **iVisualCollection) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Windows( /* [retval][out] */ IITWindowCollection **iWindowCollection) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SoundVolume( /* [retval][out] */ long *volume) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SoundVolume( /* [in] */ long volume) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Mute( /* [retval][out] */ VARIANT_BOOL *isMuted) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Mute( /* [in] */ VARIANT_BOOL shouldMute) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_PlayerState( /* [retval][out] */ ITPlayerState *playerState) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_PlayerPosition( /* [retval][out] */ long *playerPos) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_PlayerPosition( /* [in] */ long playerPos) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_CurrentEncoder( /* [retval][out] */ IITEncoder **iEncoder) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_CurrentEncoder( /* [in] */ IITEncoder *iEncoder) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_VisualsEnabled( /* [retval][out] */ VARIANT_BOOL *isEnabled) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_VisualsEnabled( /* [in] */ VARIANT_BOOL shouldEnable) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_FullScreenVisuals( /* [retval][out] */ VARIANT_BOOL *isFullScreen) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_FullScreenVisuals( /* [in] */ VARIANT_BOOL shouldUseFullScreen) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_VisualSize( /* [retval][out] */ ITVisualSize *visualSize) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_VisualSize( /* [in] */ ITVisualSize visualSize) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_CurrentVisual( /* [retval][out] */ IITVisual **iVisual) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_CurrentVisual( /* [in] */ IITVisual *iVisual) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_EQEnabled( /* [retval][out] */ VARIANT_BOOL *isEnabled) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_EQEnabled( /* [in] */ VARIANT_BOOL shouldEnable) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_CurrentEQPreset( /* [retval][out] */ IITEQPreset **iEQPreset) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_CurrentEQPreset( /* [in] */ IITEQPreset *iEQPreset) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_CurrentStreamTitle( /* [retval][out] */ BSTR *streamTitle) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_CurrentStreamURL( /* [retval][out] */ BSTR *streamURL) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_BrowserWindow( /* [retval][out] */ IITBrowserWindow **iBrowserWindow) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_EQWindow( /* [retval][out] */ IITWindow **iEQWindow) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_LibrarySource( /* [retval][out] */ IITSource **iLibrarySource) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_LibraryPlaylist( /* [retval][out] */ IITLibraryPlaylist **iLibraryPlaylist) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_CurrentTrack( /* [retval][out] */ IITTrack **iTrack) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_CurrentPlaylist( /* [retval][out] */ IITPlaylist **iPlaylist) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SelectedTracks( /* [retval][out] */ IITTrackCollection **iTrackCollection) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Version( /* [retval][out] */ BSTR *version) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE SetOptions( /* [in] */ long options) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ConvertFile2( /* [in] */ BSTR filePath, /* [retval][out] */ IITConvertOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ConvertFiles2( /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITConvertOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ConvertTrack2( /* [in] */ VARIANT *iTrackToConvert, /* [retval][out] */ IITConvertOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ConvertTracks2( /* [in] */ VARIANT *iTracksToConvert, /* [retval][out] */ IITConvertOperationStatus **iStatus) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_AppCommandMessageProcessingEnabled( /* [retval][out] */ VARIANT_BOOL *isEnabled) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_AppCommandMessageProcessingEnabled( /* [in] */ VARIANT_BOOL shouldEnable) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ForceToForegroundOnDialog( /* [retval][out] */ VARIANT_BOOL *forceToForegroundOnDialog) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_ForceToForegroundOnDialog( /* [in] */ VARIANT_BOOL forceToForegroundOnDialog) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE CreateEQPreset( /* [in] */ BSTR eqPresetName, /* [retval][out] */ IITEQPreset **iEQPreset) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE CreatePlaylistInSource( /* [in] */ BSTR playlistName, /* [in] */ VARIANT *iSource, /* [retval][out] */ IITPlaylist **iPlaylist) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetPlayerButtonsState( /* [out] */ VARIANT_BOOL *previousEnabled, /* [out] */ ITPlayButtonState *playPauseStopState, /* [out] */ VARIANT_BOOL *nextEnabled) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE PlayerButtonClicked( /* [in] */ ITPlayerButton playerButton, /* [in] */ long playerButtonModifierKeys) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_CanSetShuffle( /* [in] */ VARIANT *iPlaylist, /* [retval][out] */ VARIANT_BOOL *canSetShuffle) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_CanSetSongRepeat( /* [in] */ VARIANT *iPlaylist, /* [retval][out] */ VARIANT_BOOL *canSetSongRepeat) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ConvertOperationStatus( /* [retval][out] */ IITConvertOperationStatus **iStatus) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE SubscribeToPodcast( /* [in] */ BSTR url) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE UpdatePodcastFeeds( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE CreateFolder( /* [in] */ BSTR folderName, /* [retval][out] */ IITPlaylist **iFolder) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE CreateFolderInSource( /* [in] */ BSTR folderName, /* [in] */ VARIANT *iSource, /* [retval][out] */ IITPlaylist **iFolder) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SoundVolumeControlEnabled( /* [retval][out] */ VARIANT_BOOL *isEnabled) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_LibraryXMLPath( /* [retval][out] */ BSTR *filePath) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ITObjectPersistentIDHigh( /* [in] */ VARIANT *iObject, /* [retval][out] */ long *highID) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ITObjectPersistentIDLow( /* [in] */ VARIANT *iObject, /* [retval][out] */ long *lowID) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetITObjectPersistentIDs( /* [in] */ VARIANT *iObject, /* [out] */ long *highID, /* [out] */ long *lowID) = 0; }; #else /* C style interface */ typedef struct IiTunesVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IiTunes * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IiTunes * This); ULONG ( STDMETHODCALLTYPE *Release )( IiTunes * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IiTunes * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IiTunes * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IiTunes * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IiTunes * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *BackTrack )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *FastForward )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *NextTrack )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Pause )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Play )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *PlayFile )( IiTunes * This, /* [in] */ BSTR filePath); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *PlayPause )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *PreviousTrack )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Resume )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Rewind )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Stop )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *ConvertFile )( IiTunes * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *ConvertFiles )( IiTunes * This, /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *ConvertTrack )( IiTunes * This, /* [in] */ VARIANT *iTrackToConvert, /* [retval][out] */ IITOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *ConvertTracks )( IiTunes * This, /* [in] */ VARIANT *iTracksToConvert, /* [retval][out] */ IITOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *CheckVersion )( IiTunes * This, /* [in] */ long majorVersion, /* [in] */ long minorVersion, /* [retval][out] */ VARIANT_BOOL *isCompatible); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectByID )( IiTunes * This, /* [in] */ long sourceID, /* [in] */ long playlistID, /* [in] */ long trackID, /* [in] */ long databaseID, /* [retval][out] */ IITObject **iObject); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *CreatePlaylist )( IiTunes * This, /* [in] */ BSTR playlistName, /* [retval][out] */ IITPlaylist **iPlaylist); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *OpenURL )( IiTunes * This, /* [in] */ BSTR url); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GotoMusicStoreHomePage )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *UpdateIPod )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Authorize )( IiTunes * This, /* [in] */ long numElems, /* [size_is][in] */ VARIANT data[ ], /* [size_is][in] */ BSTR names[ ]); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Quit )( IiTunes * This); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Sources )( IiTunes * This, /* [retval][out] */ IITSourceCollection **iSourceCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Encoders )( IiTunes * This, /* [retval][out] */ IITEncoderCollection **iEncoderCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_EQPresets )( IiTunes * This, /* [retval][out] */ IITEQPresetCollection **iEQPresetCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Visuals )( IiTunes * This, /* [retval][out] */ IITVisualCollection **iVisualCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Windows )( IiTunes * This, /* [retval][out] */ IITWindowCollection **iWindowCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SoundVolume )( IiTunes * This, /* [retval][out] */ long *volume); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SoundVolume )( IiTunes * This, /* [in] */ long volume); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Mute )( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isMuted); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Mute )( IiTunes * This, /* [in] */ VARIANT_BOOL shouldMute); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayerState )( IiTunes * This, /* [retval][out] */ ITPlayerState *playerState); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayerPosition )( IiTunes * This, /* [retval][out] */ long *playerPos); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_PlayerPosition )( IiTunes * This, /* [in] */ long playerPos); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_CurrentEncoder )( IiTunes * This, /* [retval][out] */ IITEncoder **iEncoder); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_CurrentEncoder )( IiTunes * This, /* [in] */ IITEncoder *iEncoder); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_VisualsEnabled )( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_VisualsEnabled )( IiTunes * This, /* [in] */ VARIANT_BOOL shouldEnable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_FullScreenVisuals )( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isFullScreen); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_FullScreenVisuals )( IiTunes * This, /* [in] */ VARIANT_BOOL shouldUseFullScreen); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_VisualSize )( IiTunes * This, /* [retval][out] */ ITVisualSize *visualSize); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_VisualSize )( IiTunes * This, /* [in] */ ITVisualSize visualSize); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_CurrentVisual )( IiTunes * This, /* [retval][out] */ IITVisual **iVisual); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_CurrentVisual )( IiTunes * This, /* [in] */ IITVisual *iVisual); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_EQEnabled )( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_EQEnabled )( IiTunes * This, /* [in] */ VARIANT_BOOL shouldEnable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_CurrentEQPreset )( IiTunes * This, /* [retval][out] */ IITEQPreset **iEQPreset); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_CurrentEQPreset )( IiTunes * This, /* [in] */ IITEQPreset *iEQPreset); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_CurrentStreamTitle )( IiTunes * This, /* [retval][out] */ BSTR *streamTitle); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_CurrentStreamURL )( IiTunes * This, /* [retval][out] */ BSTR *streamURL); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_BrowserWindow )( IiTunes * This, /* [retval][out] */ IITBrowserWindow **iBrowserWindow); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_EQWindow )( IiTunes * This, /* [retval][out] */ IITWindow **iEQWindow); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_LibrarySource )( IiTunes * This, /* [retval][out] */ IITSource **iLibrarySource); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_LibraryPlaylist )( IiTunes * This, /* [retval][out] */ IITLibraryPlaylist **iLibraryPlaylist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_CurrentTrack )( IiTunes * This, /* [retval][out] */ IITTrack **iTrack); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_CurrentPlaylist )( IiTunes * This, /* [retval][out] */ IITPlaylist **iPlaylist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SelectedTracks )( IiTunes * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Version )( IiTunes * This, /* [retval][out] */ BSTR *version); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *SetOptions )( IiTunes * This, /* [in] */ long options); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *ConvertFile2 )( IiTunes * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITConvertOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *ConvertFiles2 )( IiTunes * This, /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITConvertOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *ConvertTrack2 )( IiTunes * This, /* [in] */ VARIANT *iTrackToConvert, /* [retval][out] */ IITConvertOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *ConvertTracks2 )( IiTunes * This, /* [in] */ VARIANT *iTracksToConvert, /* [retval][out] */ IITConvertOperationStatus **iStatus); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_AppCommandMessageProcessingEnabled )( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_AppCommandMessageProcessingEnabled )( IiTunes * This, /* [in] */ VARIANT_BOOL shouldEnable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ForceToForegroundOnDialog )( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *forceToForegroundOnDialog); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_ForceToForegroundOnDialog )( IiTunes * This, /* [in] */ VARIANT_BOOL forceToForegroundOnDialog); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *CreateEQPreset )( IiTunes * This, /* [in] */ BSTR eqPresetName, /* [retval][out] */ IITEQPreset **iEQPreset); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *CreatePlaylistInSource )( IiTunes * This, /* [in] */ BSTR playlistName, /* [in] */ VARIANT *iSource, /* [retval][out] */ IITPlaylist **iPlaylist); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetPlayerButtonsState )( IiTunes * This, /* [out] */ VARIANT_BOOL *previousEnabled, /* [out] */ ITPlayButtonState *playPauseStopState, /* [out] */ VARIANT_BOOL *nextEnabled); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *PlayerButtonClicked )( IiTunes * This, /* [in] */ ITPlayerButton playerButton, /* [in] */ long playerButtonModifierKeys); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_CanSetShuffle )( IiTunes * This, /* [in] */ VARIANT *iPlaylist, /* [retval][out] */ VARIANT_BOOL *canSetShuffle); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_CanSetSongRepeat )( IiTunes * This, /* [in] */ VARIANT *iPlaylist, /* [retval][out] */ VARIANT_BOOL *canSetSongRepeat); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ConvertOperationStatus )( IiTunes * This, /* [retval][out] */ IITConvertOperationStatus **iStatus); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *SubscribeToPodcast )( IiTunes * This, /* [in] */ BSTR url); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *UpdatePodcastFeeds )( IiTunes * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *CreateFolder )( IiTunes * This, /* [in] */ BSTR folderName, /* [retval][out] */ IITPlaylist **iFolder); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *CreateFolderInSource )( IiTunes * This, /* [in] */ BSTR folderName, /* [in] */ VARIANT *iSource, /* [retval][out] */ IITPlaylist **iFolder); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SoundVolumeControlEnabled )( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_LibraryXMLPath )( IiTunes * This, /* [retval][out] */ BSTR *filePath); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ITObjectPersistentIDHigh )( IiTunes * This, /* [in] */ VARIANT *iObject, /* [retval][out] */ long *highID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ITObjectPersistentIDLow )( IiTunes * This, /* [in] */ VARIANT *iObject, /* [retval][out] */ long *lowID); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectPersistentIDs )( IiTunes * This, /* [in] */ VARIANT *iObject, /* [out] */ long *highID, /* [out] */ long *lowID); END_INTERFACE } IiTunesVtbl; interface IiTunes { CONST_VTBL struct IiTunesVtbl *lpVtbl; }; #ifdef COBJMACROS #define IiTunes_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IiTunes_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IiTunes_Release(This) \ (This)->lpVtbl -> Release(This) #define IiTunes_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IiTunes_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IiTunes_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IiTunes_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IiTunes_BackTrack(This) \ (This)->lpVtbl -> BackTrack(This) #define IiTunes_FastForward(This) \ (This)->lpVtbl -> FastForward(This) #define IiTunes_NextTrack(This) \ (This)->lpVtbl -> NextTrack(This) #define IiTunes_Pause(This) \ (This)->lpVtbl -> Pause(This) #define IiTunes_Play(This) \ (This)->lpVtbl -> Play(This) #define IiTunes_PlayFile(This,filePath) \ (This)->lpVtbl -> PlayFile(This,filePath) #define IiTunes_PlayPause(This) \ (This)->lpVtbl -> PlayPause(This) #define IiTunes_PreviousTrack(This) \ (This)->lpVtbl -> PreviousTrack(This) #define IiTunes_Resume(This) \ (This)->lpVtbl -> Resume(This) #define IiTunes_Rewind(This) \ (This)->lpVtbl -> Rewind(This) #define IiTunes_Stop(This) \ (This)->lpVtbl -> Stop(This) #define IiTunes_ConvertFile(This,filePath,iStatus) \ (This)->lpVtbl -> ConvertFile(This,filePath,iStatus) #define IiTunes_ConvertFiles(This,filePaths,iStatus) \ (This)->lpVtbl -> ConvertFiles(This,filePaths,iStatus) #define IiTunes_ConvertTrack(This,iTrackToConvert,iStatus) \ (This)->lpVtbl -> ConvertTrack(This,iTrackToConvert,iStatus) #define IiTunes_ConvertTracks(This,iTracksToConvert,iStatus) \ (This)->lpVtbl -> ConvertTracks(This,iTracksToConvert,iStatus) #define IiTunes_CheckVersion(This,majorVersion,minorVersion,isCompatible) \ (This)->lpVtbl -> CheckVersion(This,majorVersion,minorVersion,isCompatible) #define IiTunes_GetITObjectByID(This,sourceID,playlistID,trackID,databaseID,iObject) \ (This)->lpVtbl -> GetITObjectByID(This,sourceID,playlistID,trackID,databaseID,iObject) #define IiTunes_CreatePlaylist(This,playlistName,iPlaylist) \ (This)->lpVtbl -> CreatePlaylist(This,playlistName,iPlaylist) #define IiTunes_OpenURL(This,url) \ (This)->lpVtbl -> OpenURL(This,url) #define IiTunes_GotoMusicStoreHomePage(This) \ (This)->lpVtbl -> GotoMusicStoreHomePage(This) #define IiTunes_UpdateIPod(This) \ (This)->lpVtbl -> UpdateIPod(This) #define IiTunes_Authorize(This,numElems,data,names) \ (This)->lpVtbl -> Authorize(This,numElems,data,names) #define IiTunes_Quit(This) \ (This)->lpVtbl -> Quit(This) #define IiTunes_get_Sources(This,iSourceCollection) \ (This)->lpVtbl -> get_Sources(This,iSourceCollection) #define IiTunes_get_Encoders(This,iEncoderCollection) \ (This)->lpVtbl -> get_Encoders(This,iEncoderCollection) #define IiTunes_get_EQPresets(This,iEQPresetCollection) \ (This)->lpVtbl -> get_EQPresets(This,iEQPresetCollection) #define IiTunes_get_Visuals(This,iVisualCollection) \ (This)->lpVtbl -> get_Visuals(This,iVisualCollection) #define IiTunes_get_Windows(This,iWindowCollection) \ (This)->lpVtbl -> get_Windows(This,iWindowCollection) #define IiTunes_get_SoundVolume(This,volume) \ (This)->lpVtbl -> get_SoundVolume(This,volume) #define IiTunes_put_SoundVolume(This,volume) \ (This)->lpVtbl -> put_SoundVolume(This,volume) #define IiTunes_get_Mute(This,isMuted) \ (This)->lpVtbl -> get_Mute(This,isMuted) #define IiTunes_put_Mute(This,shouldMute) \ (This)->lpVtbl -> put_Mute(This,shouldMute) #define IiTunes_get_PlayerState(This,playerState) \ (This)->lpVtbl -> get_PlayerState(This,playerState) #define IiTunes_get_PlayerPosition(This,playerPos) \ (This)->lpVtbl -> get_PlayerPosition(This,playerPos) #define IiTunes_put_PlayerPosition(This,playerPos) \ (This)->lpVtbl -> put_PlayerPosition(This,playerPos) #define IiTunes_get_CurrentEncoder(This,iEncoder) \ (This)->lpVtbl -> get_CurrentEncoder(This,iEncoder) #define IiTunes_put_CurrentEncoder(This,iEncoder) \ (This)->lpVtbl -> put_CurrentEncoder(This,iEncoder) #define IiTunes_get_VisualsEnabled(This,isEnabled) \ (This)->lpVtbl -> get_VisualsEnabled(This,isEnabled) #define IiTunes_put_VisualsEnabled(This,shouldEnable) \ (This)->lpVtbl -> put_VisualsEnabled(This,shouldEnable) #define IiTunes_get_FullScreenVisuals(This,isFullScreen) \ (This)->lpVtbl -> get_FullScreenVisuals(This,isFullScreen) #define IiTunes_put_FullScreenVisuals(This,shouldUseFullScreen) \ (This)->lpVtbl -> put_FullScreenVisuals(This,shouldUseFullScreen) #define IiTunes_get_VisualSize(This,visualSize) \ (This)->lpVtbl -> get_VisualSize(This,visualSize) #define IiTunes_put_VisualSize(This,visualSize) \ (This)->lpVtbl -> put_VisualSize(This,visualSize) #define IiTunes_get_CurrentVisual(This,iVisual) \ (This)->lpVtbl -> get_CurrentVisual(This,iVisual) #define IiTunes_put_CurrentVisual(This,iVisual) \ (This)->lpVtbl -> put_CurrentVisual(This,iVisual) #define IiTunes_get_EQEnabled(This,isEnabled) \ (This)->lpVtbl -> get_EQEnabled(This,isEnabled) #define IiTunes_put_EQEnabled(This,shouldEnable) \ (This)->lpVtbl -> put_EQEnabled(This,shouldEnable) #define IiTunes_get_CurrentEQPreset(This,iEQPreset) \ (This)->lpVtbl -> get_CurrentEQPreset(This,iEQPreset) #define IiTunes_put_CurrentEQPreset(This,iEQPreset) \ (This)->lpVtbl -> put_CurrentEQPreset(This,iEQPreset) #define IiTunes_get_CurrentStreamTitle(This,streamTitle) \ (This)->lpVtbl -> get_CurrentStreamTitle(This,streamTitle) #define IiTunes_get_CurrentStreamURL(This,streamURL) \ (This)->lpVtbl -> get_CurrentStreamURL(This,streamURL) #define IiTunes_get_BrowserWindow(This,iBrowserWindow) \ (This)->lpVtbl -> get_BrowserWindow(This,iBrowserWindow) #define IiTunes_get_EQWindow(This,iEQWindow) \ (This)->lpVtbl -> get_EQWindow(This,iEQWindow) #define IiTunes_get_LibrarySource(This,iLibrarySource) \ (This)->lpVtbl -> get_LibrarySource(This,iLibrarySource) #define IiTunes_get_LibraryPlaylist(This,iLibraryPlaylist) \ (This)->lpVtbl -> get_LibraryPlaylist(This,iLibraryPlaylist) #define IiTunes_get_CurrentTrack(This,iTrack) \ (This)->lpVtbl -> get_CurrentTrack(This,iTrack) #define IiTunes_get_CurrentPlaylist(This,iPlaylist) \ (This)->lpVtbl -> get_CurrentPlaylist(This,iPlaylist) #define IiTunes_get_SelectedTracks(This,iTrackCollection) \ (This)->lpVtbl -> get_SelectedTracks(This,iTrackCollection) #define IiTunes_get_Version(This,version) \ (This)->lpVtbl -> get_Version(This,version) #define IiTunes_SetOptions(This,options) \ (This)->lpVtbl -> SetOptions(This,options) #define IiTunes_ConvertFile2(This,filePath,iStatus) \ (This)->lpVtbl -> ConvertFile2(This,filePath,iStatus) #define IiTunes_ConvertFiles2(This,filePaths,iStatus) \ (This)->lpVtbl -> ConvertFiles2(This,filePaths,iStatus) #define IiTunes_ConvertTrack2(This,iTrackToConvert,iStatus) \ (This)->lpVtbl -> ConvertTrack2(This,iTrackToConvert,iStatus) #define IiTunes_ConvertTracks2(This,iTracksToConvert,iStatus) \ (This)->lpVtbl -> ConvertTracks2(This,iTracksToConvert,iStatus) #define IiTunes_get_AppCommandMessageProcessingEnabled(This,isEnabled) \ (This)->lpVtbl -> get_AppCommandMessageProcessingEnabled(This,isEnabled) #define IiTunes_put_AppCommandMessageProcessingEnabled(This,shouldEnable) \ (This)->lpVtbl -> put_AppCommandMessageProcessingEnabled(This,shouldEnable) #define IiTunes_get_ForceToForegroundOnDialog(This,forceToForegroundOnDialog) \ (This)->lpVtbl -> get_ForceToForegroundOnDialog(This,forceToForegroundOnDialog) #define IiTunes_put_ForceToForegroundOnDialog(This,forceToForegroundOnDialog) \ (This)->lpVtbl -> put_ForceToForegroundOnDialog(This,forceToForegroundOnDialog) #define IiTunes_CreateEQPreset(This,eqPresetName,iEQPreset) \ (This)->lpVtbl -> CreateEQPreset(This,eqPresetName,iEQPreset) #define IiTunes_CreatePlaylistInSource(This,playlistName,iSource,iPlaylist) \ (This)->lpVtbl -> CreatePlaylistInSource(This,playlistName,iSource,iPlaylist) #define IiTunes_GetPlayerButtonsState(This,previousEnabled,playPauseStopState,nextEnabled) \ (This)->lpVtbl -> GetPlayerButtonsState(This,previousEnabled,playPauseStopState,nextEnabled) #define IiTunes_PlayerButtonClicked(This,playerButton,playerButtonModifierKeys) \ (This)->lpVtbl -> PlayerButtonClicked(This,playerButton,playerButtonModifierKeys) #define IiTunes_get_CanSetShuffle(This,iPlaylist,canSetShuffle) \ (This)->lpVtbl -> get_CanSetShuffle(This,iPlaylist,canSetShuffle) #define IiTunes_get_CanSetSongRepeat(This,iPlaylist,canSetSongRepeat) \ (This)->lpVtbl -> get_CanSetSongRepeat(This,iPlaylist,canSetSongRepeat) #define IiTunes_get_ConvertOperationStatus(This,iStatus) \ (This)->lpVtbl -> get_ConvertOperationStatus(This,iStatus) #define IiTunes_SubscribeToPodcast(This,url) \ (This)->lpVtbl -> SubscribeToPodcast(This,url) #define IiTunes_UpdatePodcastFeeds(This) \ (This)->lpVtbl -> UpdatePodcastFeeds(This) #define IiTunes_CreateFolder(This,folderName,iFolder) \ (This)->lpVtbl -> CreateFolder(This,folderName,iFolder) #define IiTunes_CreateFolderInSource(This,folderName,iSource,iFolder) \ (This)->lpVtbl -> CreateFolderInSource(This,folderName,iSource,iFolder) #define IiTunes_get_SoundVolumeControlEnabled(This,isEnabled) \ (This)->lpVtbl -> get_SoundVolumeControlEnabled(This,isEnabled) #define IiTunes_get_LibraryXMLPath(This,filePath) \ (This)->lpVtbl -> get_LibraryXMLPath(This,filePath) #define IiTunes_get_ITObjectPersistentIDHigh(This,iObject,highID) \ (This)->lpVtbl -> get_ITObjectPersistentIDHigh(This,iObject,highID) #define IiTunes_get_ITObjectPersistentIDLow(This,iObject,lowID) \ (This)->lpVtbl -> get_ITObjectPersistentIDLow(This,iObject,lowID) #define IiTunes_GetITObjectPersistentIDs(This,iObject,highID,lowID) \ (This)->lpVtbl -> GetITObjectPersistentIDs(This,iObject,highID,lowID) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_BackTrack_Proxy( IiTunes * This); void __RPC_STUB IiTunes_BackTrack_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_FastForward_Proxy( IiTunes * This); void __RPC_STUB IiTunes_FastForward_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_NextTrack_Proxy( IiTunes * This); void __RPC_STUB IiTunes_NextTrack_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_Pause_Proxy( IiTunes * This); void __RPC_STUB IiTunes_Pause_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_Play_Proxy( IiTunes * This); void __RPC_STUB IiTunes_Play_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_PlayFile_Proxy( IiTunes * This, /* [in] */ BSTR filePath); void __RPC_STUB IiTunes_PlayFile_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_PlayPause_Proxy( IiTunes * This); void __RPC_STUB IiTunes_PlayPause_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_PreviousTrack_Proxy( IiTunes * This); void __RPC_STUB IiTunes_PreviousTrack_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_Resume_Proxy( IiTunes * This); void __RPC_STUB IiTunes_Resume_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_Rewind_Proxy( IiTunes * This); void __RPC_STUB IiTunes_Rewind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_Stop_Proxy( IiTunes * This); void __RPC_STUB IiTunes_Stop_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_ConvertFile_Proxy( IiTunes * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITOperationStatus **iStatus); void __RPC_STUB IiTunes_ConvertFile_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_ConvertFiles_Proxy( IiTunes * This, /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITOperationStatus **iStatus); void __RPC_STUB IiTunes_ConvertFiles_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_ConvertTrack_Proxy( IiTunes * This, /* [in] */ VARIANT *iTrackToConvert, /* [retval][out] */ IITOperationStatus **iStatus); void __RPC_STUB IiTunes_ConvertTrack_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_ConvertTracks_Proxy( IiTunes * This, /* [in] */ VARIANT *iTracksToConvert, /* [retval][out] */ IITOperationStatus **iStatus); void __RPC_STUB IiTunes_ConvertTracks_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_CheckVersion_Proxy( IiTunes * This, /* [in] */ long majorVersion, /* [in] */ long minorVersion, /* [retval][out] */ VARIANT_BOOL *isCompatible); void __RPC_STUB IiTunes_CheckVersion_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_GetITObjectByID_Proxy( IiTunes * This, /* [in] */ long sourceID, /* [in] */ long playlistID, /* [in] */ long trackID, /* [in] */ long databaseID, /* [retval][out] */ IITObject **iObject); void __RPC_STUB IiTunes_GetITObjectByID_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_CreatePlaylist_Proxy( IiTunes * This, /* [in] */ BSTR playlistName, /* [retval][out] */ IITPlaylist **iPlaylist); void __RPC_STUB IiTunes_CreatePlaylist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_OpenURL_Proxy( IiTunes * This, /* [in] */ BSTR url); void __RPC_STUB IiTunes_OpenURL_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_GotoMusicStoreHomePage_Proxy( IiTunes * This); void __RPC_STUB IiTunes_GotoMusicStoreHomePage_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_UpdateIPod_Proxy( IiTunes * This); void __RPC_STUB IiTunes_UpdateIPod_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_Authorize_Proxy( IiTunes * This, /* [in] */ long numElems, /* [size_is][in] */ VARIANT data[ ], /* [size_is][in] */ BSTR names[ ]); void __RPC_STUB IiTunes_Authorize_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_Quit_Proxy( IiTunes * This); void __RPC_STUB IiTunes_Quit_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_Sources_Proxy( IiTunes * This, /* [retval][out] */ IITSourceCollection **iSourceCollection); void __RPC_STUB IiTunes_get_Sources_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_Encoders_Proxy( IiTunes * This, /* [retval][out] */ IITEncoderCollection **iEncoderCollection); void __RPC_STUB IiTunes_get_Encoders_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_EQPresets_Proxy( IiTunes * This, /* [retval][out] */ IITEQPresetCollection **iEQPresetCollection); void __RPC_STUB IiTunes_get_EQPresets_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_Visuals_Proxy( IiTunes * This, /* [retval][out] */ IITVisualCollection **iVisualCollection); void __RPC_STUB IiTunes_get_Visuals_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_Windows_Proxy( IiTunes * This, /* [retval][out] */ IITWindowCollection **iWindowCollection); void __RPC_STUB IiTunes_get_Windows_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_SoundVolume_Proxy( IiTunes * This, /* [retval][out] */ long *volume); void __RPC_STUB IiTunes_get_SoundVolume_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_SoundVolume_Proxy( IiTunes * This, /* [in] */ long volume); void __RPC_STUB IiTunes_put_SoundVolume_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_Mute_Proxy( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isMuted); void __RPC_STUB IiTunes_get_Mute_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_Mute_Proxy( IiTunes * This, /* [in] */ VARIANT_BOOL shouldMute); void __RPC_STUB IiTunes_put_Mute_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_PlayerState_Proxy( IiTunes * This, /* [retval][out] */ ITPlayerState *playerState); void __RPC_STUB IiTunes_get_PlayerState_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_PlayerPosition_Proxy( IiTunes * This, /* [retval][out] */ long *playerPos); void __RPC_STUB IiTunes_get_PlayerPosition_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_PlayerPosition_Proxy( IiTunes * This, /* [in] */ long playerPos); void __RPC_STUB IiTunes_put_PlayerPosition_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_CurrentEncoder_Proxy( IiTunes * This, /* [retval][out] */ IITEncoder **iEncoder); void __RPC_STUB IiTunes_get_CurrentEncoder_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_CurrentEncoder_Proxy( IiTunes * This, /* [in] */ IITEncoder *iEncoder); void __RPC_STUB IiTunes_put_CurrentEncoder_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_VisualsEnabled_Proxy( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); void __RPC_STUB IiTunes_get_VisualsEnabled_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_VisualsEnabled_Proxy( IiTunes * This, /* [in] */ VARIANT_BOOL shouldEnable); void __RPC_STUB IiTunes_put_VisualsEnabled_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_FullScreenVisuals_Proxy( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isFullScreen); void __RPC_STUB IiTunes_get_FullScreenVisuals_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_FullScreenVisuals_Proxy( IiTunes * This, /* [in] */ VARIANT_BOOL shouldUseFullScreen); void __RPC_STUB IiTunes_put_FullScreenVisuals_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_VisualSize_Proxy( IiTunes * This, /* [retval][out] */ ITVisualSize *visualSize); void __RPC_STUB IiTunes_get_VisualSize_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_VisualSize_Proxy( IiTunes * This, /* [in] */ ITVisualSize visualSize); void __RPC_STUB IiTunes_put_VisualSize_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_CurrentVisual_Proxy( IiTunes * This, /* [retval][out] */ IITVisual **iVisual); void __RPC_STUB IiTunes_get_CurrentVisual_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_CurrentVisual_Proxy( IiTunes * This, /* [in] */ IITVisual *iVisual); void __RPC_STUB IiTunes_put_CurrentVisual_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_EQEnabled_Proxy( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); void __RPC_STUB IiTunes_get_EQEnabled_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_EQEnabled_Proxy( IiTunes * This, /* [in] */ VARIANT_BOOL shouldEnable); void __RPC_STUB IiTunes_put_EQEnabled_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_CurrentEQPreset_Proxy( IiTunes * This, /* [retval][out] */ IITEQPreset **iEQPreset); void __RPC_STUB IiTunes_get_CurrentEQPreset_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_CurrentEQPreset_Proxy( IiTunes * This, /* [in] */ IITEQPreset *iEQPreset); void __RPC_STUB IiTunes_put_CurrentEQPreset_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_CurrentStreamTitle_Proxy( IiTunes * This, /* [retval][out] */ BSTR *streamTitle); void __RPC_STUB IiTunes_get_CurrentStreamTitle_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_CurrentStreamURL_Proxy( IiTunes * This, /* [retval][out] */ BSTR *streamURL); void __RPC_STUB IiTunes_get_CurrentStreamURL_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_BrowserWindow_Proxy( IiTunes * This, /* [retval][out] */ IITBrowserWindow **iBrowserWindow); void __RPC_STUB IiTunes_get_BrowserWindow_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_EQWindow_Proxy( IiTunes * This, /* [retval][out] */ IITWindow **iEQWindow); void __RPC_STUB IiTunes_get_EQWindow_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_LibrarySource_Proxy( IiTunes * This, /* [retval][out] */ IITSource **iLibrarySource); void __RPC_STUB IiTunes_get_LibrarySource_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_LibraryPlaylist_Proxy( IiTunes * This, /* [retval][out] */ IITLibraryPlaylist **iLibraryPlaylist); void __RPC_STUB IiTunes_get_LibraryPlaylist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_CurrentTrack_Proxy( IiTunes * This, /* [retval][out] */ IITTrack **iTrack); void __RPC_STUB IiTunes_get_CurrentTrack_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_CurrentPlaylist_Proxy( IiTunes * This, /* [retval][out] */ IITPlaylist **iPlaylist); void __RPC_STUB IiTunes_get_CurrentPlaylist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_SelectedTracks_Proxy( IiTunes * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); void __RPC_STUB IiTunes_get_SelectedTracks_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_Version_Proxy( IiTunes * This, /* [retval][out] */ BSTR *version); void __RPC_STUB IiTunes_get_Version_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_SetOptions_Proxy( IiTunes * This, /* [in] */ long options); void __RPC_STUB IiTunes_SetOptions_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_ConvertFile2_Proxy( IiTunes * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITConvertOperationStatus **iStatus); void __RPC_STUB IiTunes_ConvertFile2_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_ConvertFiles2_Proxy( IiTunes * This, /* [in] */ VARIANT *filePaths, /* [retval][out] */ IITConvertOperationStatus **iStatus); void __RPC_STUB IiTunes_ConvertFiles2_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_ConvertTrack2_Proxy( IiTunes * This, /* [in] */ VARIANT *iTrackToConvert, /* [retval][out] */ IITConvertOperationStatus **iStatus); void __RPC_STUB IiTunes_ConvertTrack2_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_ConvertTracks2_Proxy( IiTunes * This, /* [in] */ VARIANT *iTracksToConvert, /* [retval][out] */ IITConvertOperationStatus **iStatus); void __RPC_STUB IiTunes_ConvertTracks2_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_AppCommandMessageProcessingEnabled_Proxy( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); void __RPC_STUB IiTunes_get_AppCommandMessageProcessingEnabled_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_AppCommandMessageProcessingEnabled_Proxy( IiTunes * This, /* [in] */ VARIANT_BOOL shouldEnable); void __RPC_STUB IiTunes_put_AppCommandMessageProcessingEnabled_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_ForceToForegroundOnDialog_Proxy( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *forceToForegroundOnDialog); void __RPC_STUB IiTunes_get_ForceToForegroundOnDialog_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IiTunes_put_ForceToForegroundOnDialog_Proxy( IiTunes * This, /* [in] */ VARIANT_BOOL forceToForegroundOnDialog); void __RPC_STUB IiTunes_put_ForceToForegroundOnDialog_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_CreateEQPreset_Proxy( IiTunes * This, /* [in] */ BSTR eqPresetName, /* [retval][out] */ IITEQPreset **iEQPreset); void __RPC_STUB IiTunes_CreateEQPreset_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_CreatePlaylistInSource_Proxy( IiTunes * This, /* [in] */ BSTR playlistName, /* [in] */ VARIANT *iSource, /* [retval][out] */ IITPlaylist **iPlaylist); void __RPC_STUB IiTunes_CreatePlaylistInSource_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_GetPlayerButtonsState_Proxy( IiTunes * This, /* [out] */ VARIANT_BOOL *previousEnabled, /* [out] */ ITPlayButtonState *playPauseStopState, /* [out] */ VARIANT_BOOL *nextEnabled); void __RPC_STUB IiTunes_GetPlayerButtonsState_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_PlayerButtonClicked_Proxy( IiTunes * This, /* [in] */ ITPlayerButton playerButton, /* [in] */ long playerButtonModifierKeys); void __RPC_STUB IiTunes_PlayerButtonClicked_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_CanSetShuffle_Proxy( IiTunes * This, /* [in] */ VARIANT *iPlaylist, /* [retval][out] */ VARIANT_BOOL *canSetShuffle); void __RPC_STUB IiTunes_get_CanSetShuffle_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_CanSetSongRepeat_Proxy( IiTunes * This, /* [in] */ VARIANT *iPlaylist, /* [retval][out] */ VARIANT_BOOL *canSetSongRepeat); void __RPC_STUB IiTunes_get_CanSetSongRepeat_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_ConvertOperationStatus_Proxy( IiTunes * This, /* [retval][out] */ IITConvertOperationStatus **iStatus); void __RPC_STUB IiTunes_get_ConvertOperationStatus_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_SubscribeToPodcast_Proxy( IiTunes * This, /* [in] */ BSTR url); void __RPC_STUB IiTunes_SubscribeToPodcast_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_UpdatePodcastFeeds_Proxy( IiTunes * This); void __RPC_STUB IiTunes_UpdatePodcastFeeds_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_CreateFolder_Proxy( IiTunes * This, /* [in] */ BSTR folderName, /* [retval][out] */ IITPlaylist **iFolder); void __RPC_STUB IiTunes_CreateFolder_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_CreateFolderInSource_Proxy( IiTunes * This, /* [in] */ BSTR folderName, /* [in] */ VARIANT *iSource, /* [retval][out] */ IITPlaylist **iFolder); void __RPC_STUB IiTunes_CreateFolderInSource_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_SoundVolumeControlEnabled_Proxy( IiTunes * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); void __RPC_STUB IiTunes_get_SoundVolumeControlEnabled_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_LibraryXMLPath_Proxy( IiTunes * This, /* [retval][out] */ BSTR *filePath); void __RPC_STUB IiTunes_get_LibraryXMLPath_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_ITObjectPersistentIDHigh_Proxy( IiTunes * This, /* [in] */ VARIANT *iObject, /* [retval][out] */ long *highID); void __RPC_STUB IiTunes_get_ITObjectPersistentIDHigh_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IiTunes_get_ITObjectPersistentIDLow_Proxy( IiTunes * This, /* [in] */ VARIANT *iObject, /* [retval][out] */ long *lowID); void __RPC_STUB IiTunes_get_ITObjectPersistentIDLow_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IiTunes_GetITObjectPersistentIDs_Proxy( IiTunes * This, /* [in] */ VARIANT *iObject, /* [out] */ long *highID, /* [out] */ long *lowID); void __RPC_STUB IiTunes_GetITObjectPersistentIDs_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IiTunes_INTERFACE_DEFINED__ */ #ifndef ___IiTunesEvents_DISPINTERFACE_DEFINED__ #define ___IiTunesEvents_DISPINTERFACE_DEFINED__ /* dispinterface _IiTunesEvents */ /* [helpstring][uuid] */ EXTERN_C const IID DIID__IiTunesEvents; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("5846EB78-317E-4b6f-B0C3-11EE8C8FEEF2") _IiTunesEvents : public IDispatch { }; #else /* C style interface */ typedef struct _IiTunesEventsVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( _IiTunesEvents * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( _IiTunesEvents * This); ULONG ( STDMETHODCALLTYPE *Release )( _IiTunesEvents * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( _IiTunesEvents * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( _IiTunesEvents * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( _IiTunesEvents * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( _IiTunesEvents * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); END_INTERFACE } _IiTunesEventsVtbl; interface _IiTunesEvents { CONST_VTBL struct _IiTunesEventsVtbl *lpVtbl; }; #ifdef COBJMACROS #define _IiTunesEvents_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define _IiTunesEvents_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define _IiTunesEvents_Release(This) \ (This)->lpVtbl -> Release(This) #define _IiTunesEvents_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define _IiTunesEvents_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define _IiTunesEvents_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define _IiTunesEvents_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* ___IiTunesEvents_DISPINTERFACE_DEFINED__ */ #ifndef ___IITConvertOperationStatusEvents_DISPINTERFACE_DEFINED__ #define ___IITConvertOperationStatusEvents_DISPINTERFACE_DEFINED__ /* dispinterface _IITConvertOperationStatusEvents */ /* [helpstring][uuid] */ EXTERN_C const IID DIID__IITConvertOperationStatusEvents; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("5C47A705-8E8A-45a1-9EED-71C993F0BF60") _IITConvertOperationStatusEvents : public IDispatch { }; #else /* C style interface */ typedef struct _IITConvertOperationStatusEventsVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( _IITConvertOperationStatusEvents * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( _IITConvertOperationStatusEvents * This); ULONG ( STDMETHODCALLTYPE *Release )( _IITConvertOperationStatusEvents * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( _IITConvertOperationStatusEvents * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( _IITConvertOperationStatusEvents * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( _IITConvertOperationStatusEvents * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( _IITConvertOperationStatusEvents * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); END_INTERFACE } _IITConvertOperationStatusEventsVtbl; interface _IITConvertOperationStatusEvents { CONST_VTBL struct _IITConvertOperationStatusEventsVtbl *lpVtbl; }; #ifdef COBJMACROS #define _IITConvertOperationStatusEvents_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define _IITConvertOperationStatusEvents_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define _IITConvertOperationStatusEvents_Release(This) \ (This)->lpVtbl -> Release(This) #define _IITConvertOperationStatusEvents_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define _IITConvertOperationStatusEvents_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define _IITConvertOperationStatusEvents_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define _IITConvertOperationStatusEvents_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* ___IITConvertOperationStatusEvents_DISPINTERFACE_DEFINED__ */ EXTERN_C const CLSID CLSID_iTunesApp; #ifdef __cplusplus class DECLSPEC_UUID("DC0C2640-1415-4644-875C-6F4D769839BA") iTunesApp; #endif EXTERN_C const CLSID CLSID_iTunesConvertOperationStatus; #ifdef __cplusplus class DECLSPEC_UUID("D06596AD-C900-41b2-BC68-1B486450FC56") iTunesConvertOperationStatus; #endif #ifndef __IITArtwork_INTERFACE_DEFINED__ #define __IITArtwork_INTERFACE_DEFINED__ /* interface IITArtwork */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITArtwork; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("D0A6C1F8-BF3D-4cd8-AC47-FE32BDD17257") IITArtwork : public IDispatch { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Delete( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE SetArtworkFromFile( /* [in] */ BSTR filePath) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE SaveArtworkToFile( /* [in] */ BSTR filePath) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Format( /* [retval][out] */ ITArtworkFormat *format) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_IsDownloadedArtwork( /* [retval][out] */ VARIANT_BOOL *isDownloadedArtwork) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Description( /* [retval][out] */ BSTR *description) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Description( /* [in] */ BSTR description) = 0; }; #else /* C style interface */ typedef struct IITArtworkVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITArtwork * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITArtwork * This); ULONG ( STDMETHODCALLTYPE *Release )( IITArtwork * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITArtwork * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITArtwork * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITArtwork * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITArtwork * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Delete )( IITArtwork * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *SetArtworkFromFile )( IITArtwork * This, /* [in] */ BSTR filePath); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *SaveArtworkToFile )( IITArtwork * This, /* [in] */ BSTR filePath); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Format )( IITArtwork * This, /* [retval][out] */ ITArtworkFormat *format); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_IsDownloadedArtwork )( IITArtwork * This, /* [retval][out] */ VARIANT_BOOL *isDownloadedArtwork); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Description )( IITArtwork * This, /* [retval][out] */ BSTR *description); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Description )( IITArtwork * This, /* [in] */ BSTR description); END_INTERFACE } IITArtworkVtbl; interface IITArtwork { CONST_VTBL struct IITArtworkVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITArtwork_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITArtwork_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITArtwork_Release(This) \ (This)->lpVtbl -> Release(This) #define IITArtwork_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITArtwork_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITArtwork_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITArtwork_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITArtwork_Delete(This) \ (This)->lpVtbl -> Delete(This) #define IITArtwork_SetArtworkFromFile(This,filePath) \ (This)->lpVtbl -> SetArtworkFromFile(This,filePath) #define IITArtwork_SaveArtworkToFile(This,filePath) \ (This)->lpVtbl -> SaveArtworkToFile(This,filePath) #define IITArtwork_get_Format(This,format) \ (This)->lpVtbl -> get_Format(This,format) #define IITArtwork_get_IsDownloadedArtwork(This,isDownloadedArtwork) \ (This)->lpVtbl -> get_IsDownloadedArtwork(This,isDownloadedArtwork) #define IITArtwork_get_Description(This,description) \ (This)->lpVtbl -> get_Description(This,description) #define IITArtwork_put_Description(This,description) \ (This)->lpVtbl -> put_Description(This,description) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITArtwork_Delete_Proxy( IITArtwork * This); void __RPC_STUB IITArtwork_Delete_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITArtwork_SetArtworkFromFile_Proxy( IITArtwork * This, /* [in] */ BSTR filePath); void __RPC_STUB IITArtwork_SetArtworkFromFile_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITArtwork_SaveArtworkToFile_Proxy( IITArtwork * This, /* [in] */ BSTR filePath); void __RPC_STUB IITArtwork_SaveArtworkToFile_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITArtwork_get_Format_Proxy( IITArtwork * This, /* [retval][out] */ ITArtworkFormat *format); void __RPC_STUB IITArtwork_get_Format_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITArtwork_get_IsDownloadedArtwork_Proxy( IITArtwork * This, /* [retval][out] */ VARIANT_BOOL *isDownloadedArtwork); void __RPC_STUB IITArtwork_get_IsDownloadedArtwork_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITArtwork_get_Description_Proxy( IITArtwork * This, /* [retval][out] */ BSTR *description); void __RPC_STUB IITArtwork_get_Description_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITArtwork_put_Description_Proxy( IITArtwork * This, /* [in] */ BSTR description); void __RPC_STUB IITArtwork_put_Description_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITArtwork_INTERFACE_DEFINED__ */ #ifndef __IITArtworkCollection_INTERFACE_DEFINED__ #define __IITArtworkCollection_INTERFACE_DEFINED__ /* interface IITArtworkCollection */ /* [unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITArtworkCollection; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("BF2742D7-418C-4858-9AF9-2981B062D23E") IITArtworkCollection : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Count( /* [retval][out] */ long *count) = 0; virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Item( /* [in] */ long index, /* [retval][out] */ IITArtwork **iArtwork) = 0; virtual /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE get__NewEnum( /* [retval][out] */ IUnknown **iEnumerator) = 0; }; #else /* C style interface */ typedef struct IITArtworkCollectionVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITArtworkCollection * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITArtworkCollection * This); ULONG ( STDMETHODCALLTYPE *Release )( IITArtworkCollection * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITArtworkCollection * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITArtworkCollection * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITArtworkCollection * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITArtworkCollection * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Count )( IITArtworkCollection * This, /* [retval][out] */ long *count); /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Item )( IITArtworkCollection * This, /* [in] */ long index, /* [retval][out] */ IITArtwork **iArtwork); /* [helpstring][restricted][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get__NewEnum )( IITArtworkCollection * This, /* [retval][out] */ IUnknown **iEnumerator); END_INTERFACE } IITArtworkCollectionVtbl; interface IITArtworkCollection { CONST_VTBL struct IITArtworkCollectionVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITArtworkCollection_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITArtworkCollection_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITArtworkCollection_Release(This) \ (This)->lpVtbl -> Release(This) #define IITArtworkCollection_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITArtworkCollection_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITArtworkCollection_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITArtworkCollection_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITArtworkCollection_get_Count(This,count) \ (This)->lpVtbl -> get_Count(This,count) #define IITArtworkCollection_get_Item(This,index,iArtwork) \ (This)->lpVtbl -> get_Item(This,index,iArtwork) #define IITArtworkCollection_get__NewEnum(This,iEnumerator) \ (This)->lpVtbl -> get__NewEnum(This,iEnumerator) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITArtworkCollection_get_Count_Proxy( IITArtworkCollection * This, /* [retval][out] */ long *count); void __RPC_STUB IITArtworkCollection_get_Count_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IITArtworkCollection_get_Item_Proxy( IITArtworkCollection * This, /* [in] */ long index, /* [retval][out] */ IITArtwork **iArtwork); void __RPC_STUB IITArtworkCollection_get_Item_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE IITArtworkCollection_get__NewEnum_Proxy( IITArtworkCollection * This, /* [retval][out] */ IUnknown **iEnumerator); void __RPC_STUB IITArtworkCollection_get__NewEnum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITArtworkCollection_INTERFACE_DEFINED__ */ #ifndef __IITURLTrack_INTERFACE_DEFINED__ #define __IITURLTrack_INTERFACE_DEFINED__ /* interface IITURLTrack */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITURLTrack; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("1116E3B5-29FD-4393-A7BD-454E5E327900") IITURLTrack : public IITTrack { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_URL( /* [retval][out] */ BSTR *url) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_URL( /* [in] */ BSTR url) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Podcast( /* [retval][out] */ VARIANT_BOOL *isPodcast) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE UpdatePodcastFeed( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE DownloadPodcastEpisode( void) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Category( /* [retval][out] */ BSTR *category) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Category( /* [in] */ BSTR category) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Description( /* [retval][out] */ BSTR *description) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Description( /* [in] */ BSTR description) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_LongDescription( /* [retval][out] */ BSTR *longDescription) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_LongDescription( /* [in] */ BSTR longDescription) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Reveal( void) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_AlbumRating( /* [retval][out] */ long *rating) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_AlbumRating( /* [in] */ long rating) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_AlbumRatingKind( /* [retval][out] */ ITRatingKind *ratingKind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_RatingKind( /* [retval][out] */ ITRatingKind *ratingKind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Playlists( /* [retval][out] */ IITPlaylistCollection **iPlaylistCollection) = 0; }; #else /* C style interface */ typedef struct IITURLTrackVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITURLTrack * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITURLTrack * This); ULONG ( STDMETHODCALLTYPE *Release )( IITURLTrack * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITURLTrack * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITURLTrack * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITURLTrack * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITURLTrack * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectIDs )( IITURLTrack * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITURLTrack * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Name )( IITURLTrack * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Index )( IITURLTrack * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SourceID )( IITURLTrack * This, /* [retval][out] */ long *sourceID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlaylistID )( IITURLTrack * This, /* [retval][out] */ long *playlistID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackID )( IITURLTrack * This, /* [retval][out] */ long *trackID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackDatabaseID )( IITURLTrack * This, /* [retval][out] */ long *databaseID); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Delete )( IITURLTrack * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Play )( IITURLTrack * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddArtworkFromFile )( IITURLTrack * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITArtwork **iArtwork); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITURLTrack * This, /* [retval][out] */ ITTrackKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Playlist )( IITURLTrack * This, /* [retval][out] */ IITPlaylist **iPlaylist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Album )( IITURLTrack * This, /* [retval][out] */ BSTR *album); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Album )( IITURLTrack * This, /* [in] */ BSTR album); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Artist )( IITURLTrack * This, /* [retval][out] */ BSTR *artist); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Artist )( IITURLTrack * This, /* [in] */ BSTR artist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_BitRate )( IITURLTrack * This, /* [retval][out] */ long *bitrate); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_BPM )( IITURLTrack * This, /* [retval][out] */ long *beatsPerMinute); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_BPM )( IITURLTrack * This, /* [in] */ long beatsPerMinute); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Comment )( IITURLTrack * This, /* [retval][out] */ BSTR *comment); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Comment )( IITURLTrack * This, /* [in] */ BSTR comment); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Compilation )( IITURLTrack * This, /* [retval][out] */ VARIANT_BOOL *isCompilation); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Compilation )( IITURLTrack * This, /* [in] */ VARIANT_BOOL shouldBeCompilation); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Composer )( IITURLTrack * This, /* [retval][out] */ BSTR *composer); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Composer )( IITURLTrack * This, /* [in] */ BSTR composer); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DateAdded )( IITURLTrack * This, /* [retval][out] */ DATE *dateAdded); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DiscCount )( IITURLTrack * This, /* [retval][out] */ long *discCount); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_DiscCount )( IITURLTrack * This, /* [in] */ long discCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DiscNumber )( IITURLTrack * This, /* [retval][out] */ long *discNumber); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_DiscNumber )( IITURLTrack * This, /* [in] */ long discNumber); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Duration )( IITURLTrack * This, /* [retval][out] */ long *duration); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Enabled )( IITURLTrack * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Enabled )( IITURLTrack * This, /* [in] */ VARIANT_BOOL shouldBeEnabled); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_EQ )( IITURLTrack * This, /* [retval][out] */ BSTR *eq); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_EQ )( IITURLTrack * This, /* [in] */ BSTR eq); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Finish )( IITURLTrack * This, /* [in] */ long finish); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Finish )( IITURLTrack * This, /* [retval][out] */ long *finish); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Genre )( IITURLTrack * This, /* [retval][out] */ BSTR *genre); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Genre )( IITURLTrack * This, /* [in] */ BSTR genre); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Grouping )( IITURLTrack * This, /* [retval][out] */ BSTR *grouping); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Grouping )( IITURLTrack * This, /* [in] */ BSTR grouping); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_KindAsString )( IITURLTrack * This, /* [retval][out] */ BSTR *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ModificationDate )( IITURLTrack * This, /* [retval][out] */ DATE *dateModified); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayedCount )( IITURLTrack * This, /* [retval][out] */ long *playedCount); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_PlayedCount )( IITURLTrack * This, /* [in] */ long playedCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayedDate )( IITURLTrack * This, /* [retval][out] */ DATE *playedDate); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_PlayedDate )( IITURLTrack * This, /* [in] */ DATE playedDate); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayOrderIndex )( IITURLTrack * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Rating )( IITURLTrack * This, /* [retval][out] */ long *rating); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Rating )( IITURLTrack * This, /* [in] */ long rating); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SampleRate )( IITURLTrack * This, /* [retval][out] */ long *sampleRate); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Size )( IITURLTrack * This, /* [retval][out] */ long *size); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Start )( IITURLTrack * This, /* [retval][out] */ long *start); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Start )( IITURLTrack * This, /* [in] */ long start); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Time )( IITURLTrack * This, /* [retval][out] */ BSTR *time); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackCount )( IITURLTrack * This, /* [retval][out] */ long *trackCount); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_TrackCount )( IITURLTrack * This, /* [in] */ long trackCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackNumber )( IITURLTrack * This, /* [retval][out] */ long *trackNumber); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_TrackNumber )( IITURLTrack * This, /* [in] */ long trackNumber); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_VolumeAdjustment )( IITURLTrack * This, /* [retval][out] */ long *volumeAdjustment); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_VolumeAdjustment )( IITURLTrack * This, /* [in] */ long volumeAdjustment); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Year )( IITURLTrack * This, /* [retval][out] */ long *year); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Year )( IITURLTrack * This, /* [in] */ long year); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Artwork )( IITURLTrack * This, /* [retval][out] */ IITArtworkCollection **iArtworkCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_URL )( IITURLTrack * This, /* [retval][out] */ BSTR *url); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_URL )( IITURLTrack * This, /* [in] */ BSTR url); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Podcast )( IITURLTrack * This, /* [retval][out] */ VARIANT_BOOL *isPodcast); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *UpdatePodcastFeed )( IITURLTrack * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *DownloadPodcastEpisode )( IITURLTrack * This); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Category )( IITURLTrack * This, /* [retval][out] */ BSTR *category); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Category )( IITURLTrack * This, /* [in] */ BSTR category); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Description )( IITURLTrack * This, /* [retval][out] */ BSTR *description); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Description )( IITURLTrack * This, /* [in] */ BSTR description); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_LongDescription )( IITURLTrack * This, /* [retval][out] */ BSTR *longDescription); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_LongDescription )( IITURLTrack * This, /* [in] */ BSTR longDescription); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Reveal )( IITURLTrack * This); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_AlbumRating )( IITURLTrack * This, /* [retval][out] */ long *rating); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_AlbumRating )( IITURLTrack * This, /* [in] */ long rating); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_AlbumRatingKind )( IITURLTrack * This, /* [retval][out] */ ITRatingKind *ratingKind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_RatingKind )( IITURLTrack * This, /* [retval][out] */ ITRatingKind *ratingKind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Playlists )( IITURLTrack * This, /* [retval][out] */ IITPlaylistCollection **iPlaylistCollection); END_INTERFACE } IITURLTrackVtbl; interface IITURLTrack { CONST_VTBL struct IITURLTrackVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITURLTrack_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITURLTrack_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITURLTrack_Release(This) \ (This)->lpVtbl -> Release(This) #define IITURLTrack_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITURLTrack_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITURLTrack_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITURLTrack_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITURLTrack_GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) \ (This)->lpVtbl -> GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) #define IITURLTrack_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITURLTrack_put_Name(This,name) \ (This)->lpVtbl -> put_Name(This,name) #define IITURLTrack_get_Index(This,index) \ (This)->lpVtbl -> get_Index(This,index) #define IITURLTrack_get_SourceID(This,sourceID) \ (This)->lpVtbl -> get_SourceID(This,sourceID) #define IITURLTrack_get_PlaylistID(This,playlistID) \ (This)->lpVtbl -> get_PlaylistID(This,playlistID) #define IITURLTrack_get_TrackID(This,trackID) \ (This)->lpVtbl -> get_TrackID(This,trackID) #define IITURLTrack_get_TrackDatabaseID(This,databaseID) \ (This)->lpVtbl -> get_TrackDatabaseID(This,databaseID) #define IITURLTrack_Delete(This) \ (This)->lpVtbl -> Delete(This) #define IITURLTrack_Play(This) \ (This)->lpVtbl -> Play(This) #define IITURLTrack_AddArtworkFromFile(This,filePath,iArtwork) \ (This)->lpVtbl -> AddArtworkFromFile(This,filePath,iArtwork) #define IITURLTrack_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITURLTrack_get_Playlist(This,iPlaylist) \ (This)->lpVtbl -> get_Playlist(This,iPlaylist) #define IITURLTrack_get_Album(This,album) \ (This)->lpVtbl -> get_Album(This,album) #define IITURLTrack_put_Album(This,album) \ (This)->lpVtbl -> put_Album(This,album) #define IITURLTrack_get_Artist(This,artist) \ (This)->lpVtbl -> get_Artist(This,artist) #define IITURLTrack_put_Artist(This,artist) \ (This)->lpVtbl -> put_Artist(This,artist) #define IITURLTrack_get_BitRate(This,bitrate) \ (This)->lpVtbl -> get_BitRate(This,bitrate) #define IITURLTrack_get_BPM(This,beatsPerMinute) \ (This)->lpVtbl -> get_BPM(This,beatsPerMinute) #define IITURLTrack_put_BPM(This,beatsPerMinute) \ (This)->lpVtbl -> put_BPM(This,beatsPerMinute) #define IITURLTrack_get_Comment(This,comment) \ (This)->lpVtbl -> get_Comment(This,comment) #define IITURLTrack_put_Comment(This,comment) \ (This)->lpVtbl -> put_Comment(This,comment) #define IITURLTrack_get_Compilation(This,isCompilation) \ (This)->lpVtbl -> get_Compilation(This,isCompilation) #define IITURLTrack_put_Compilation(This,shouldBeCompilation) \ (This)->lpVtbl -> put_Compilation(This,shouldBeCompilation) #define IITURLTrack_get_Composer(This,composer) \ (This)->lpVtbl -> get_Composer(This,composer) #define IITURLTrack_put_Composer(This,composer) \ (This)->lpVtbl -> put_Composer(This,composer) #define IITURLTrack_get_DateAdded(This,dateAdded) \ (This)->lpVtbl -> get_DateAdded(This,dateAdded) #define IITURLTrack_get_DiscCount(This,discCount) \ (This)->lpVtbl -> get_DiscCount(This,discCount) #define IITURLTrack_put_DiscCount(This,discCount) \ (This)->lpVtbl -> put_DiscCount(This,discCount) #define IITURLTrack_get_DiscNumber(This,discNumber) \ (This)->lpVtbl -> get_DiscNumber(This,discNumber) #define IITURLTrack_put_DiscNumber(This,discNumber) \ (This)->lpVtbl -> put_DiscNumber(This,discNumber) #define IITURLTrack_get_Duration(This,duration) \ (This)->lpVtbl -> get_Duration(This,duration) #define IITURLTrack_get_Enabled(This,isEnabled) \ (This)->lpVtbl -> get_Enabled(This,isEnabled) #define IITURLTrack_put_Enabled(This,shouldBeEnabled) \ (This)->lpVtbl -> put_Enabled(This,shouldBeEnabled) #define IITURLTrack_get_EQ(This,eq) \ (This)->lpVtbl -> get_EQ(This,eq) #define IITURLTrack_put_EQ(This,eq) \ (This)->lpVtbl -> put_EQ(This,eq) #define IITURLTrack_put_Finish(This,finish) \ (This)->lpVtbl -> put_Finish(This,finish) #define IITURLTrack_get_Finish(This,finish) \ (This)->lpVtbl -> get_Finish(This,finish) #define IITURLTrack_get_Genre(This,genre) \ (This)->lpVtbl -> get_Genre(This,genre) #define IITURLTrack_put_Genre(This,genre) \ (This)->lpVtbl -> put_Genre(This,genre) #define IITURLTrack_get_Grouping(This,grouping) \ (This)->lpVtbl -> get_Grouping(This,grouping) #define IITURLTrack_put_Grouping(This,grouping) \ (This)->lpVtbl -> put_Grouping(This,grouping) #define IITURLTrack_get_KindAsString(This,kind) \ (This)->lpVtbl -> get_KindAsString(This,kind) #define IITURLTrack_get_ModificationDate(This,dateModified) \ (This)->lpVtbl -> get_ModificationDate(This,dateModified) #define IITURLTrack_get_PlayedCount(This,playedCount) \ (This)->lpVtbl -> get_PlayedCount(This,playedCount) #define IITURLTrack_put_PlayedCount(This,playedCount) \ (This)->lpVtbl -> put_PlayedCount(This,playedCount) #define IITURLTrack_get_PlayedDate(This,playedDate) \ (This)->lpVtbl -> get_PlayedDate(This,playedDate) #define IITURLTrack_put_PlayedDate(This,playedDate) \ (This)->lpVtbl -> put_PlayedDate(This,playedDate) #define IITURLTrack_get_PlayOrderIndex(This,index) \ (This)->lpVtbl -> get_PlayOrderIndex(This,index) #define IITURLTrack_get_Rating(This,rating) \ (This)->lpVtbl -> get_Rating(This,rating) #define IITURLTrack_put_Rating(This,rating) \ (This)->lpVtbl -> put_Rating(This,rating) #define IITURLTrack_get_SampleRate(This,sampleRate) \ (This)->lpVtbl -> get_SampleRate(This,sampleRate) #define IITURLTrack_get_Size(This,size) \ (This)->lpVtbl -> get_Size(This,size) #define IITURLTrack_get_Start(This,start) \ (This)->lpVtbl -> get_Start(This,start) #define IITURLTrack_put_Start(This,start) \ (This)->lpVtbl -> put_Start(This,start) #define IITURLTrack_get_Time(This,time) \ (This)->lpVtbl -> get_Time(This,time) #define IITURLTrack_get_TrackCount(This,trackCount) \ (This)->lpVtbl -> get_TrackCount(This,trackCount) #define IITURLTrack_put_TrackCount(This,trackCount) \ (This)->lpVtbl -> put_TrackCount(This,trackCount) #define IITURLTrack_get_TrackNumber(This,trackNumber) \ (This)->lpVtbl -> get_TrackNumber(This,trackNumber) #define IITURLTrack_put_TrackNumber(This,trackNumber) \ (This)->lpVtbl -> put_TrackNumber(This,trackNumber) #define IITURLTrack_get_VolumeAdjustment(This,volumeAdjustment) \ (This)->lpVtbl -> get_VolumeAdjustment(This,volumeAdjustment) #define IITURLTrack_put_VolumeAdjustment(This,volumeAdjustment) \ (This)->lpVtbl -> put_VolumeAdjustment(This,volumeAdjustment) #define IITURLTrack_get_Year(This,year) \ (This)->lpVtbl -> get_Year(This,year) #define IITURLTrack_put_Year(This,year) \ (This)->lpVtbl -> put_Year(This,year) #define IITURLTrack_get_Artwork(This,iArtworkCollection) \ (This)->lpVtbl -> get_Artwork(This,iArtworkCollection) #define IITURLTrack_get_URL(This,url) \ (This)->lpVtbl -> get_URL(This,url) #define IITURLTrack_put_URL(This,url) \ (This)->lpVtbl -> put_URL(This,url) #define IITURLTrack_get_Podcast(This,isPodcast) \ (This)->lpVtbl -> get_Podcast(This,isPodcast) #define IITURLTrack_UpdatePodcastFeed(This) \ (This)->lpVtbl -> UpdatePodcastFeed(This) #define IITURLTrack_DownloadPodcastEpisode(This) \ (This)->lpVtbl -> DownloadPodcastEpisode(This) #define IITURLTrack_get_Category(This,category) \ (This)->lpVtbl -> get_Category(This,category) #define IITURLTrack_put_Category(This,category) \ (This)->lpVtbl -> put_Category(This,category) #define IITURLTrack_get_Description(This,description) \ (This)->lpVtbl -> get_Description(This,description) #define IITURLTrack_put_Description(This,description) \ (This)->lpVtbl -> put_Description(This,description) #define IITURLTrack_get_LongDescription(This,longDescription) \ (This)->lpVtbl -> get_LongDescription(This,longDescription) #define IITURLTrack_put_LongDescription(This,longDescription) \ (This)->lpVtbl -> put_LongDescription(This,longDescription) #define IITURLTrack_Reveal(This) \ (This)->lpVtbl -> Reveal(This) #define IITURLTrack_get_AlbumRating(This,rating) \ (This)->lpVtbl -> get_AlbumRating(This,rating) #define IITURLTrack_put_AlbumRating(This,rating) \ (This)->lpVtbl -> put_AlbumRating(This,rating) #define IITURLTrack_get_AlbumRatingKind(This,ratingKind) \ (This)->lpVtbl -> get_AlbumRatingKind(This,ratingKind) #define IITURLTrack_get_RatingKind(This,ratingKind) \ (This)->lpVtbl -> get_RatingKind(This,ratingKind) #define IITURLTrack_get_Playlists(This,iPlaylistCollection) \ (This)->lpVtbl -> get_Playlists(This,iPlaylistCollection) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITURLTrack_get_URL_Proxy( IITURLTrack * This, /* [retval][out] */ BSTR *url); void __RPC_STUB IITURLTrack_get_URL_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITURLTrack_put_URL_Proxy( IITURLTrack * This, /* [in] */ BSTR url); void __RPC_STUB IITURLTrack_put_URL_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITURLTrack_get_Podcast_Proxy( IITURLTrack * This, /* [retval][out] */ VARIANT_BOOL *isPodcast); void __RPC_STUB IITURLTrack_get_Podcast_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITURLTrack_UpdatePodcastFeed_Proxy( IITURLTrack * This); void __RPC_STUB IITURLTrack_UpdatePodcastFeed_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITURLTrack_DownloadPodcastEpisode_Proxy( IITURLTrack * This); void __RPC_STUB IITURLTrack_DownloadPodcastEpisode_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITURLTrack_get_Category_Proxy( IITURLTrack * This, /* [retval][out] */ BSTR *category); void __RPC_STUB IITURLTrack_get_Category_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITURLTrack_put_Category_Proxy( IITURLTrack * This, /* [in] */ BSTR category); void __RPC_STUB IITURLTrack_put_Category_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITURLTrack_get_Description_Proxy( IITURLTrack * This, /* [retval][out] */ BSTR *description); void __RPC_STUB IITURLTrack_get_Description_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITURLTrack_put_Description_Proxy( IITURLTrack * This, /* [in] */ BSTR description); void __RPC_STUB IITURLTrack_put_Description_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITURLTrack_get_LongDescription_Proxy( IITURLTrack * This, /* [retval][out] */ BSTR *longDescription); void __RPC_STUB IITURLTrack_get_LongDescription_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITURLTrack_put_LongDescription_Proxy( IITURLTrack * This, /* [in] */ BSTR longDescription); void __RPC_STUB IITURLTrack_put_LongDescription_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITURLTrack_Reveal_Proxy( IITURLTrack * This); void __RPC_STUB IITURLTrack_Reveal_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITURLTrack_get_AlbumRating_Proxy( IITURLTrack * This, /* [retval][out] */ long *rating); void __RPC_STUB IITURLTrack_get_AlbumRating_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITURLTrack_put_AlbumRating_Proxy( IITURLTrack * This, /* [in] */ long rating); void __RPC_STUB IITURLTrack_put_AlbumRating_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITURLTrack_get_AlbumRatingKind_Proxy( IITURLTrack * This, /* [retval][out] */ ITRatingKind *ratingKind); void __RPC_STUB IITURLTrack_get_AlbumRatingKind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITURLTrack_get_RatingKind_Proxy( IITURLTrack * This, /* [retval][out] */ ITRatingKind *ratingKind); void __RPC_STUB IITURLTrack_get_RatingKind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITURLTrack_get_Playlists_Proxy( IITURLTrack * This, /* [retval][out] */ IITPlaylistCollection **iPlaylistCollection); void __RPC_STUB IITURLTrack_get_Playlists_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITURLTrack_INTERFACE_DEFINED__ */ #ifndef __IITAudioCDPlaylist_INTERFACE_DEFINED__ #define __IITAudioCDPlaylist_INTERFACE_DEFINED__ /* interface IITAudioCDPlaylist */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITAudioCDPlaylist; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("CF496DF3-0FED-4d7d-9BD8-529B6E8A082E") IITAudioCDPlaylist : public IITPlaylist { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Artist( /* [retval][out] */ BSTR *artist) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Compilation( /* [retval][out] */ VARIANT_BOOL *isCompiliation) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Composer( /* [retval][out] */ BSTR *composer) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_DiscCount( /* [retval][out] */ long *discCount) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_DiscNumber( /* [retval][out] */ long *discNumber) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Genre( /* [retval][out] */ BSTR *genre) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Year( /* [retval][out] */ long *year) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Reveal( void) = 0; }; #else /* C style interface */ typedef struct IITAudioCDPlaylistVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITAudioCDPlaylist * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITAudioCDPlaylist * This); ULONG ( STDMETHODCALLTYPE *Release )( IITAudioCDPlaylist * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITAudioCDPlaylist * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITAudioCDPlaylist * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITAudioCDPlaylist * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITAudioCDPlaylist * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectIDs )( IITAudioCDPlaylist * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITAudioCDPlaylist * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Name )( IITAudioCDPlaylist * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Index )( IITAudioCDPlaylist * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SourceID )( IITAudioCDPlaylist * This, /* [retval][out] */ long *sourceID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlaylistID )( IITAudioCDPlaylist * This, /* [retval][out] */ long *playlistID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackID )( IITAudioCDPlaylist * This, /* [retval][out] */ long *trackID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackDatabaseID )( IITAudioCDPlaylist * This, /* [retval][out] */ long *databaseID); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Delete )( IITAudioCDPlaylist * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *PlayFirstTrack )( IITAudioCDPlaylist * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Print )( IITAudioCDPlaylist * This, /* [in] */ VARIANT_BOOL showPrintDialog, /* [in] */ ITPlaylistPrintKind printKind, /* [in] */ BSTR theme); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Search )( IITAudioCDPlaylist * This, /* [in] */ BSTR searchText, /* [in] */ ITPlaylistSearchField searchFields, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITAudioCDPlaylist * This, /* [retval][out] */ ITPlaylistKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Source )( IITAudioCDPlaylist * This, /* [retval][out] */ IITSource **iSource); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Duration )( IITAudioCDPlaylist * This, /* [retval][out] */ long *duration); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Shuffle )( IITAudioCDPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isShuffle); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Shuffle )( IITAudioCDPlaylist * This, /* [in] */ VARIANT_BOOL shouldShuffle); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Size )( IITAudioCDPlaylist * This, /* [retval][out] */ double *size); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SongRepeat )( IITAudioCDPlaylist * This, /* [retval][out] */ ITPlaylistRepeatMode *repeatMode); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SongRepeat )( IITAudioCDPlaylist * This, /* [in] */ ITPlaylistRepeatMode repeatMode); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Time )( IITAudioCDPlaylist * This, /* [retval][out] */ BSTR *time); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Visible )( IITAudioCDPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isVisible); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Tracks )( IITAudioCDPlaylist * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Artist )( IITAudioCDPlaylist * This, /* [retval][out] */ BSTR *artist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Compilation )( IITAudioCDPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isCompiliation); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Composer )( IITAudioCDPlaylist * This, /* [retval][out] */ BSTR *composer); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DiscCount )( IITAudioCDPlaylist * This, /* [retval][out] */ long *discCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DiscNumber )( IITAudioCDPlaylist * This, /* [retval][out] */ long *discNumber); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Genre )( IITAudioCDPlaylist * This, /* [retval][out] */ BSTR *genre); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Year )( IITAudioCDPlaylist * This, /* [retval][out] */ long *year); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Reveal )( IITAudioCDPlaylist * This); END_INTERFACE } IITAudioCDPlaylistVtbl; interface IITAudioCDPlaylist { CONST_VTBL struct IITAudioCDPlaylistVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITAudioCDPlaylist_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITAudioCDPlaylist_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITAudioCDPlaylist_Release(This) \ (This)->lpVtbl -> Release(This) #define IITAudioCDPlaylist_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITAudioCDPlaylist_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITAudioCDPlaylist_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITAudioCDPlaylist_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITAudioCDPlaylist_GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) \ (This)->lpVtbl -> GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) #define IITAudioCDPlaylist_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITAudioCDPlaylist_put_Name(This,name) \ (This)->lpVtbl -> put_Name(This,name) #define IITAudioCDPlaylist_get_Index(This,index) \ (This)->lpVtbl -> get_Index(This,index) #define IITAudioCDPlaylist_get_SourceID(This,sourceID) \ (This)->lpVtbl -> get_SourceID(This,sourceID) #define IITAudioCDPlaylist_get_PlaylistID(This,playlistID) \ (This)->lpVtbl -> get_PlaylistID(This,playlistID) #define IITAudioCDPlaylist_get_TrackID(This,trackID) \ (This)->lpVtbl -> get_TrackID(This,trackID) #define IITAudioCDPlaylist_get_TrackDatabaseID(This,databaseID) \ (This)->lpVtbl -> get_TrackDatabaseID(This,databaseID) #define IITAudioCDPlaylist_Delete(This) \ (This)->lpVtbl -> Delete(This) #define IITAudioCDPlaylist_PlayFirstTrack(This) \ (This)->lpVtbl -> PlayFirstTrack(This) #define IITAudioCDPlaylist_Print(This,showPrintDialog,printKind,theme) \ (This)->lpVtbl -> Print(This,showPrintDialog,printKind,theme) #define IITAudioCDPlaylist_Search(This,searchText,searchFields,iTrackCollection) \ (This)->lpVtbl -> Search(This,searchText,searchFields,iTrackCollection) #define IITAudioCDPlaylist_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITAudioCDPlaylist_get_Source(This,iSource) \ (This)->lpVtbl -> get_Source(This,iSource) #define IITAudioCDPlaylist_get_Duration(This,duration) \ (This)->lpVtbl -> get_Duration(This,duration) #define IITAudioCDPlaylist_get_Shuffle(This,isShuffle) \ (This)->lpVtbl -> get_Shuffle(This,isShuffle) #define IITAudioCDPlaylist_put_Shuffle(This,shouldShuffle) \ (This)->lpVtbl -> put_Shuffle(This,shouldShuffle) #define IITAudioCDPlaylist_get_Size(This,size) \ (This)->lpVtbl -> get_Size(This,size) #define IITAudioCDPlaylist_get_SongRepeat(This,repeatMode) \ (This)->lpVtbl -> get_SongRepeat(This,repeatMode) #define IITAudioCDPlaylist_put_SongRepeat(This,repeatMode) \ (This)->lpVtbl -> put_SongRepeat(This,repeatMode) #define IITAudioCDPlaylist_get_Time(This,time) \ (This)->lpVtbl -> get_Time(This,time) #define IITAudioCDPlaylist_get_Visible(This,isVisible) \ (This)->lpVtbl -> get_Visible(This,isVisible) #define IITAudioCDPlaylist_get_Tracks(This,iTrackCollection) \ (This)->lpVtbl -> get_Tracks(This,iTrackCollection) #define IITAudioCDPlaylist_get_Artist(This,artist) \ (This)->lpVtbl -> get_Artist(This,artist) #define IITAudioCDPlaylist_get_Compilation(This,isCompiliation) \ (This)->lpVtbl -> get_Compilation(This,isCompiliation) #define IITAudioCDPlaylist_get_Composer(This,composer) \ (This)->lpVtbl -> get_Composer(This,composer) #define IITAudioCDPlaylist_get_DiscCount(This,discCount) \ (This)->lpVtbl -> get_DiscCount(This,discCount) #define IITAudioCDPlaylist_get_DiscNumber(This,discNumber) \ (This)->lpVtbl -> get_DiscNumber(This,discNumber) #define IITAudioCDPlaylist_get_Genre(This,genre) \ (This)->lpVtbl -> get_Genre(This,genre) #define IITAudioCDPlaylist_get_Year(This,year) \ (This)->lpVtbl -> get_Year(This,year) #define IITAudioCDPlaylist_Reveal(This) \ (This)->lpVtbl -> Reveal(This) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITAudioCDPlaylist_get_Artist_Proxy( IITAudioCDPlaylist * This, /* [retval][out] */ BSTR *artist); void __RPC_STUB IITAudioCDPlaylist_get_Artist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITAudioCDPlaylist_get_Compilation_Proxy( IITAudioCDPlaylist * This, /* [retval][out] */ VARIANT_BOOL *isCompiliation); void __RPC_STUB IITAudioCDPlaylist_get_Compilation_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITAudioCDPlaylist_get_Composer_Proxy( IITAudioCDPlaylist * This, /* [retval][out] */ BSTR *composer); void __RPC_STUB IITAudioCDPlaylist_get_Composer_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITAudioCDPlaylist_get_DiscCount_Proxy( IITAudioCDPlaylist * This, /* [retval][out] */ long *discCount); void __RPC_STUB IITAudioCDPlaylist_get_DiscCount_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITAudioCDPlaylist_get_DiscNumber_Proxy( IITAudioCDPlaylist * This, /* [retval][out] */ long *discNumber); void __RPC_STUB IITAudioCDPlaylist_get_DiscNumber_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITAudioCDPlaylist_get_Genre_Proxy( IITAudioCDPlaylist * This, /* [retval][out] */ BSTR *genre); void __RPC_STUB IITAudioCDPlaylist_get_Genre_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITAudioCDPlaylist_get_Year_Proxy( IITAudioCDPlaylist * This, /* [retval][out] */ long *year); void __RPC_STUB IITAudioCDPlaylist_get_Year_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITAudioCDPlaylist_Reveal_Proxy( IITAudioCDPlaylist * This); void __RPC_STUB IITAudioCDPlaylist_Reveal_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITAudioCDPlaylist_INTERFACE_DEFINED__ */ #ifndef __IITPlaylistCollection_INTERFACE_DEFINED__ #define __IITPlaylistCollection_INTERFACE_DEFINED__ /* interface IITPlaylistCollection */ /* [unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITPlaylistCollection; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("FF194254-909D-4437-9C50-3AAC2AE6305C") IITPlaylistCollection : public IDispatch { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Count( /* [retval][out] */ long *count) = 0; virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Item( /* [in] */ long index, /* [retval][out] */ IITPlaylist **iPlaylist) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByName( /* [in] */ BSTR name, /* [retval][out] */ IITPlaylist **iPlaylist) = 0; virtual /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE get__NewEnum( /* [retval][out] */ IUnknown **iEnumerator) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ItemByPersistentID( /* [in] */ long highID, /* [in] */ long lowID, /* [retval][out] */ IITPlaylist **iPlaylist) = 0; }; #else /* C style interface */ typedef struct IITPlaylistCollectionVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITPlaylistCollection * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITPlaylistCollection * This); ULONG ( STDMETHODCALLTYPE *Release )( IITPlaylistCollection * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITPlaylistCollection * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITPlaylistCollection * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITPlaylistCollection * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITPlaylistCollection * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Count )( IITPlaylistCollection * This, /* [retval][out] */ long *count); /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Item )( IITPlaylistCollection * This, /* [in] */ long index, /* [retval][out] */ IITPlaylist **iPlaylist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByName )( IITPlaylistCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITPlaylist **iPlaylist); /* [helpstring][restricted][id][propget] */ HRESULT ( STDMETHODCALLTYPE *get__NewEnum )( IITPlaylistCollection * This, /* [retval][out] */ IUnknown **iEnumerator); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ItemByPersistentID )( IITPlaylistCollection * This, /* [in] */ long highID, /* [in] */ long lowID, /* [retval][out] */ IITPlaylist **iPlaylist); END_INTERFACE } IITPlaylistCollectionVtbl; interface IITPlaylistCollection { CONST_VTBL struct IITPlaylistCollectionVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITPlaylistCollection_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITPlaylistCollection_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITPlaylistCollection_Release(This) \ (This)->lpVtbl -> Release(This) #define IITPlaylistCollection_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITPlaylistCollection_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITPlaylistCollection_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITPlaylistCollection_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITPlaylistCollection_get_Count(This,count) \ (This)->lpVtbl -> get_Count(This,count) #define IITPlaylistCollection_get_Item(This,index,iPlaylist) \ (This)->lpVtbl -> get_Item(This,index,iPlaylist) #define IITPlaylistCollection_get_ItemByName(This,name,iPlaylist) \ (This)->lpVtbl -> get_ItemByName(This,name,iPlaylist) #define IITPlaylistCollection_get__NewEnum(This,iEnumerator) \ (This)->lpVtbl -> get__NewEnum(This,iEnumerator) #define IITPlaylistCollection_get_ItemByPersistentID(This,highID,lowID,iPlaylist) \ (This)->lpVtbl -> get_ItemByPersistentID(This,highID,lowID,iPlaylist) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylistCollection_get_Count_Proxy( IITPlaylistCollection * This, /* [retval][out] */ long *count); void __RPC_STUB IITPlaylistCollection_get_Count_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylistCollection_get_Item_Proxy( IITPlaylistCollection * This, /* [in] */ long index, /* [retval][out] */ IITPlaylist **iPlaylist); void __RPC_STUB IITPlaylistCollection_get_Item_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylistCollection_get_ItemByName_Proxy( IITPlaylistCollection * This, /* [in] */ BSTR name, /* [retval][out] */ IITPlaylist **iPlaylist); void __RPC_STUB IITPlaylistCollection_get_ItemByName_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][restricted][id][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylistCollection_get__NewEnum_Proxy( IITPlaylistCollection * This, /* [retval][out] */ IUnknown **iEnumerator); void __RPC_STUB IITPlaylistCollection_get__NewEnum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylistCollection_get_ItemByPersistentID_Proxy( IITPlaylistCollection * This, /* [in] */ long highID, /* [in] */ long lowID, /* [retval][out] */ IITPlaylist **iPlaylist); void __RPC_STUB IITPlaylistCollection_get_ItemByPersistentID_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITPlaylistCollection_INTERFACE_DEFINED__ */ #ifndef __IITIPodSource_INTERFACE_DEFINED__ #define __IITIPodSource_INTERFACE_DEFINED__ /* interface IITIPodSource */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITIPodSource; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("CF4D8ACE-1720-4fb9-B0AE-9877249E89B0") IITIPodSource : public IITSource { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE UpdateIPod( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE EjectIPod( void) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SoftwareVersion( /* [retval][out] */ BSTR *softwareVersion) = 0; }; #else /* C style interface */ typedef struct IITIPodSourceVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITIPodSource * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITIPodSource * This); ULONG ( STDMETHODCALLTYPE *Release )( IITIPodSource * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITIPodSource * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITIPodSource * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITIPodSource * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITIPodSource * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectIDs )( IITIPodSource * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITIPodSource * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Name )( IITIPodSource * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Index )( IITIPodSource * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SourceID )( IITIPodSource * This, /* [retval][out] */ long *sourceID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlaylistID )( IITIPodSource * This, /* [retval][out] */ long *playlistID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackID )( IITIPodSource * This, /* [retval][out] */ long *trackID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackDatabaseID )( IITIPodSource * This, /* [retval][out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITIPodSource * This, /* [retval][out] */ ITSourceKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Capacity )( IITIPodSource * This, /* [retval][out] */ double *capacity); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_FreeSpace )( IITIPodSource * This, /* [retval][out] */ double *freespace); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Playlists )( IITIPodSource * This, /* [retval][out] */ IITPlaylistCollection **iPlaylistCollection); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *UpdateIPod )( IITIPodSource * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *EjectIPod )( IITIPodSource * This); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SoftwareVersion )( IITIPodSource * This, /* [retval][out] */ BSTR *softwareVersion); END_INTERFACE } IITIPodSourceVtbl; interface IITIPodSource { CONST_VTBL struct IITIPodSourceVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITIPodSource_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITIPodSource_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITIPodSource_Release(This) \ (This)->lpVtbl -> Release(This) #define IITIPodSource_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITIPodSource_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITIPodSource_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITIPodSource_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITIPodSource_GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) \ (This)->lpVtbl -> GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) #define IITIPodSource_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITIPodSource_put_Name(This,name) \ (This)->lpVtbl -> put_Name(This,name) #define IITIPodSource_get_Index(This,index) \ (This)->lpVtbl -> get_Index(This,index) #define IITIPodSource_get_SourceID(This,sourceID) \ (This)->lpVtbl -> get_SourceID(This,sourceID) #define IITIPodSource_get_PlaylistID(This,playlistID) \ (This)->lpVtbl -> get_PlaylistID(This,playlistID) #define IITIPodSource_get_TrackID(This,trackID) \ (This)->lpVtbl -> get_TrackID(This,trackID) #define IITIPodSource_get_TrackDatabaseID(This,databaseID) \ (This)->lpVtbl -> get_TrackDatabaseID(This,databaseID) #define IITIPodSource_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITIPodSource_get_Capacity(This,capacity) \ (This)->lpVtbl -> get_Capacity(This,capacity) #define IITIPodSource_get_FreeSpace(This,freespace) \ (This)->lpVtbl -> get_FreeSpace(This,freespace) #define IITIPodSource_get_Playlists(This,iPlaylistCollection) \ (This)->lpVtbl -> get_Playlists(This,iPlaylistCollection) #define IITIPodSource_UpdateIPod(This) \ (This)->lpVtbl -> UpdateIPod(This) #define IITIPodSource_EjectIPod(This) \ (This)->lpVtbl -> EjectIPod(This) #define IITIPodSource_get_SoftwareVersion(This,softwareVersion) \ (This)->lpVtbl -> get_SoftwareVersion(This,softwareVersion) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITIPodSource_UpdateIPod_Proxy( IITIPodSource * This); void __RPC_STUB IITIPodSource_UpdateIPod_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITIPodSource_EjectIPod_Proxy( IITIPodSource * This); void __RPC_STUB IITIPodSource_EjectIPod_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITIPodSource_get_SoftwareVersion_Proxy( IITIPodSource * This, /* [retval][out] */ BSTR *softwareVersion); void __RPC_STUB IITIPodSource_get_SoftwareVersion_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITIPodSource_INTERFACE_DEFINED__ */ #ifndef __IITFileOrCDTrack_INTERFACE_DEFINED__ #define __IITFileOrCDTrack_INTERFACE_DEFINED__ /* interface IITFileOrCDTrack */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITFileOrCDTrack; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("00D7FE99-7868-4cc7-AD9E-ACFD70D09566") IITFileOrCDTrack : public IITTrack { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Location( /* [retval][out] */ BSTR *location) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE UpdateInfoFromFile( void) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Podcast( /* [retval][out] */ VARIANT_BOOL *isPodcast) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE UpdatePodcastFeed( void) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_RememberBookmark( /* [retval][out] */ VARIANT_BOOL *rememberBookmark) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_RememberBookmark( /* [in] */ VARIANT_BOOL shouldRememberBookmark) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ExcludeFromShuffle( /* [retval][out] */ VARIANT_BOOL *excludeFromShuffle) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_ExcludeFromShuffle( /* [in] */ VARIANT_BOOL shouldExcludeFromShuffle) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Lyrics( /* [retval][out] */ BSTR *lyrics) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Lyrics( /* [in] */ BSTR lyrics) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Category( /* [retval][out] */ BSTR *category) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Category( /* [in] */ BSTR category) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Description( /* [retval][out] */ BSTR *description) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Description( /* [in] */ BSTR description) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_LongDescription( /* [retval][out] */ BSTR *longDescription) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_LongDescription( /* [in] */ BSTR longDescription) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_BookmarkTime( /* [retval][out] */ long *bookmarkTime) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_BookmarkTime( /* [in] */ long bookmarkTime) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_VideoKind( /* [retval][out] */ ITVideoKind *videoKind) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_VideoKind( /* [in] */ ITVideoKind videoKind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SkippedCount( /* [retval][out] */ long *skippedCount) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SkippedCount( /* [in] */ long skippedCount) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SkippedDate( /* [retval][out] */ DATE *skippedDate) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SkippedDate( /* [in] */ DATE skippedDate) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_PartOfGaplessAlbum( /* [retval][out] */ VARIANT_BOOL *partOfGaplessAlbum) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_PartOfGaplessAlbum( /* [in] */ VARIANT_BOOL shouldBePartOfGaplessAlbum) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_AlbumArtist( /* [retval][out] */ BSTR *albumArtist) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_AlbumArtist( /* [in] */ BSTR albumArtist) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Show( /* [retval][out] */ BSTR *showName) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Show( /* [in] */ BSTR showName) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SeasonNumber( /* [retval][out] */ long *seasonNumber) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SeasonNumber( /* [in] */ long seasonNumber) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_EpisodeID( /* [retval][out] */ BSTR *episodeID) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_EpisodeID( /* [in] */ BSTR episodeID) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_EpisodeNumber( /* [retval][out] */ long *episodeNumber) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_EpisodeNumber( /* [in] */ long episodeNumber) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Size64High( /* [retval][out] */ long *sizeHigh) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Size64Low( /* [retval][out] */ long *sizeLow) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Unplayed( /* [retval][out] */ VARIANT_BOOL *isUnplayed) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Unplayed( /* [in] */ VARIANT_BOOL shouldBeUnplayed) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SortAlbum( /* [retval][out] */ BSTR *album) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SortAlbum( /* [in] */ BSTR album) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SortAlbumArtist( /* [retval][out] */ BSTR *albumArtist) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SortAlbumArtist( /* [in] */ BSTR albumArtist) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SortArtist( /* [retval][out] */ BSTR *artist) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SortArtist( /* [in] */ BSTR artist) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SortComposer( /* [retval][out] */ BSTR *composer) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SortComposer( /* [in] */ BSTR composer) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SortName( /* [retval][out] */ BSTR *name) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SortName( /* [in] */ BSTR name) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SortShow( /* [retval][out] */ BSTR *showName) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_SortShow( /* [in] */ BSTR showName) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Reveal( void) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_AlbumRating( /* [retval][out] */ long *rating) = 0; virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_AlbumRating( /* [in] */ long rating) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_AlbumRatingKind( /* [retval][out] */ ITRatingKind *ratingKind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_RatingKind( /* [retval][out] */ ITRatingKind *ratingKind) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Playlists( /* [retval][out] */ IITPlaylistCollection **iPlaylistCollection) = 0; }; #else /* C style interface */ typedef struct IITFileOrCDTrackVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITFileOrCDTrack * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITFileOrCDTrack * This); ULONG ( STDMETHODCALLTYPE *Release )( IITFileOrCDTrack * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITFileOrCDTrack * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITFileOrCDTrack * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITFileOrCDTrack * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITFileOrCDTrack * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetITObjectIDs )( IITFileOrCDTrack * This, /* [out] */ long *sourceID, /* [out] */ long *playlistID, /* [out] */ long *trackID, /* [out] */ long *databaseID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Name )( IITFileOrCDTrack * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Index )( IITFileOrCDTrack * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SourceID )( IITFileOrCDTrack * This, /* [retval][out] */ long *sourceID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlaylistID )( IITFileOrCDTrack * This, /* [retval][out] */ long *playlistID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackID )( IITFileOrCDTrack * This, /* [retval][out] */ long *trackID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackDatabaseID )( IITFileOrCDTrack * This, /* [retval][out] */ long *databaseID); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Delete )( IITFileOrCDTrack * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Play )( IITFileOrCDTrack * This); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *AddArtworkFromFile )( IITFileOrCDTrack * This, /* [in] */ BSTR filePath, /* [retval][out] */ IITArtwork **iArtwork); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITFileOrCDTrack * This, /* [retval][out] */ ITTrackKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Playlist )( IITFileOrCDTrack * This, /* [retval][out] */ IITPlaylist **iPlaylist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Album )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *album); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Album )( IITFileOrCDTrack * This, /* [in] */ BSTR album); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Artist )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *artist); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Artist )( IITFileOrCDTrack * This, /* [in] */ BSTR artist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_BitRate )( IITFileOrCDTrack * This, /* [retval][out] */ long *bitrate); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_BPM )( IITFileOrCDTrack * This, /* [retval][out] */ long *beatsPerMinute); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_BPM )( IITFileOrCDTrack * This, /* [in] */ long beatsPerMinute); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Comment )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *comment); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Comment )( IITFileOrCDTrack * This, /* [in] */ BSTR comment); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Compilation )( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *isCompilation); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Compilation )( IITFileOrCDTrack * This, /* [in] */ VARIANT_BOOL shouldBeCompilation); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Composer )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *composer); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Composer )( IITFileOrCDTrack * This, /* [in] */ BSTR composer); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DateAdded )( IITFileOrCDTrack * This, /* [retval][out] */ DATE *dateAdded); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DiscCount )( IITFileOrCDTrack * This, /* [retval][out] */ long *discCount); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_DiscCount )( IITFileOrCDTrack * This, /* [in] */ long discCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_DiscNumber )( IITFileOrCDTrack * This, /* [retval][out] */ long *discNumber); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_DiscNumber )( IITFileOrCDTrack * This, /* [in] */ long discNumber); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Duration )( IITFileOrCDTrack * This, /* [retval][out] */ long *duration); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Enabled )( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *isEnabled); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Enabled )( IITFileOrCDTrack * This, /* [in] */ VARIANT_BOOL shouldBeEnabled); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_EQ )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *eq); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_EQ )( IITFileOrCDTrack * This, /* [in] */ BSTR eq); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Finish )( IITFileOrCDTrack * This, /* [in] */ long finish); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Finish )( IITFileOrCDTrack * This, /* [retval][out] */ long *finish); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Genre )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *genre); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Genre )( IITFileOrCDTrack * This, /* [in] */ BSTR genre); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Grouping )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *grouping); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Grouping )( IITFileOrCDTrack * This, /* [in] */ BSTR grouping); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_KindAsString )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ModificationDate )( IITFileOrCDTrack * This, /* [retval][out] */ DATE *dateModified); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayedCount )( IITFileOrCDTrack * This, /* [retval][out] */ long *playedCount); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_PlayedCount )( IITFileOrCDTrack * This, /* [in] */ long playedCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayedDate )( IITFileOrCDTrack * This, /* [retval][out] */ DATE *playedDate); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_PlayedDate )( IITFileOrCDTrack * This, /* [in] */ DATE playedDate); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PlayOrderIndex )( IITFileOrCDTrack * This, /* [retval][out] */ long *index); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Rating )( IITFileOrCDTrack * This, /* [retval][out] */ long *rating); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Rating )( IITFileOrCDTrack * This, /* [in] */ long rating); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SampleRate )( IITFileOrCDTrack * This, /* [retval][out] */ long *sampleRate); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Size )( IITFileOrCDTrack * This, /* [retval][out] */ long *size); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Start )( IITFileOrCDTrack * This, /* [retval][out] */ long *start); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Start )( IITFileOrCDTrack * This, /* [in] */ long start); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Time )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *time); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackCount )( IITFileOrCDTrack * This, /* [retval][out] */ long *trackCount); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_TrackCount )( IITFileOrCDTrack * This, /* [in] */ long trackCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_TrackNumber )( IITFileOrCDTrack * This, /* [retval][out] */ long *trackNumber); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_TrackNumber )( IITFileOrCDTrack * This, /* [in] */ long trackNumber); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_VolumeAdjustment )( IITFileOrCDTrack * This, /* [retval][out] */ long *volumeAdjustment); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_VolumeAdjustment )( IITFileOrCDTrack * This, /* [in] */ long volumeAdjustment); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Year )( IITFileOrCDTrack * This, /* [retval][out] */ long *year); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Year )( IITFileOrCDTrack * This, /* [in] */ long year); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Artwork )( IITFileOrCDTrack * This, /* [retval][out] */ IITArtworkCollection **iArtworkCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Location )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *location); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *UpdateInfoFromFile )( IITFileOrCDTrack * This); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Podcast )( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *isPodcast); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *UpdatePodcastFeed )( IITFileOrCDTrack * This); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_RememberBookmark )( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *rememberBookmark); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_RememberBookmark )( IITFileOrCDTrack * This, /* [in] */ VARIANT_BOOL shouldRememberBookmark); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_ExcludeFromShuffle )( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *excludeFromShuffle); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_ExcludeFromShuffle )( IITFileOrCDTrack * This, /* [in] */ VARIANT_BOOL shouldExcludeFromShuffle); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Lyrics )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *lyrics); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Lyrics )( IITFileOrCDTrack * This, /* [in] */ BSTR lyrics); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Category )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *category); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Category )( IITFileOrCDTrack * This, /* [in] */ BSTR category); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Description )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *description); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Description )( IITFileOrCDTrack * This, /* [in] */ BSTR description); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_LongDescription )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *longDescription); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_LongDescription )( IITFileOrCDTrack * This, /* [in] */ BSTR longDescription); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_BookmarkTime )( IITFileOrCDTrack * This, /* [retval][out] */ long *bookmarkTime); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_BookmarkTime )( IITFileOrCDTrack * This, /* [in] */ long bookmarkTime); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_VideoKind )( IITFileOrCDTrack * This, /* [retval][out] */ ITVideoKind *videoKind); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_VideoKind )( IITFileOrCDTrack * This, /* [in] */ ITVideoKind videoKind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SkippedCount )( IITFileOrCDTrack * This, /* [retval][out] */ long *skippedCount); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SkippedCount )( IITFileOrCDTrack * This, /* [in] */ long skippedCount); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SkippedDate )( IITFileOrCDTrack * This, /* [retval][out] */ DATE *skippedDate); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SkippedDate )( IITFileOrCDTrack * This, /* [in] */ DATE skippedDate); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_PartOfGaplessAlbum )( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *partOfGaplessAlbum); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_PartOfGaplessAlbum )( IITFileOrCDTrack * This, /* [in] */ VARIANT_BOOL shouldBePartOfGaplessAlbum); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_AlbumArtist )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *albumArtist); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_AlbumArtist )( IITFileOrCDTrack * This, /* [in] */ BSTR albumArtist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Show )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *showName); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Show )( IITFileOrCDTrack * This, /* [in] */ BSTR showName); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SeasonNumber )( IITFileOrCDTrack * This, /* [retval][out] */ long *seasonNumber); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SeasonNumber )( IITFileOrCDTrack * This, /* [in] */ long seasonNumber); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_EpisodeID )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *episodeID); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_EpisodeID )( IITFileOrCDTrack * This, /* [in] */ BSTR episodeID); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_EpisodeNumber )( IITFileOrCDTrack * This, /* [retval][out] */ long *episodeNumber); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_EpisodeNumber )( IITFileOrCDTrack * This, /* [in] */ long episodeNumber); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Size64High )( IITFileOrCDTrack * This, /* [retval][out] */ long *sizeHigh); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Size64Low )( IITFileOrCDTrack * This, /* [retval][out] */ long *sizeLow); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Unplayed )( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *isUnplayed); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Unplayed )( IITFileOrCDTrack * This, /* [in] */ VARIANT_BOOL shouldBeUnplayed); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SortAlbum )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *album); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SortAlbum )( IITFileOrCDTrack * This, /* [in] */ BSTR album); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SortAlbumArtist )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *albumArtist); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SortAlbumArtist )( IITFileOrCDTrack * This, /* [in] */ BSTR albumArtist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SortArtist )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *artist); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SortArtist )( IITFileOrCDTrack * This, /* [in] */ BSTR artist); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SortComposer )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *composer); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SortComposer )( IITFileOrCDTrack * This, /* [in] */ BSTR composer); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SortName )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *name); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SortName )( IITFileOrCDTrack * This, /* [in] */ BSTR name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SortShow )( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *showName); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_SortShow )( IITFileOrCDTrack * This, /* [in] */ BSTR showName); /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *Reveal )( IITFileOrCDTrack * This); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_AlbumRating )( IITFileOrCDTrack * This, /* [retval][out] */ long *rating); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_AlbumRating )( IITFileOrCDTrack * This, /* [in] */ long rating); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_AlbumRatingKind )( IITFileOrCDTrack * This, /* [retval][out] */ ITRatingKind *ratingKind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_RatingKind )( IITFileOrCDTrack * This, /* [retval][out] */ ITRatingKind *ratingKind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Playlists )( IITFileOrCDTrack * This, /* [retval][out] */ IITPlaylistCollection **iPlaylistCollection); END_INTERFACE } IITFileOrCDTrackVtbl; interface IITFileOrCDTrack { CONST_VTBL struct IITFileOrCDTrackVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITFileOrCDTrack_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITFileOrCDTrack_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITFileOrCDTrack_Release(This) \ (This)->lpVtbl -> Release(This) #define IITFileOrCDTrack_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITFileOrCDTrack_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITFileOrCDTrack_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITFileOrCDTrack_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITFileOrCDTrack_GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) \ (This)->lpVtbl -> GetITObjectIDs(This,sourceID,playlistID,trackID,databaseID) #define IITFileOrCDTrack_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITFileOrCDTrack_put_Name(This,name) \ (This)->lpVtbl -> put_Name(This,name) #define IITFileOrCDTrack_get_Index(This,index) \ (This)->lpVtbl -> get_Index(This,index) #define IITFileOrCDTrack_get_SourceID(This,sourceID) \ (This)->lpVtbl -> get_SourceID(This,sourceID) #define IITFileOrCDTrack_get_PlaylistID(This,playlistID) \ (This)->lpVtbl -> get_PlaylistID(This,playlistID) #define IITFileOrCDTrack_get_TrackID(This,trackID) \ (This)->lpVtbl -> get_TrackID(This,trackID) #define IITFileOrCDTrack_get_TrackDatabaseID(This,databaseID) \ (This)->lpVtbl -> get_TrackDatabaseID(This,databaseID) #define IITFileOrCDTrack_Delete(This) \ (This)->lpVtbl -> Delete(This) #define IITFileOrCDTrack_Play(This) \ (This)->lpVtbl -> Play(This) #define IITFileOrCDTrack_AddArtworkFromFile(This,filePath,iArtwork) \ (This)->lpVtbl -> AddArtworkFromFile(This,filePath,iArtwork) #define IITFileOrCDTrack_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITFileOrCDTrack_get_Playlist(This,iPlaylist) \ (This)->lpVtbl -> get_Playlist(This,iPlaylist) #define IITFileOrCDTrack_get_Album(This,album) \ (This)->lpVtbl -> get_Album(This,album) #define IITFileOrCDTrack_put_Album(This,album) \ (This)->lpVtbl -> put_Album(This,album) #define IITFileOrCDTrack_get_Artist(This,artist) \ (This)->lpVtbl -> get_Artist(This,artist) #define IITFileOrCDTrack_put_Artist(This,artist) \ (This)->lpVtbl -> put_Artist(This,artist) #define IITFileOrCDTrack_get_BitRate(This,bitrate) \ (This)->lpVtbl -> get_BitRate(This,bitrate) #define IITFileOrCDTrack_get_BPM(This,beatsPerMinute) \ (This)->lpVtbl -> get_BPM(This,beatsPerMinute) #define IITFileOrCDTrack_put_BPM(This,beatsPerMinute) \ (This)->lpVtbl -> put_BPM(This,beatsPerMinute) #define IITFileOrCDTrack_get_Comment(This,comment) \ (This)->lpVtbl -> get_Comment(This,comment) #define IITFileOrCDTrack_put_Comment(This,comment) \ (This)->lpVtbl -> put_Comment(This,comment) #define IITFileOrCDTrack_get_Compilation(This,isCompilation) \ (This)->lpVtbl -> get_Compilation(This,isCompilation) #define IITFileOrCDTrack_put_Compilation(This,shouldBeCompilation) \ (This)->lpVtbl -> put_Compilation(This,shouldBeCompilation) #define IITFileOrCDTrack_get_Composer(This,composer) \ (This)->lpVtbl -> get_Composer(This,composer) #define IITFileOrCDTrack_put_Composer(This,composer) \ (This)->lpVtbl -> put_Composer(This,composer) #define IITFileOrCDTrack_get_DateAdded(This,dateAdded) \ (This)->lpVtbl -> get_DateAdded(This,dateAdded) #define IITFileOrCDTrack_get_DiscCount(This,discCount) \ (This)->lpVtbl -> get_DiscCount(This,discCount) #define IITFileOrCDTrack_put_DiscCount(This,discCount) \ (This)->lpVtbl -> put_DiscCount(This,discCount) #define IITFileOrCDTrack_get_DiscNumber(This,discNumber) \ (This)->lpVtbl -> get_DiscNumber(This,discNumber) #define IITFileOrCDTrack_put_DiscNumber(This,discNumber) \ (This)->lpVtbl -> put_DiscNumber(This,discNumber) #define IITFileOrCDTrack_get_Duration(This,duration) \ (This)->lpVtbl -> get_Duration(This,duration) #define IITFileOrCDTrack_get_Enabled(This,isEnabled) \ (This)->lpVtbl -> get_Enabled(This,isEnabled) #define IITFileOrCDTrack_put_Enabled(This,shouldBeEnabled) \ (This)->lpVtbl -> put_Enabled(This,shouldBeEnabled) #define IITFileOrCDTrack_get_EQ(This,eq) \ (This)->lpVtbl -> get_EQ(This,eq) #define IITFileOrCDTrack_put_EQ(This,eq) \ (This)->lpVtbl -> put_EQ(This,eq) #define IITFileOrCDTrack_put_Finish(This,finish) \ (This)->lpVtbl -> put_Finish(This,finish) #define IITFileOrCDTrack_get_Finish(This,finish) \ (This)->lpVtbl -> get_Finish(This,finish) #define IITFileOrCDTrack_get_Genre(This,genre) \ (This)->lpVtbl -> get_Genre(This,genre) #define IITFileOrCDTrack_put_Genre(This,genre) \ (This)->lpVtbl -> put_Genre(This,genre) #define IITFileOrCDTrack_get_Grouping(This,grouping) \ (This)->lpVtbl -> get_Grouping(This,grouping) #define IITFileOrCDTrack_put_Grouping(This,grouping) \ (This)->lpVtbl -> put_Grouping(This,grouping) #define IITFileOrCDTrack_get_KindAsString(This,kind) \ (This)->lpVtbl -> get_KindAsString(This,kind) #define IITFileOrCDTrack_get_ModificationDate(This,dateModified) \ (This)->lpVtbl -> get_ModificationDate(This,dateModified) #define IITFileOrCDTrack_get_PlayedCount(This,playedCount) \ (This)->lpVtbl -> get_PlayedCount(This,playedCount) #define IITFileOrCDTrack_put_PlayedCount(This,playedCount) \ (This)->lpVtbl -> put_PlayedCount(This,playedCount) #define IITFileOrCDTrack_get_PlayedDate(This,playedDate) \ (This)->lpVtbl -> get_PlayedDate(This,playedDate) #define IITFileOrCDTrack_put_PlayedDate(This,playedDate) \ (This)->lpVtbl -> put_PlayedDate(This,playedDate) #define IITFileOrCDTrack_get_PlayOrderIndex(This,index) \ (This)->lpVtbl -> get_PlayOrderIndex(This,index) #define IITFileOrCDTrack_get_Rating(This,rating) \ (This)->lpVtbl -> get_Rating(This,rating) #define IITFileOrCDTrack_put_Rating(This,rating) \ (This)->lpVtbl -> put_Rating(This,rating) #define IITFileOrCDTrack_get_SampleRate(This,sampleRate) \ (This)->lpVtbl -> get_SampleRate(This,sampleRate) #define IITFileOrCDTrack_get_Size(This,size) \ (This)->lpVtbl -> get_Size(This,size) #define IITFileOrCDTrack_get_Start(This,start) \ (This)->lpVtbl -> get_Start(This,start) #define IITFileOrCDTrack_put_Start(This,start) \ (This)->lpVtbl -> put_Start(This,start) #define IITFileOrCDTrack_get_Time(This,time) \ (This)->lpVtbl -> get_Time(This,time) #define IITFileOrCDTrack_get_TrackCount(This,trackCount) \ (This)->lpVtbl -> get_TrackCount(This,trackCount) #define IITFileOrCDTrack_put_TrackCount(This,trackCount) \ (This)->lpVtbl -> put_TrackCount(This,trackCount) #define IITFileOrCDTrack_get_TrackNumber(This,trackNumber) \ (This)->lpVtbl -> get_TrackNumber(This,trackNumber) #define IITFileOrCDTrack_put_TrackNumber(This,trackNumber) \ (This)->lpVtbl -> put_TrackNumber(This,trackNumber) #define IITFileOrCDTrack_get_VolumeAdjustment(This,volumeAdjustment) \ (This)->lpVtbl -> get_VolumeAdjustment(This,volumeAdjustment) #define IITFileOrCDTrack_put_VolumeAdjustment(This,volumeAdjustment) \ (This)->lpVtbl -> put_VolumeAdjustment(This,volumeAdjustment) #define IITFileOrCDTrack_get_Year(This,year) \ (This)->lpVtbl -> get_Year(This,year) #define IITFileOrCDTrack_put_Year(This,year) \ (This)->lpVtbl -> put_Year(This,year) #define IITFileOrCDTrack_get_Artwork(This,iArtworkCollection) \ (This)->lpVtbl -> get_Artwork(This,iArtworkCollection) #define IITFileOrCDTrack_get_Location(This,location) \ (This)->lpVtbl -> get_Location(This,location) #define IITFileOrCDTrack_UpdateInfoFromFile(This) \ (This)->lpVtbl -> UpdateInfoFromFile(This) #define IITFileOrCDTrack_get_Podcast(This,isPodcast) \ (This)->lpVtbl -> get_Podcast(This,isPodcast) #define IITFileOrCDTrack_UpdatePodcastFeed(This) \ (This)->lpVtbl -> UpdatePodcastFeed(This) #define IITFileOrCDTrack_get_RememberBookmark(This,rememberBookmark) \ (This)->lpVtbl -> get_RememberBookmark(This,rememberBookmark) #define IITFileOrCDTrack_put_RememberBookmark(This,shouldRememberBookmark) \ (This)->lpVtbl -> put_RememberBookmark(This,shouldRememberBookmark) #define IITFileOrCDTrack_get_ExcludeFromShuffle(This,excludeFromShuffle) \ (This)->lpVtbl -> get_ExcludeFromShuffle(This,excludeFromShuffle) #define IITFileOrCDTrack_put_ExcludeFromShuffle(This,shouldExcludeFromShuffle) \ (This)->lpVtbl -> put_ExcludeFromShuffle(This,shouldExcludeFromShuffle) #define IITFileOrCDTrack_get_Lyrics(This,lyrics) \ (This)->lpVtbl -> get_Lyrics(This,lyrics) #define IITFileOrCDTrack_put_Lyrics(This,lyrics) \ (This)->lpVtbl -> put_Lyrics(This,lyrics) #define IITFileOrCDTrack_get_Category(This,category) \ (This)->lpVtbl -> get_Category(This,category) #define IITFileOrCDTrack_put_Category(This,category) \ (This)->lpVtbl -> put_Category(This,category) #define IITFileOrCDTrack_get_Description(This,description) \ (This)->lpVtbl -> get_Description(This,description) #define IITFileOrCDTrack_put_Description(This,description) \ (This)->lpVtbl -> put_Description(This,description) #define IITFileOrCDTrack_get_LongDescription(This,longDescription) \ (This)->lpVtbl -> get_LongDescription(This,longDescription) #define IITFileOrCDTrack_put_LongDescription(This,longDescription) \ (This)->lpVtbl -> put_LongDescription(This,longDescription) #define IITFileOrCDTrack_get_BookmarkTime(This,bookmarkTime) \ (This)->lpVtbl -> get_BookmarkTime(This,bookmarkTime) #define IITFileOrCDTrack_put_BookmarkTime(This,bookmarkTime) \ (This)->lpVtbl -> put_BookmarkTime(This,bookmarkTime) #define IITFileOrCDTrack_get_VideoKind(This,videoKind) \ (This)->lpVtbl -> get_VideoKind(This,videoKind) #define IITFileOrCDTrack_put_VideoKind(This,videoKind) \ (This)->lpVtbl -> put_VideoKind(This,videoKind) #define IITFileOrCDTrack_get_SkippedCount(This,skippedCount) \ (This)->lpVtbl -> get_SkippedCount(This,skippedCount) #define IITFileOrCDTrack_put_SkippedCount(This,skippedCount) \ (This)->lpVtbl -> put_SkippedCount(This,skippedCount) #define IITFileOrCDTrack_get_SkippedDate(This,skippedDate) \ (This)->lpVtbl -> get_SkippedDate(This,skippedDate) #define IITFileOrCDTrack_put_SkippedDate(This,skippedDate) \ (This)->lpVtbl -> put_SkippedDate(This,skippedDate) #define IITFileOrCDTrack_get_PartOfGaplessAlbum(This,partOfGaplessAlbum) \ (This)->lpVtbl -> get_PartOfGaplessAlbum(This,partOfGaplessAlbum) #define IITFileOrCDTrack_put_PartOfGaplessAlbum(This,shouldBePartOfGaplessAlbum) \ (This)->lpVtbl -> put_PartOfGaplessAlbum(This,shouldBePartOfGaplessAlbum) #define IITFileOrCDTrack_get_AlbumArtist(This,albumArtist) \ (This)->lpVtbl -> get_AlbumArtist(This,albumArtist) #define IITFileOrCDTrack_put_AlbumArtist(This,albumArtist) \ (This)->lpVtbl -> put_AlbumArtist(This,albumArtist) #define IITFileOrCDTrack_get_Show(This,showName) \ (This)->lpVtbl -> get_Show(This,showName) #define IITFileOrCDTrack_put_Show(This,showName) \ (This)->lpVtbl -> put_Show(This,showName) #define IITFileOrCDTrack_get_SeasonNumber(This,seasonNumber) \ (This)->lpVtbl -> get_SeasonNumber(This,seasonNumber) #define IITFileOrCDTrack_put_SeasonNumber(This,seasonNumber) \ (This)->lpVtbl -> put_SeasonNumber(This,seasonNumber) #define IITFileOrCDTrack_get_EpisodeID(This,episodeID) \ (This)->lpVtbl -> get_EpisodeID(This,episodeID) #define IITFileOrCDTrack_put_EpisodeID(This,episodeID) \ (This)->lpVtbl -> put_EpisodeID(This,episodeID) #define IITFileOrCDTrack_get_EpisodeNumber(This,episodeNumber) \ (This)->lpVtbl -> get_EpisodeNumber(This,episodeNumber) #define IITFileOrCDTrack_put_EpisodeNumber(This,episodeNumber) \ (This)->lpVtbl -> put_EpisodeNumber(This,episodeNumber) #define IITFileOrCDTrack_get_Size64High(This,sizeHigh) \ (This)->lpVtbl -> get_Size64High(This,sizeHigh) #define IITFileOrCDTrack_get_Size64Low(This,sizeLow) \ (This)->lpVtbl -> get_Size64Low(This,sizeLow) #define IITFileOrCDTrack_get_Unplayed(This,isUnplayed) \ (This)->lpVtbl -> get_Unplayed(This,isUnplayed) #define IITFileOrCDTrack_put_Unplayed(This,shouldBeUnplayed) \ (This)->lpVtbl -> put_Unplayed(This,shouldBeUnplayed) #define IITFileOrCDTrack_get_SortAlbum(This,album) \ (This)->lpVtbl -> get_SortAlbum(This,album) #define IITFileOrCDTrack_put_SortAlbum(This,album) \ (This)->lpVtbl -> put_SortAlbum(This,album) #define IITFileOrCDTrack_get_SortAlbumArtist(This,albumArtist) \ (This)->lpVtbl -> get_SortAlbumArtist(This,albumArtist) #define IITFileOrCDTrack_put_SortAlbumArtist(This,albumArtist) \ (This)->lpVtbl -> put_SortAlbumArtist(This,albumArtist) #define IITFileOrCDTrack_get_SortArtist(This,artist) \ (This)->lpVtbl -> get_SortArtist(This,artist) #define IITFileOrCDTrack_put_SortArtist(This,artist) \ (This)->lpVtbl -> put_SortArtist(This,artist) #define IITFileOrCDTrack_get_SortComposer(This,composer) \ (This)->lpVtbl -> get_SortComposer(This,composer) #define IITFileOrCDTrack_put_SortComposer(This,composer) \ (This)->lpVtbl -> put_SortComposer(This,composer) #define IITFileOrCDTrack_get_SortName(This,name) \ (This)->lpVtbl -> get_SortName(This,name) #define IITFileOrCDTrack_put_SortName(This,name) \ (This)->lpVtbl -> put_SortName(This,name) #define IITFileOrCDTrack_get_SortShow(This,showName) \ (This)->lpVtbl -> get_SortShow(This,showName) #define IITFileOrCDTrack_put_SortShow(This,showName) \ (This)->lpVtbl -> put_SortShow(This,showName) #define IITFileOrCDTrack_Reveal(This) \ (This)->lpVtbl -> Reveal(This) #define IITFileOrCDTrack_get_AlbumRating(This,rating) \ (This)->lpVtbl -> get_AlbumRating(This,rating) #define IITFileOrCDTrack_put_AlbumRating(This,rating) \ (This)->lpVtbl -> put_AlbumRating(This,rating) #define IITFileOrCDTrack_get_AlbumRatingKind(This,ratingKind) \ (This)->lpVtbl -> get_AlbumRatingKind(This,ratingKind) #define IITFileOrCDTrack_get_RatingKind(This,ratingKind) \ (This)->lpVtbl -> get_RatingKind(This,ratingKind) #define IITFileOrCDTrack_get_Playlists(This,iPlaylistCollection) \ (This)->lpVtbl -> get_Playlists(This,iPlaylistCollection) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_Location_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *location); void __RPC_STUB IITFileOrCDTrack_get_Location_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_UpdateInfoFromFile_Proxy( IITFileOrCDTrack * This); void __RPC_STUB IITFileOrCDTrack_UpdateInfoFromFile_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_Podcast_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *isPodcast); void __RPC_STUB IITFileOrCDTrack_get_Podcast_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_UpdatePodcastFeed_Proxy( IITFileOrCDTrack * This); void __RPC_STUB IITFileOrCDTrack_UpdatePodcastFeed_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_RememberBookmark_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *rememberBookmark); void __RPC_STUB IITFileOrCDTrack_get_RememberBookmark_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_RememberBookmark_Proxy( IITFileOrCDTrack * This, /* [in] */ VARIANT_BOOL shouldRememberBookmark); void __RPC_STUB IITFileOrCDTrack_put_RememberBookmark_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_ExcludeFromShuffle_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *excludeFromShuffle); void __RPC_STUB IITFileOrCDTrack_get_ExcludeFromShuffle_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_ExcludeFromShuffle_Proxy( IITFileOrCDTrack * This, /* [in] */ VARIANT_BOOL shouldExcludeFromShuffle); void __RPC_STUB IITFileOrCDTrack_put_ExcludeFromShuffle_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_Lyrics_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *lyrics); void __RPC_STUB IITFileOrCDTrack_get_Lyrics_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_Lyrics_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR lyrics); void __RPC_STUB IITFileOrCDTrack_put_Lyrics_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_Category_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *category); void __RPC_STUB IITFileOrCDTrack_get_Category_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_Category_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR category); void __RPC_STUB IITFileOrCDTrack_put_Category_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_Description_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *description); void __RPC_STUB IITFileOrCDTrack_get_Description_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_Description_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR description); void __RPC_STUB IITFileOrCDTrack_put_Description_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_LongDescription_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *longDescription); void __RPC_STUB IITFileOrCDTrack_get_LongDescription_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_LongDescription_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR longDescription); void __RPC_STUB IITFileOrCDTrack_put_LongDescription_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_BookmarkTime_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ long *bookmarkTime); void __RPC_STUB IITFileOrCDTrack_get_BookmarkTime_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_BookmarkTime_Proxy( IITFileOrCDTrack * This, /* [in] */ long bookmarkTime); void __RPC_STUB IITFileOrCDTrack_put_BookmarkTime_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_VideoKind_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ ITVideoKind *videoKind); void __RPC_STUB IITFileOrCDTrack_get_VideoKind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_VideoKind_Proxy( IITFileOrCDTrack * This, /* [in] */ ITVideoKind videoKind); void __RPC_STUB IITFileOrCDTrack_put_VideoKind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_SkippedCount_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ long *skippedCount); void __RPC_STUB IITFileOrCDTrack_get_SkippedCount_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_SkippedCount_Proxy( IITFileOrCDTrack * This, /* [in] */ long skippedCount); void __RPC_STUB IITFileOrCDTrack_put_SkippedCount_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_SkippedDate_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ DATE *skippedDate); void __RPC_STUB IITFileOrCDTrack_get_SkippedDate_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_SkippedDate_Proxy( IITFileOrCDTrack * This, /* [in] */ DATE skippedDate); void __RPC_STUB IITFileOrCDTrack_put_SkippedDate_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_PartOfGaplessAlbum_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *partOfGaplessAlbum); void __RPC_STUB IITFileOrCDTrack_get_PartOfGaplessAlbum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_PartOfGaplessAlbum_Proxy( IITFileOrCDTrack * This, /* [in] */ VARIANT_BOOL shouldBePartOfGaplessAlbum); void __RPC_STUB IITFileOrCDTrack_put_PartOfGaplessAlbum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_AlbumArtist_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *albumArtist); void __RPC_STUB IITFileOrCDTrack_get_AlbumArtist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_AlbumArtist_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR albumArtist); void __RPC_STUB IITFileOrCDTrack_put_AlbumArtist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_Show_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *showName); void __RPC_STUB IITFileOrCDTrack_get_Show_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_Show_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR showName); void __RPC_STUB IITFileOrCDTrack_put_Show_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_SeasonNumber_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ long *seasonNumber); void __RPC_STUB IITFileOrCDTrack_get_SeasonNumber_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_SeasonNumber_Proxy( IITFileOrCDTrack * This, /* [in] */ long seasonNumber); void __RPC_STUB IITFileOrCDTrack_put_SeasonNumber_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_EpisodeID_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *episodeID); void __RPC_STUB IITFileOrCDTrack_get_EpisodeID_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_EpisodeID_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR episodeID); void __RPC_STUB IITFileOrCDTrack_put_EpisodeID_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_EpisodeNumber_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ long *episodeNumber); void __RPC_STUB IITFileOrCDTrack_get_EpisodeNumber_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_EpisodeNumber_Proxy( IITFileOrCDTrack * This, /* [in] */ long episodeNumber); void __RPC_STUB IITFileOrCDTrack_put_EpisodeNumber_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_Size64High_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ long *sizeHigh); void __RPC_STUB IITFileOrCDTrack_get_Size64High_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_Size64Low_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ long *sizeLow); void __RPC_STUB IITFileOrCDTrack_get_Size64Low_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_Unplayed_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ VARIANT_BOOL *isUnplayed); void __RPC_STUB IITFileOrCDTrack_get_Unplayed_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_Unplayed_Proxy( IITFileOrCDTrack * This, /* [in] */ VARIANT_BOOL shouldBeUnplayed); void __RPC_STUB IITFileOrCDTrack_put_Unplayed_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_SortAlbum_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *album); void __RPC_STUB IITFileOrCDTrack_get_SortAlbum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_SortAlbum_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR album); void __RPC_STUB IITFileOrCDTrack_put_SortAlbum_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_SortAlbumArtist_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *albumArtist); void __RPC_STUB IITFileOrCDTrack_get_SortAlbumArtist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_SortAlbumArtist_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR albumArtist); void __RPC_STUB IITFileOrCDTrack_put_SortAlbumArtist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_SortArtist_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *artist); void __RPC_STUB IITFileOrCDTrack_get_SortArtist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_SortArtist_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR artist); void __RPC_STUB IITFileOrCDTrack_put_SortArtist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_SortComposer_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *composer); void __RPC_STUB IITFileOrCDTrack_get_SortComposer_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_SortComposer_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR composer); void __RPC_STUB IITFileOrCDTrack_put_SortComposer_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_SortName_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *name); void __RPC_STUB IITFileOrCDTrack_get_SortName_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_SortName_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR name); void __RPC_STUB IITFileOrCDTrack_put_SortName_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_SortShow_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ BSTR *showName); void __RPC_STUB IITFileOrCDTrack_get_SortShow_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_SortShow_Proxy( IITFileOrCDTrack * This, /* [in] */ BSTR showName); void __RPC_STUB IITFileOrCDTrack_put_SortShow_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_Reveal_Proxy( IITFileOrCDTrack * This); void __RPC_STUB IITFileOrCDTrack_Reveal_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_AlbumRating_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ long *rating); void __RPC_STUB IITFileOrCDTrack_get_AlbumRating_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_put_AlbumRating_Proxy( IITFileOrCDTrack * This, /* [in] */ long rating); void __RPC_STUB IITFileOrCDTrack_put_AlbumRating_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_AlbumRatingKind_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ ITRatingKind *ratingKind); void __RPC_STUB IITFileOrCDTrack_get_AlbumRatingKind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_RatingKind_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ ITRatingKind *ratingKind); void __RPC_STUB IITFileOrCDTrack_get_RatingKind_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITFileOrCDTrack_get_Playlists_Proxy( IITFileOrCDTrack * This, /* [retval][out] */ IITPlaylistCollection **iPlaylistCollection); void __RPC_STUB IITFileOrCDTrack_get_Playlists_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITFileOrCDTrack_INTERFACE_DEFINED__ */ #ifndef __IITPlaylistWindow_INTERFACE_DEFINED__ #define __IITPlaylistWindow_INTERFACE_DEFINED__ /* interface IITPlaylistWindow */ /* [hidden][unique][helpstring][dual][uuid][object] */ EXTERN_C const IID IID_IITPlaylistWindow; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("349CBB45-2E5A-4822-8E4A-A75555A186F7") IITPlaylistWindow : public IITWindow { public: virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_SelectedTracks( /* [retval][out] */ IITTrackCollection **iTrackCollection) = 0; virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Playlist( /* [retval][out] */ IITPlaylist **iPlaylist) = 0; }; #else /* C style interface */ typedef struct IITPlaylistWindowVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IITPlaylistWindow * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IITPlaylistWindow * This); ULONG ( STDMETHODCALLTYPE *Release )( IITPlaylistWindow * This); HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( IITPlaylistWindow * This, /* [out] */ UINT *pctinfo); HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( IITPlaylistWindow * This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo); HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( IITPlaylistWindow * This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( IITPlaylistWindow * This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Name )( IITPlaylistWindow * This, /* [retval][out] */ BSTR *name); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Kind )( IITPlaylistWindow * This, /* [retval][out] */ ITWindowKind *kind); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Visible )( IITPlaylistWindow * This, /* [retval][out] */ VARIANT_BOOL *isVisible); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Visible )( IITPlaylistWindow * This, /* [in] */ VARIANT_BOOL shouldBeVisible); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Resizable )( IITPlaylistWindow * This, /* [retval][out] */ VARIANT_BOOL *isResizable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Minimized )( IITPlaylistWindow * This, /* [retval][out] */ VARIANT_BOOL *isMinimized); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Minimized )( IITPlaylistWindow * This, /* [in] */ VARIANT_BOOL shouldBeMinimized); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Maximizable )( IITPlaylistWindow * This, /* [retval][out] */ VARIANT_BOOL *isMaximizable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Maximized )( IITPlaylistWindow * This, /* [retval][out] */ VARIANT_BOOL *isMaximized); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Maximized )( IITPlaylistWindow * This, /* [in] */ VARIANT_BOOL shouldBeMaximized); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Zoomable )( IITPlaylistWindow * This, /* [retval][out] */ VARIANT_BOOL *isZoomable); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Zoomed )( IITPlaylistWindow * This, /* [retval][out] */ VARIANT_BOOL *isZoomed); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Zoomed )( IITPlaylistWindow * This, /* [in] */ VARIANT_BOOL shouldBeZoomed); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Top )( IITPlaylistWindow * This, /* [retval][out] */ long *top); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Top )( IITPlaylistWindow * This, /* [in] */ long top); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Left )( IITPlaylistWindow * This, /* [retval][out] */ long *left); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Left )( IITPlaylistWindow * This, /* [in] */ long left); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Bottom )( IITPlaylistWindow * This, /* [retval][out] */ long *bottom); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Bottom )( IITPlaylistWindow * This, /* [in] */ long bottom); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Right )( IITPlaylistWindow * This, /* [retval][out] */ long *right); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Right )( IITPlaylistWindow * This, /* [in] */ long right); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Width )( IITPlaylistWindow * This, /* [retval][out] */ long *width); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Width )( IITPlaylistWindow * This, /* [in] */ long width); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Height )( IITPlaylistWindow * This, /* [retval][out] */ long *height); /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE *put_Height )( IITPlaylistWindow * This, /* [in] */ long height); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_SelectedTracks )( IITPlaylistWindow * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Playlist )( IITPlaylistWindow * This, /* [retval][out] */ IITPlaylist **iPlaylist); END_INTERFACE } IITPlaylistWindowVtbl; interface IITPlaylistWindow { CONST_VTBL struct IITPlaylistWindowVtbl *lpVtbl; }; #ifdef COBJMACROS #define IITPlaylistWindow_QueryInterface(This,riid,ppvObject) \ (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) #define IITPlaylistWindow_AddRef(This) \ (This)->lpVtbl -> AddRef(This) #define IITPlaylistWindow_Release(This) \ (This)->lpVtbl -> Release(This) #define IITPlaylistWindow_GetTypeInfoCount(This,pctinfo) \ (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) #define IITPlaylistWindow_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) #define IITPlaylistWindow_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) #define IITPlaylistWindow_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) #define IITPlaylistWindow_get_Name(This,name) \ (This)->lpVtbl -> get_Name(This,name) #define IITPlaylistWindow_get_Kind(This,kind) \ (This)->lpVtbl -> get_Kind(This,kind) #define IITPlaylistWindow_get_Visible(This,isVisible) \ (This)->lpVtbl -> get_Visible(This,isVisible) #define IITPlaylistWindow_put_Visible(This,shouldBeVisible) \ (This)->lpVtbl -> put_Visible(This,shouldBeVisible) #define IITPlaylistWindow_get_Resizable(This,isResizable) \ (This)->lpVtbl -> get_Resizable(This,isResizable) #define IITPlaylistWindow_get_Minimized(This,isMinimized) \ (This)->lpVtbl -> get_Minimized(This,isMinimized) #define IITPlaylistWindow_put_Minimized(This,shouldBeMinimized) \ (This)->lpVtbl -> put_Minimized(This,shouldBeMinimized) #define IITPlaylistWindow_get_Maximizable(This,isMaximizable) \ (This)->lpVtbl -> get_Maximizable(This,isMaximizable) #define IITPlaylistWindow_get_Maximized(This,isMaximized) \ (This)->lpVtbl -> get_Maximized(This,isMaximized) #define IITPlaylistWindow_put_Maximized(This,shouldBeMaximized) \ (This)->lpVtbl -> put_Maximized(This,shouldBeMaximized) #define IITPlaylistWindow_get_Zoomable(This,isZoomable) \ (This)->lpVtbl -> get_Zoomable(This,isZoomable) #define IITPlaylistWindow_get_Zoomed(This,isZoomed) \ (This)->lpVtbl -> get_Zoomed(This,isZoomed) #define IITPlaylistWindow_put_Zoomed(This,shouldBeZoomed) \ (This)->lpVtbl -> put_Zoomed(This,shouldBeZoomed) #define IITPlaylistWindow_get_Top(This,top) \ (This)->lpVtbl -> get_Top(This,top) #define IITPlaylistWindow_put_Top(This,top) \ (This)->lpVtbl -> put_Top(This,top) #define IITPlaylistWindow_get_Left(This,left) \ (This)->lpVtbl -> get_Left(This,left) #define IITPlaylistWindow_put_Left(This,left) \ (This)->lpVtbl -> put_Left(This,left) #define IITPlaylistWindow_get_Bottom(This,bottom) \ (This)->lpVtbl -> get_Bottom(This,bottom) #define IITPlaylistWindow_put_Bottom(This,bottom) \ (This)->lpVtbl -> put_Bottom(This,bottom) #define IITPlaylistWindow_get_Right(This,right) \ (This)->lpVtbl -> get_Right(This,right) #define IITPlaylistWindow_put_Right(This,right) \ (This)->lpVtbl -> put_Right(This,right) #define IITPlaylistWindow_get_Width(This,width) \ (This)->lpVtbl -> get_Width(This,width) #define IITPlaylistWindow_put_Width(This,width) \ (This)->lpVtbl -> put_Width(This,width) #define IITPlaylistWindow_get_Height(This,height) \ (This)->lpVtbl -> get_Height(This,height) #define IITPlaylistWindow_put_Height(This,height) \ (This)->lpVtbl -> put_Height(This,height) #define IITPlaylistWindow_get_SelectedTracks(This,iTrackCollection) \ (This)->lpVtbl -> get_SelectedTracks(This,iTrackCollection) #define IITPlaylistWindow_get_Playlist(This,iPlaylist) \ (This)->lpVtbl -> get_Playlist(This,iPlaylist) #endif /* COBJMACROS */ #endif /* C style interface */ /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylistWindow_get_SelectedTracks_Proxy( IITPlaylistWindow * This, /* [retval][out] */ IITTrackCollection **iTrackCollection); void __RPC_STUB IITPlaylistWindow_get_SelectedTracks_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IITPlaylistWindow_get_Playlist_Proxy( IITPlaylistWindow * This, /* [retval][out] */ IITPlaylist **iPlaylist); void __RPC_STUB IITPlaylistWindow_get_Playlist_Stub( IRpcStubBuffer *This, IRpcChannelBuffer *_pRpcChannelBuffer, PRPC_MESSAGE _pRpcMessage, DWORD *_pdwStubPhase); #endif /* __IITPlaylistWindow_INTERFACE_DEFINED__ */ #endif /* __iTunesLib_LIBRARY_DEFINED__ */ /* Additional Prototypes for ALL interfaces */ /* end of Additional Prototypes */ #ifdef __cplusplus } #endif #endif ================================================ FILE: lib/3rdparty/iTunesCOMAPI/iTunesCOMInterface_i.c ================================================ /* this ALWAYS GENERATED file contains the IIDs and CLSIDs */ /* link this file in with the server and any clients */ /* File created by MIDL compiler version 6.00.0366 */ /* at Wed Jun 25 17:02:20 2008 */ /* Compiler settings for iTunesCOMInterface.idl: Oicf, W1, Zp8, env=Win32 (32b run) protocol : dce , ms_ext, c_ext, robust error checks: allocation ref bounds_check enum stub_data VC __declspec() decoration level: __declspec(uuid()), __declspec(selectany), __declspec(novtable) DECLSPEC_UUID(), MIDL_INTERFACE() */ //@@MIDL_FILE_HEADING( ) #pragma warning( disable: 4049 ) /* more than 64k source lines */ #ifdef __cplusplus extern "C"{ #endif #include <rpc.h> #include <rpcndr.h> #ifdef _MIDL_USE_GUIDDEF_ #ifndef INITGUID #define INITGUID #include <guiddef.h> #undef INITGUID #else #include <guiddef.h> #endif #define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) #else // !_MIDL_USE_GUIDDEF_ #ifndef __IID_DEFINED__ #define __IID_DEFINED__ typedef struct _IID { unsigned long x; unsigned short s1; unsigned short s2; unsigned char c[8]; } IID; #endif // __IID_DEFINED__ #ifndef CLSID_DEFINED #define CLSID_DEFINED typedef IID CLSID; #endif // CLSID_DEFINED #define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} #endif !_MIDL_USE_GUIDDEF_ MIDL_DEFINE_GUID(IID, LIBID_iTunesLib,0x9E93C96F,0xCF0D,0x43f6,0x8B,0xA8,0xB8,0x07,0xA3,0x37,0x07,0x12); MIDL_DEFINE_GUID(IID, IID_IITObject,0x9FAB0E27,0x70D7,0x4e3a,0x99,0x65,0xB0,0xC8,0xB8,0x86,0x9B,0xB6); MIDL_DEFINE_GUID(IID, IID_IITSource,0xAEC1C4D3,0xAEF1,0x4255,0xB8,0x92,0x3E,0x3D,0x13,0xAD,0xFD,0xF9); MIDL_DEFINE_GUID(IID, IID_IITSourceCollection,0x2FF6CE20,0xFF87,0x4183,0xB0,0xB3,0xF3,0x23,0xD0,0x47,0xAF,0x41); MIDL_DEFINE_GUID(IID, IID_IITEncoder,0x1CF95A1C,0x55FE,0x4f45,0xA2,0xD3,0x85,0xAC,0x6C,0x50,0x4A,0x73); MIDL_DEFINE_GUID(IID, IID_IITEncoderCollection,0x8862BCA9,0x168D,0x4549,0xA9,0xD5,0xAD,0xB3,0x5E,0x55,0x3B,0xA6); MIDL_DEFINE_GUID(IID, IID_IITEQPreset,0x5BE75F4F,0x68FA,0x4212,0xAC,0xB7,0xBE,0x44,0xEA,0x56,0x97,0x59); MIDL_DEFINE_GUID(IID, IID_IITEQPresetCollection,0xAEF4D111,0x3331,0x48da,0xB0,0xC2,0xB4,0x68,0xD5,0xD6,0x1D,0x08); MIDL_DEFINE_GUID(IID, IID_IITPlaylist,0x3D5E072F,0x2A77,0x4b17,0x9E,0x73,0xE0,0x3B,0x77,0xCC,0xCC,0xA9); MIDL_DEFINE_GUID(IID, IID_IITOperationStatus,0x206479C9,0xFE32,0x4f9b,0xA1,0x8A,0x47,0x5A,0xC9,0x39,0xB4,0x79); MIDL_DEFINE_GUID(IID, IID_IITConvertOperationStatus,0x7063AAF6,0xABA0,0x493b,0xB4,0xFC,0x92,0x0A,0x9F,0x10,0x58,0x75); MIDL_DEFINE_GUID(IID, IID_IITLibraryPlaylist,0x53AE1704,0x491C,0x4289,0x94,0xA0,0x95,0x88,0x15,0x67,0x5A,0x3D); MIDL_DEFINE_GUID(IID, IID_IITUserPlaylist,0x0A504DED,0xA0B5,0x465a,0x8A,0x94,0x50,0xE2,0x0D,0x7D,0xF6,0x92); MIDL_DEFINE_GUID(IID, IID_IITTrack,0x4CB0915D,0x1E54,0x4727,0xBA,0xF3,0xCE,0x6C,0xC9,0xA2,0x25,0xA1); MIDL_DEFINE_GUID(IID, IID_IITTrackCollection,0x755D76F1,0x6B85,0x4ce4,0x8F,0x5F,0xF8,0x8D,0x97,0x43,0xDC,0xD8); MIDL_DEFINE_GUID(IID, IID_IITVisual,0x340F3315,0xED72,0x4c09,0x9A,0xCF,0x21,0xEB,0x4B,0xDF,0x99,0x31); MIDL_DEFINE_GUID(IID, IID_IITVisualCollection,0x88A4CCDD,0x114F,0x4043,0xB6,0x9B,0x84,0xD4,0xE6,0x27,0x49,0x57); MIDL_DEFINE_GUID(IID, IID_IITWindow,0x370D7BE0,0x3A89,0x4a42,0xB9,0x02,0xC7,0x5F,0xC1,0x38,0xBE,0x09); MIDL_DEFINE_GUID(IID, IID_IITBrowserWindow,0xC999F455,0xC4D5,0x4aa4,0x82,0x77,0xF9,0x97,0x53,0x69,0x99,0x74); MIDL_DEFINE_GUID(IID, IID_IITWindowCollection,0x3D8DE381,0x6C0E,0x481f,0xA8,0x65,0xE2,0x38,0x5F,0x59,0xFA,0x43); MIDL_DEFINE_GUID(IID, IID_IiTunes,0x9DD6680B,0x3EDC,0x40db,0xA7,0x71,0xE6,0xFE,0x48,0x32,0xE3,0x4A); MIDL_DEFINE_GUID(IID, DIID__IiTunesEvents,0x5846EB78,0x317E,0x4b6f,0xB0,0xC3,0x11,0xEE,0x8C,0x8F,0xEE,0xF2); MIDL_DEFINE_GUID(IID, DIID__IITConvertOperationStatusEvents,0x5C47A705,0x8E8A,0x45a1,0x9E,0xED,0x71,0xC9,0x93,0xF0,0xBF,0x60); MIDL_DEFINE_GUID(CLSID, CLSID_iTunesApp,0xDC0C2640,0x1415,0x4644,0x87,0x5C,0x6F,0x4D,0x76,0x98,0x39,0xBA); MIDL_DEFINE_GUID(CLSID, CLSID_iTunesConvertOperationStatus,0xD06596AD,0xC900,0x41b2,0xBC,0x68,0x1B,0x48,0x64,0x50,0xFC,0x56); MIDL_DEFINE_GUID(IID, IID_IITArtwork,0xD0A6C1F8,0xBF3D,0x4cd8,0xAC,0x47,0xFE,0x32,0xBD,0xD1,0x72,0x57); MIDL_DEFINE_GUID(IID, IID_IITArtworkCollection,0xBF2742D7,0x418C,0x4858,0x9A,0xF9,0x29,0x81,0xB0,0x62,0xD2,0x3E); MIDL_DEFINE_GUID(IID, IID_IITURLTrack,0x1116E3B5,0x29FD,0x4393,0xA7,0xBD,0x45,0x4E,0x5E,0x32,0x79,0x00); MIDL_DEFINE_GUID(IID, IID_IITAudioCDPlaylist,0xCF496DF3,0x0FED,0x4d7d,0x9B,0xD8,0x52,0x9B,0x6E,0x8A,0x08,0x2E); MIDL_DEFINE_GUID(IID, IID_IITPlaylistCollection,0xFF194254,0x909D,0x4437,0x9C,0x50,0x3A,0xAC,0x2A,0xE6,0x30,0x5C); MIDL_DEFINE_GUID(IID, IID_IITIPodSource,0xCF4D8ACE,0x1720,0x4fb9,0xB0,0xAE,0x98,0x77,0x24,0x9E,0x89,0xB0); MIDL_DEFINE_GUID(IID, IID_IITFileOrCDTrack,0x00D7FE99,0x7868,0x4cc7,0xAD,0x9E,0xAC,0xFD,0x70,0xD0,0x95,0x66); MIDL_DEFINE_GUID(IID, IID_IITPlaylistWindow,0x349CBB45,0x2E5A,0x4822,0x8E,0x4A,0xA7,0x55,0x55,0xA1,0x86,0xF7); #undef MIDL_DEFINE_GUID #ifdef __cplusplus } #endif ================================================ FILE: lib/3rdparty/mad.h ================================================ /* * libmad - MPEG audio decoder library * Copyright (C) 2000-2004 Underbit Technologies, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * If you would like to negotiate alternate licensing terms, you may do * so by contacting: Underbit Technologies, Inc. <info@underbit.com> */ # ifdef __cplusplus extern "C" { # endif # define FPM_INTEL # define SIZEOF_INT 4 # define SIZEOF_LONG 4 # define SIZEOF_LONG_LONG 8 /* Id: version.h,v 1.26 2004/01/23 09:41:33 rob Exp */ # ifndef LIBMAD_VERSION_H # define LIBMAD_VERSION_H # define MAD_VERSION_MAJOR 0 # define MAD_VERSION_MINOR 15 # define MAD_VERSION_PATCH 1 # define MAD_VERSION_EXTRA " (beta)" # define MAD_VERSION_STRINGIZE(str) #str # define MAD_VERSION_STRING(num) MAD_VERSION_STRINGIZE(num) # define MAD_VERSION MAD_VERSION_STRING(MAD_VERSION_MAJOR) "." \ MAD_VERSION_STRING(MAD_VERSION_MINOR) "." \ MAD_VERSION_STRING(MAD_VERSION_PATCH) \ MAD_VERSION_EXTRA # define MAD_PUBLISHYEAR "2000-2004" # define MAD_AUTHOR "Underbit Technologies, Inc." # define MAD_EMAIL "info@underbit.com" extern char const mad_version[]; extern char const mad_copyright[]; extern char const mad_author[]; extern char const mad_build[]; # endif /* Id: fixed.h,v 1.38 2004/02/17 02:02:03 rob Exp */ # ifndef LIBMAD_FIXED_H # define LIBMAD_FIXED_H # if SIZEOF_INT >= 4 typedef signed int mad_fixed_t; typedef signed int mad_fixed64hi_t; typedef unsigned int mad_fixed64lo_t; # else typedef signed long mad_fixed_t; typedef signed long mad_fixed64hi_t; typedef unsigned long mad_fixed64lo_t; # endif # if defined(_MSC_VER) # define mad_fixed64_t signed __int64 # elif 1 || defined(__GNUC__) # define mad_fixed64_t signed long long # endif # if defined(FPM_FLOAT) typedef double mad_sample_t; # else typedef mad_fixed_t mad_sample_t; # endif /* * Fixed-point format: 0xABBBBBBB * A == whole part (sign + 3 bits) * B == fractional part (28 bits) * * Values are signed two's complement, so the effective range is: * 0x80000000 to 0x7fffffff * -8.0 to +7.9999999962747097015380859375 * * The smallest representable value is: * 0x00000001 == 0.0000000037252902984619140625 (i.e. about 3.725e-9) * * 28 bits of fractional accuracy represent about * 8.6 digits of decimal accuracy. * * Fixed-point numbers can be added or subtracted as normal * integers, but multiplication requires shifting the 64-bit result * from 56 fractional bits back to 28 (and rounding.) * * Changing the definition of MAD_F_FRACBITS is only partially * supported, and must be done with care. */ # define MAD_F_FRACBITS 28 # if MAD_F_FRACBITS == 28 # define MAD_F(x) ((mad_fixed_t) (x##L)) # else # if MAD_F_FRACBITS < 28 # warning "MAD_F_FRACBITS < 28" # define MAD_F(x) ((mad_fixed_t) \ (((x##L) + \ (1L << (28 - MAD_F_FRACBITS - 1))) >> \ (28 - MAD_F_FRACBITS))) # elif MAD_F_FRACBITS > 28 # error "MAD_F_FRACBITS > 28 not currently supported" # define MAD_F(x) ((mad_fixed_t) \ ((x##L) << (MAD_F_FRACBITS - 28))) # endif # endif # define MAD_F_MIN ((mad_fixed_t) -0x80000000L) # define MAD_F_MAX ((mad_fixed_t) +0x7fffffffL) # define MAD_F_ONE MAD_F(0x10000000) # define mad_f_tofixed(x) ((mad_fixed_t) \ ((x) * (double) (1L << MAD_F_FRACBITS) + 0.5)) # define mad_f_todouble(x) ((double) \ ((x) / (double) (1L << MAD_F_FRACBITS))) # define mad_f_intpart(x) ((x) >> MAD_F_FRACBITS) # define mad_f_fracpart(x) ((x) & ((1L << MAD_F_FRACBITS) - 1)) /* (x should be positive) */ # define mad_f_fromint(x) ((x) << MAD_F_FRACBITS) # define mad_f_add(x, y) ((x) + (y)) # define mad_f_sub(x, y) ((x) - (y)) # if defined(FPM_FLOAT) # error "FPM_FLOAT not yet supported" # undef MAD_F # define MAD_F(x) mad_f_todouble(x) # define mad_f_mul(x, y) ((x) * (y)) # define mad_f_scale64 # undef ASO_ZEROCHECK # elif defined(FPM_64BIT) /* * This version should be the most accurate if 64-bit types are supported by * the compiler, although it may not be the most efficient. */ # if defined(OPT_ACCURACY) # define mad_f_mul(x, y) \ ((mad_fixed_t) \ ((((mad_fixed64_t) (x) * (y)) + \ (1L << (MAD_F_SCALEBITS - 1))) >> MAD_F_SCALEBITS)) # else # define mad_f_mul(x, y) \ ((mad_fixed_t) (((mad_fixed64_t) (x) * (y)) >> MAD_F_SCALEBITS)) # endif # define MAD_F_SCALEBITS MAD_F_FRACBITS /* --- Intel --------------------------------------------------------------- */ # elif defined(FPM_INTEL) # if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable: 4035) /* no return value */ static __forceinline mad_fixed_t mad_f_mul_inline(mad_fixed_t x, mad_fixed_t y) { enum { fracbits = MAD_F_FRACBITS }; __asm { mov eax, x imul y shrd eax, edx, fracbits } /* implicit return of eax */ } # pragma warning(pop) # define mad_f_mul mad_f_mul_inline # define mad_f_scale64 # else /* * This Intel version is fast and accurate; the disposition of the least * significant bit depends on OPT_ACCURACY via mad_f_scale64(). */ # define MAD_F_MLX(hi, lo, x, y) \ asm ("imull %3" \ : "=a" (lo), "=d" (hi) \ : "%a" (x), "rm" (y) \ : "cc") # if defined(OPT_ACCURACY) /* * This gives best accuracy but is not very fast. */ # define MAD_F_MLA(hi, lo, x, y) \ ({ mad_fixed64hi_t __hi; \ mad_fixed64lo_t __lo; \ MAD_F_MLX(__hi, __lo, (x), (y)); \ asm ("addl %2,%0\n\t" \ "adcl %3,%1" \ : "=rm" (lo), "=rm" (hi) \ : "r" (__lo), "r" (__hi), "0" (lo), "1" (hi) \ : "cc"); \ }) # endif /* OPT_ACCURACY */ # if defined(OPT_ACCURACY) /* * Surprisingly, this is faster than SHRD followed by ADC. */ # define mad_f_scale64(hi, lo) \ ({ mad_fixed64hi_t __hi_; \ mad_fixed64lo_t __lo_; \ mad_fixed_t __result; \ asm ("addl %4,%2\n\t" \ "adcl %5,%3" \ : "=rm" (__lo_), "=rm" (__hi_) \ : "0" (lo), "1" (hi), \ "ir" (1L << (MAD_F_SCALEBITS - 1)), "ir" (0) \ : "cc"); \ asm ("shrdl %3,%2,%1" \ : "=rm" (__result) \ : "0" (__lo_), "r" (__hi_), "I" (MAD_F_SCALEBITS) \ : "cc"); \ __result; \ }) # elif defined(OPT_INTEL) /* * Alternate Intel scaling that may or may not perform better. */ # define mad_f_scale64(hi, lo) \ ({ mad_fixed_t __result; \ asm ("shrl %3,%1\n\t" \ "shll %4,%2\n\t" \ "orl %2,%1" \ : "=rm" (__result) \ : "0" (lo), "r" (hi), \ "I" (MAD_F_SCALEBITS), "I" (32 - MAD_F_SCALEBITS) \ : "cc"); \ __result; \ }) # else # define mad_f_scale64(hi, lo) \ ({ mad_fixed_t __result; \ asm ("shrdl %3,%2,%1" \ : "=rm" (__result) \ : "0" (lo), "r" (hi), "I" (MAD_F_SCALEBITS) \ : "cc"); \ __result; \ }) # endif /* OPT_ACCURACY */ # define MAD_F_SCALEBITS MAD_F_FRACBITS # endif /* --- ARM ----------------------------------------------------------------- */ # elif defined(FPM_ARM) /* * This ARM V4 version is as accurate as FPM_64BIT but much faster. The * least significant bit is properly rounded at no CPU cycle cost! */ # if 1 /* * This is faster than the default implementation via MAD_F_MLX() and * mad_f_scale64(). */ # define mad_f_mul(x, y) \ ({ mad_fixed64hi_t __hi; \ mad_fixed64lo_t __lo; \ mad_fixed_t __result; \ asm ("smull %0, %1, %3, %4\n\t" \ "movs %0, %0, lsr %5\n\t" \ "adc %2, %0, %1, lsl %6" \ : "=&r" (__lo), "=&r" (__hi), "=r" (__result) \ : "%r" (x), "r" (y), \ "M" (MAD_F_SCALEBITS), "M" (32 - MAD_F_SCALEBITS) \ : "cc"); \ __result; \ }) # endif # define MAD_F_MLX(hi, lo, x, y) \ asm ("smull %0, %1, %2, %3" \ : "=&r" (lo), "=&r" (hi) \ : "%r" (x), "r" (y)) # define MAD_F_MLA(hi, lo, x, y) \ asm ("smlal %0, %1, %2, %3" \ : "+r" (lo), "+r" (hi) \ : "%r" (x), "r" (y)) # define MAD_F_MLN(hi, lo) \ asm ("rsbs %0, %2, #0\n\t" \ "rsc %1, %3, #0" \ : "=r" (lo), "=r" (hi) \ : "0" (lo), "1" (hi) \ : "cc") # define mad_f_scale64(hi, lo) \ ({ mad_fixed_t __result; \ asm ("movs %0, %1, lsr %3\n\t" \ "adc %0, %0, %2, lsl %4" \ : "=&r" (__result) \ : "r" (lo), "r" (hi), \ "M" (MAD_F_SCALEBITS), "M" (32 - MAD_F_SCALEBITS) \ : "cc"); \ __result; \ }) # define MAD_F_SCALEBITS MAD_F_FRACBITS /* --- MIPS ---------------------------------------------------------------- */ # elif defined(FPM_MIPS) /* * This MIPS version is fast and accurate; the disposition of the least * significant bit depends on OPT_ACCURACY via mad_f_scale64(). */ # define MAD_F_MLX(hi, lo, x, y) \ asm ("mult %2,%3" \ : "=l" (lo), "=h" (hi) \ : "%r" (x), "r" (y)) # if defined(HAVE_MADD_ASM) # define MAD_F_MLA(hi, lo, x, y) \ asm ("madd %2,%3" \ : "+l" (lo), "+h" (hi) \ : "%r" (x), "r" (y)) # elif defined(HAVE_MADD16_ASM) /* * This loses significant accuracy due to the 16-bit integer limit in the * multiply/accumulate instruction. */ # define MAD_F_ML0(hi, lo, x, y) \ asm ("mult %2,%3" \ : "=l" (lo), "=h" (hi) \ : "%r" ((x) >> 12), "r" ((y) >> 16)) # define MAD_F_MLA(hi, lo, x, y) \ asm ("madd16 %2,%3" \ : "+l" (lo), "+h" (hi) \ : "%r" ((x) >> 12), "r" ((y) >> 16)) # define MAD_F_MLZ(hi, lo) ((mad_fixed_t) (lo)) # endif # if defined(OPT_SPEED) # define mad_f_scale64(hi, lo) \ ((mad_fixed_t) ((hi) << (32 - MAD_F_SCALEBITS))) # define MAD_F_SCALEBITS MAD_F_FRACBITS # endif /* --- SPARC --------------------------------------------------------------- */ # elif defined(FPM_SPARC) /* * This SPARC V8 version is fast and accurate; the disposition of the least * significant bit depends on OPT_ACCURACY via mad_f_scale64(). */ # define MAD_F_MLX(hi, lo, x, y) \ asm ("smul %2, %3, %0\n\t" \ "rd %%y, %1" \ : "=r" (lo), "=r" (hi) \ : "%r" (x), "rI" (y)) /* --- PowerPC ------------------------------------------------------------- */ # elif defined(FPM_PPC) /* * This PowerPC version is fast and accurate; the disposition of the least * significant bit depends on OPT_ACCURACY via mad_f_scale64(). */ # define MAD_F_MLX(hi, lo, x, y) \ do { \ asm ("mullw %0,%1,%2" \ : "=r" (lo) \ : "%r" (x), "r" (y)); \ asm ("mulhw %0,%1,%2" \ : "=r" (hi) \ : "%r" (x), "r" (y)); \ } \ while (0) # if defined(OPT_ACCURACY) /* * This gives best accuracy but is not very fast. */ # define MAD_F_MLA(hi, lo, x, y) \ ({ mad_fixed64hi_t __hi; \ mad_fixed64lo_t __lo; \ MAD_F_MLX(__hi, __lo, (x), (y)); \ asm ("addc %0,%2,%3\n\t" \ "adde %1,%4,%5" \ : "=r" (lo), "=r" (hi) \ : "%r" (lo), "r" (__lo), \ "%r" (hi), "r" (__hi) \ : "xer"); \ }) # endif # if defined(OPT_ACCURACY) /* * This is slower than the truncating version below it. */ # define mad_f_scale64(hi, lo) \ ({ mad_fixed_t __result, __round; \ asm ("rotrwi %0,%1,%2" \ : "=r" (__result) \ : "r" (lo), "i" (MAD_F_SCALEBITS)); \ asm ("extrwi %0,%1,1,0" \ : "=r" (__round) \ : "r" (__result)); \ asm ("insrwi %0,%1,%2,0" \ : "+r" (__result) \ : "r" (hi), "i" (MAD_F_SCALEBITS)); \ asm ("add %0,%1,%2" \ : "=r" (__result) \ : "%r" (__result), "r" (__round)); \ __result; \ }) # else # define mad_f_scale64(hi, lo) \ ({ mad_fixed_t __result; \ asm ("rotrwi %0,%1,%2" \ : "=r" (__result) \ : "r" (lo), "i" (MAD_F_SCALEBITS)); \ asm ("insrwi %0,%1,%2,0" \ : "+r" (__result) \ : "r" (hi), "i" (MAD_F_SCALEBITS)); \ __result; \ }) # endif # define MAD_F_SCALEBITS MAD_F_FRACBITS /* --- Default ------------------------------------------------------------- */ # elif defined(FPM_DEFAULT) /* * This version is the most portable but it loses significant accuracy. * Furthermore, accuracy is biased against the second argument, so care * should be taken when ordering operands. * * The scale factors are constant as this is not used with SSO. * * Pre-rounding is required to stay within the limits of compliance. */ # if defined(OPT_SPEED) # define mad_f_mul(x, y) (((x) >> 12) * ((y) >> 16)) # else # define mad_f_mul(x, y) ((((x) + (1L << 11)) >> 12) * \ (((y) + (1L << 15)) >> 16)) # endif /* ------------------------------------------------------------------------- */ # else # error "no FPM selected" # endif /* default implementations */ # if !defined(mad_f_mul) # define mad_f_mul(x, y) \ ({ register mad_fixed64hi_t __hi; \ register mad_fixed64lo_t __lo; \ MAD_F_MLX(__hi, __lo, (x), (y)); \ mad_f_scale64(__hi, __lo); \ }) # endif # if !defined(MAD_F_MLA) # define MAD_F_ML0(hi, lo, x, y) ((lo) = mad_f_mul((x), (y))) # define MAD_F_MLA(hi, lo, x, y) ((lo) += mad_f_mul((x), (y))) # define MAD_F_MLN(hi, lo) ((lo) = -(lo)) # define MAD_F_MLZ(hi, lo) ((void) (hi), (mad_fixed_t) (lo)) # endif # if !defined(MAD_F_ML0) # define MAD_F_ML0(hi, lo, x, y) MAD_F_MLX((hi), (lo), (x), (y)) # endif # if !defined(MAD_F_MLN) # define MAD_F_MLN(hi, lo) ((hi) = ((lo) = -(lo)) ? ~(hi) : -(hi)) # endif # if !defined(MAD_F_MLZ) # define MAD_F_MLZ(hi, lo) mad_f_scale64((hi), (lo)) # endif # if !defined(mad_f_scale64) # if defined(OPT_ACCURACY) # define mad_f_scale64(hi, lo) \ ((((mad_fixed_t) \ (((hi) << (32 - (MAD_F_SCALEBITS - 1))) | \ ((lo) >> (MAD_F_SCALEBITS - 1)))) + 1) >> 1) # else # define mad_f_scale64(hi, lo) \ ((mad_fixed_t) \ (((hi) << (32 - MAD_F_SCALEBITS)) | \ ((lo) >> MAD_F_SCALEBITS))) # endif # define MAD_F_SCALEBITS MAD_F_FRACBITS # endif /* C routines */ mad_fixed_t mad_f_abs(mad_fixed_t); mad_fixed_t mad_f_div(mad_fixed_t, mad_fixed_t); # endif /* Id: bit.h,v 1.12 2004/01/23 09:41:32 rob Exp */ # ifndef LIBMAD_BIT_H # define LIBMAD_BIT_H struct mad_bitptr { unsigned char const *byte; unsigned short cache; unsigned short left; }; void mad_bit_init(struct mad_bitptr *, unsigned char const *); # define mad_bit_finish(bitptr) /* nothing */ unsigned int mad_bit_length(struct mad_bitptr const *, struct mad_bitptr const *); # define mad_bit_bitsleft(bitptr) ((bitptr)->left) unsigned char const *mad_bit_nextbyte(struct mad_bitptr const *); void mad_bit_skip(struct mad_bitptr *, unsigned int); unsigned long mad_bit_read(struct mad_bitptr *, unsigned int); void mad_bit_write(struct mad_bitptr *, unsigned int, unsigned long); unsigned short mad_bit_crc(struct mad_bitptr, unsigned int, unsigned short); # endif /* Id: timer.h,v 1.16 2004/01/23 09:41:33 rob Exp */ # ifndef LIBMAD_TIMER_H # define LIBMAD_TIMER_H typedef struct { signed long seconds; /* whole seconds */ unsigned long fraction; /* 1/MAD_TIMER_RESOLUTION seconds */ } mad_timer_t; extern mad_timer_t const mad_timer_zero; # define MAD_TIMER_RESOLUTION 352800000UL enum mad_units { MAD_UNITS_HOURS = -2, MAD_UNITS_MINUTES = -1, MAD_UNITS_SECONDS = 0, /* metric units */ MAD_UNITS_DECISECONDS = 10, MAD_UNITS_CENTISECONDS = 100, MAD_UNITS_MILLISECONDS = 1000, /* audio sample units */ MAD_UNITS_8000_HZ = 8000, MAD_UNITS_11025_HZ = 11025, MAD_UNITS_12000_HZ = 12000, MAD_UNITS_16000_HZ = 16000, MAD_UNITS_22050_HZ = 22050, MAD_UNITS_24000_HZ = 24000, MAD_UNITS_32000_HZ = 32000, MAD_UNITS_44100_HZ = 44100, MAD_UNITS_48000_HZ = 48000, /* video frame/field units */ MAD_UNITS_24_FPS = 24, MAD_UNITS_25_FPS = 25, MAD_UNITS_30_FPS = 30, MAD_UNITS_48_FPS = 48, MAD_UNITS_50_FPS = 50, MAD_UNITS_60_FPS = 60, /* CD audio frames */ MAD_UNITS_75_FPS = 75, /* video drop-frame units */ MAD_UNITS_23_976_FPS = -24, MAD_UNITS_24_975_FPS = -25, MAD_UNITS_29_97_FPS = -30, MAD_UNITS_47_952_FPS = -48, MAD_UNITS_49_95_FPS = -50, MAD_UNITS_59_94_FPS = -60 }; # define mad_timer_reset(timer) ((void) (*(timer) = mad_timer_zero)) int mad_timer_compare(mad_timer_t, mad_timer_t); # define mad_timer_sign(timer) mad_timer_compare((timer), mad_timer_zero) void mad_timer_negate(mad_timer_t *); mad_timer_t mad_timer_abs(mad_timer_t); void mad_timer_set(mad_timer_t *, unsigned long, unsigned long, unsigned long); void mad_timer_add(mad_timer_t *, mad_timer_t); void mad_timer_multiply(mad_timer_t *, signed long); signed long mad_timer_count(mad_timer_t, enum mad_units); unsigned long mad_timer_fraction(mad_timer_t, unsigned long); void mad_timer_string(mad_timer_t, char *, char const *, enum mad_units, enum mad_units, unsigned long); # endif /* Id: stream.h,v 1.20 2004/02/05 09:02:39 rob Exp */ # ifndef LIBMAD_STREAM_H # define LIBMAD_STREAM_H # define MAD_BUFFER_GUARD 8 # define MAD_BUFFER_MDLEN (511 + 2048 + MAD_BUFFER_GUARD) enum mad_error { MAD_ERROR_NONE = 0x0000, /* no error */ MAD_ERROR_BUFLEN = 0x0001, /* input buffer too small (or EOF) */ MAD_ERROR_BUFPTR = 0x0002, /* invalid (null) buffer pointer */ MAD_ERROR_NOMEM = 0x0031, /* not enough memory */ MAD_ERROR_LOSTSYNC = 0x0101, /* lost synchronization */ MAD_ERROR_BADLAYER = 0x0102, /* reserved header layer value */ MAD_ERROR_BADBITRATE = 0x0103, /* forbidden bitrate value */ MAD_ERROR_BADSAMPLERATE = 0x0104, /* reserved sample frequency value */ MAD_ERROR_BADEMPHASIS = 0x0105, /* reserved emphasis value */ MAD_ERROR_BADCRC = 0x0201, /* CRC check failed */ MAD_ERROR_BADBITALLOC = 0x0211, /* forbidden bit allocation value */ MAD_ERROR_BADSCALEFACTOR = 0x0221, /* bad scalefactor index */ MAD_ERROR_BADMODE = 0x0222, /* bad bitrate/mode combination */ MAD_ERROR_BADFRAMELEN = 0x0231, /* bad frame length */ MAD_ERROR_BADBIGVALUES = 0x0232, /* bad big_values count */ MAD_ERROR_BADBLOCKTYPE = 0x0233, /* reserved block_type */ MAD_ERROR_BADSCFSI = 0x0234, /* bad scalefactor selection info */ MAD_ERROR_BADDATAPTR = 0x0235, /* bad main_data_begin pointer */ MAD_ERROR_BADPART3LEN = 0x0236, /* bad audio data length */ MAD_ERROR_BADHUFFTABLE = 0x0237, /* bad Huffman table select */ MAD_ERROR_BADHUFFDATA = 0x0238, /* Huffman data overrun */ MAD_ERROR_BADSTEREO = 0x0239 /* incompatible block_type for JS */ }; # define MAD_RECOVERABLE(error) ((error) & 0xff00) struct mad_stream { unsigned char const *buffer; /* input bitstream buffer */ unsigned char const *bufend; /* end of buffer */ unsigned long skiplen; /* bytes to skip before next frame */ int sync; /* stream sync found */ unsigned long freerate; /* free bitrate (fixed) */ unsigned char const *this_frame; /* start of current frame */ unsigned char const *next_frame; /* start of next frame */ struct mad_bitptr ptr; /* current processing bit pointer */ struct mad_bitptr anc_ptr; /* ancillary bits pointer */ unsigned int anc_bitlen; /* number of ancillary bits */ unsigned char (*main_data)[MAD_BUFFER_MDLEN]; /* Layer III main_data() */ unsigned int md_len; /* bytes in main_data */ int options; /* decoding options (see below) */ enum mad_error error; /* error code (see above) */ }; enum { MAD_OPTION_IGNORECRC = 0x0001, /* ignore CRC errors */ MAD_OPTION_HALFSAMPLERATE = 0x0002 /* generate PCM at 1/2 sample rate */ # if 0 /* not yet implemented */ MAD_OPTION_LEFTCHANNEL = 0x0010, /* decode left channel only */ MAD_OPTION_RIGHTCHANNEL = 0x0020, /* decode right channel only */ MAD_OPTION_SINGLECHANNEL = 0x0030 /* combine channels */ # endif }; void mad_stream_init(struct mad_stream *); void mad_stream_finish(struct mad_stream *); # define mad_stream_options(stream, opts) \ ((void) ((stream)->options = (opts))) void mad_stream_buffer(struct mad_stream *, unsigned char const *, unsigned long); void mad_stream_skip(struct mad_stream *, unsigned long); int mad_stream_sync(struct mad_stream *); char const *mad_stream_errorstr(struct mad_stream const *); # endif /* Id: frame.h,v 1.20 2004/01/23 09:41:32 rob Exp */ # ifndef LIBMAD_FRAME_H # define LIBMAD_FRAME_H enum mad_layer { MAD_LAYER_I = 1, /* Layer I */ MAD_LAYER_II = 2, /* Layer II */ MAD_LAYER_III = 3 /* Layer III */ }; enum mad_mode { MAD_MODE_SINGLE_CHANNEL = 0, /* single channel */ MAD_MODE_DUAL_CHANNEL = 1, /* dual channel */ MAD_MODE_JOINT_STEREO = 2, /* joint (MS/intensity) stereo */ MAD_MODE_STEREO = 3 /* normal LR stereo */ }; enum mad_emphasis { MAD_EMPHASIS_NONE = 0, /* no emphasis */ MAD_EMPHASIS_50_15_US = 1, /* 50/15 microseconds emphasis */ MAD_EMPHASIS_CCITT_J_17 = 3, /* CCITT J.17 emphasis */ MAD_EMPHASIS_RESERVED = 2 /* unknown emphasis */ }; struct mad_header { enum mad_layer layer; /* audio layer (1, 2, or 3) */ enum mad_mode mode; /* channel mode (see above) */ int mode_extension; /* additional mode info */ enum mad_emphasis emphasis; /* de-emphasis to use (see above) */ unsigned long bitrate; /* stream bitrate (bps) */ unsigned int samplerate; /* sampling frequency (Hz) */ unsigned short crc_check; /* frame CRC accumulator */ unsigned short crc_target; /* final target CRC checksum */ int flags; /* flags (see below) */ int private_bits; /* private bits (see below) */ mad_timer_t duration; /* audio playing time of frame */ }; struct mad_frame { struct mad_header header; /* MPEG audio header */ int options; /* decoding options (from stream) */ mad_fixed_t sbsample[2][36][32]; /* synthesis subband filter samples */ mad_fixed_t (*overlap)[2][32][18]; /* Layer III block overlap data */ }; # define MAD_NCHANNELS(header) ((header)->mode ? 2 : 1) # define MAD_NSBSAMPLES(header) \ ((header)->layer == MAD_LAYER_I ? 12 : \ (((header)->layer == MAD_LAYER_III && \ ((header)->flags & MAD_FLAG_LSF_EXT)) ? 18 : 36)) enum { MAD_FLAG_NPRIVATE_III = 0x0007, /* number of Layer III private bits */ MAD_FLAG_INCOMPLETE = 0x0008, /* header but not data is decoded */ MAD_FLAG_PROTECTION = 0x0010, /* frame has CRC protection */ MAD_FLAG_COPYRIGHT = 0x0020, /* frame is copyright */ MAD_FLAG_ORIGINAL = 0x0040, /* frame is original (else copy) */ MAD_FLAG_PADDING = 0x0080, /* frame has additional slot */ MAD_FLAG_I_STEREO = 0x0100, /* uses intensity joint stereo */ MAD_FLAG_MS_STEREO = 0x0200, /* uses middle/side joint stereo */ MAD_FLAG_FREEFORMAT = 0x0400, /* uses free format bitrate */ MAD_FLAG_LSF_EXT = 0x1000, /* lower sampling freq. extension */ MAD_FLAG_MC_EXT = 0x2000, /* multichannel audio extension */ MAD_FLAG_MPEG_2_5_EXT = 0x4000 /* MPEG 2.5 (unofficial) extension */ }; enum { MAD_PRIVATE_HEADER = 0x0100, /* header private bit */ MAD_PRIVATE_III = 0x001f /* Layer III private bits (up to 5) */ }; void mad_header_init(struct mad_header *); # define mad_header_finish(header) /* nothing */ int mad_header_decode(struct mad_header *, struct mad_stream *); void mad_frame_init(struct mad_frame *); void mad_frame_finish(struct mad_frame *); int mad_frame_decode(struct mad_frame *, struct mad_stream *); void mad_frame_mute(struct mad_frame *); # endif /* Id: synth.h,v 1.15 2004/01/23 09:41:33 rob Exp */ # ifndef LIBMAD_SYNTH_H # define LIBMAD_SYNTH_H struct mad_pcm { unsigned int samplerate; /* sampling frequency (Hz) */ unsigned short channels; /* number of channels */ unsigned short length; /* number of samples per channel */ mad_fixed_t samples[2][1152]; /* PCM output samples [ch][sample] */ }; struct mad_synth { mad_fixed_t filter[2][2][2][16][8]; /* polyphase filterbank outputs */ /* [ch][eo][peo][s][v] */ unsigned int phase; /* current processing phase */ struct mad_pcm pcm; /* PCM output */ }; /* single channel PCM selector */ enum { MAD_PCM_CHANNEL_SINGLE = 0 }; /* dual channel PCM selector */ enum { MAD_PCM_CHANNEL_DUAL_1 = 0, MAD_PCM_CHANNEL_DUAL_2 = 1 }; /* stereo PCM selector */ enum { MAD_PCM_CHANNEL_STEREO_LEFT = 0, MAD_PCM_CHANNEL_STEREO_RIGHT = 1 }; void mad_synth_init(struct mad_synth *); # define mad_synth_finish(synth) /* nothing */ void mad_synth_mute(struct mad_synth *); void mad_synth_frame(struct mad_synth *, struct mad_frame const *); # endif /* Id: decoder.h,v 1.17 2004/01/23 09:41:32 rob Exp */ # ifndef LIBMAD_DECODER_H # define LIBMAD_DECODER_H enum mad_decoder_mode { MAD_DECODER_MODE_SYNC = 0, MAD_DECODER_MODE_ASYNC }; enum mad_flow { MAD_FLOW_CONTINUE = 0x0000, /* continue normally */ MAD_FLOW_STOP = 0x0010, /* stop decoding normally */ MAD_FLOW_BREAK = 0x0011, /* stop decoding and signal an error */ MAD_FLOW_IGNORE = 0x0020 /* ignore the current frame */ }; struct mad_decoder { enum mad_decoder_mode mode; int options; struct { long pid; int in; int out; } async; struct { struct mad_stream stream; struct mad_frame frame; struct mad_synth synth; } *sync; void *cb_data; enum mad_flow (*input_func)(void *, struct mad_stream *); enum mad_flow (*header_func)(void *, struct mad_header const *); enum mad_flow (*filter_func)(void *, struct mad_stream const *, struct mad_frame *); enum mad_flow (*output_func)(void *, struct mad_header const *, struct mad_pcm *); enum mad_flow (*error_func)(void *, struct mad_stream *, struct mad_frame *); enum mad_flow (*message_func)(void *, void *, unsigned int *); }; void mad_decoder_init(struct mad_decoder *, void *, enum mad_flow (*)(void *, struct mad_stream *), enum mad_flow (*)(void *, struct mad_header const *), enum mad_flow (*)(void *, struct mad_stream const *, struct mad_frame *), enum mad_flow (*)(void *, struct mad_header const *, struct mad_pcm *), enum mad_flow (*)(void *, struct mad_stream *, struct mad_frame *), enum mad_flow (*)(void *, void *, unsigned int *)); int mad_decoder_finish(struct mad_decoder *); # define mad_decoder_options(decoder, opts) \ ((void) ((decoder)->options = (opts))) int mad_decoder_run(struct mad_decoder *, enum mad_decoder_mode); int mad_decoder_message(struct mad_decoder *, void *, unsigned int *); # endif # ifdef __cplusplus } # endif ================================================ FILE: lib/3rdparty/patches/README ================================================ tbytevector_cpp.patch is for Taglib 1.5 to fix v.slow parsing of some rare MP3 files, see: http://bugs.kde.org/show_bug.cgi?id=159821 **** ================================================ FILE: lib/3rdparty/patches/tbytevector_cpp.patch ================================================ Index: tbytevector.cpp =================================================================== --- tbytevector.cpp (revision 789815) +++ tbytevector.cpp (working copy) @@ -432,24 +432,62 @@ const int patternSize = pattern.size(); const int withSize = with.size(); - int offset = find(pattern); + if(withSize<=patternSize){ + int cpyOffset(0); + int cpySize(0); + int srcOffset(0); - while(offset >= 0) { + while( srcOffset < size()-patternSize ){ + if( memcmp(pattern.data(),data()+srcOffset,patternSize)==0 ){ + // Match, lets move the memory - const int originalSize = size(); + // Move + if(cpySize){ + memmove(data()+cpyOffset,data()+srcOffset-cpySize,cpySize); + cpyOffset += cpySize; + cpySize=0; + } - if(withSize > patternSize) - resize(originalSize + withSize - patternSize); + // Copy the replacement + memcpy(data()+cpyOffset,with.data(),withSize); + cpyOffset += withSize; - if(patternSize != withSize) - ::memcpy(data() + offset + withSize, mid(offset + patternSize).data(), originalSize - offset - patternSize); + // Jump ahead the patternSize + srcOffset+=patternSize; + + }else{ + // No match, step ahead + ++cpySize; + ++srcOffset; + } + } - if(withSize < patternSize) - resize(originalSize + withSize - patternSize); + memmove(data()+cpyOffset,data()+srcOffset-cpySize,cpySize+patternSize); + cpyOffset += cpySize+patternSize; - ::memcpy(data() + offset, with.data(), withSize); + resize(cpyOffset); - offset = find(pattern, offset + withSize); + }else{ + + int offset = find(pattern); + + while(offset >= 0) { + + const int originalSize = size(); + + if(withSize > patternSize) + resize(originalSize + withSize - patternSize); + + if(patternSize != withSize) + ::memcpy(data() + offset + withSize, mid(offset + patternSize).data(), originalSize - offset - patternSize); + + if(withSize < patternSize) + resize(originalSize + withSize - patternSize); + + ::memcpy(data() + offset, with.data(), withSize); + + offset = find(pattern, offset + withSize); + } } return *this; ================================================ FILE: lib/3rdparty/samplerate.h ================================================ /* ** Copyright (C) 2002-2004 Erik de Castro Lopo <erikd@mega-nerd.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, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ /* ** API documentation is available here: ** http://www.mega-nerd.com/SRC/api.html */ #ifndef SAMPLERATE_H #define SAMPLERATE_H #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* Opaque data type SRC_STATE. */ typedef struct SRC_STATE_tag SRC_STATE ; /* SRC_DATA is used to pass data to src_simple() and src_process(). */ typedef struct { float *data_in, *data_out ; long input_frames, output_frames ; long input_frames_used, output_frames_gen ; int end_of_input ; double src_ratio ; } SRC_DATA ; /* SRC_CB_DATA is used with callback based API. */ typedef struct { long frames ; float *data_in ; } SRC_CB_DATA ; /* ** User supplied callback function type for use with src_callback_new() ** and src_callback_read(). First parameter is the same pointer that was ** passed into src_callback_new(). Second parameter is pointer to a ** pointer. The user supplied callback function must modify *data to ** point to the start of the user supplied float array. The user supplied ** function must return the number of frames that **data points to. */ typedef long (*src_callback_t) (void *cb_data, float **data) ; /* ** Standard initialisation function : return an anonymous pointer to the ** internal state of the converter. Choose a converter from the enums below. ** Error returned in *error. */ SRC_STATE* src_new (int converter_type, int channels, int *error) ; /* ** Initilisation for callback based API : return an anonymous pointer to the ** internal state of the converter. Choose a converter from the enums below. ** The cb_data pointer can point to any data or be set to NULL. Whatever the ** value, when processing, user supplied function "func" gets called with ** cb_data as first parameter. */ SRC_STATE* src_callback_new (src_callback_t func, int converter_type, int channels, int *error, void* cb_data) ; /* ** Cleanup all internal allocations. ** Always returns NULL. */ SRC_STATE* src_delete (SRC_STATE *state) ; /* ** Standard processing function. ** Returns non zero on error. */ int src_process (SRC_STATE *state, SRC_DATA *data) ; /* ** Callback based processing function. Read up to frames worth of data from ** the converter int *data and return frames read or -1 on error. */ long src_callback_read (SRC_STATE *state, double src_ratio, long frames, float *data) ; /* ** Simple interface for performing a single conversion from input buffer to ** output buffer at a fixed conversion ratio. ** Simple interface does not require initialisation as it can only operate on ** a single buffer worth of audio. */ int src_simple (SRC_DATA *data, int converter_type, int channels) ; /* ** This library contains a number of different sample rate converters, ** numbered 0 through N. ** ** Return a string giving either a name or a more full description of each ** sample rate converter or NULL if no sample rate converter exists for ** the given value. The converters are sequentially numbered from 0 to N. */ const char *src_get_name (int converter_type) ; const char *src_get_description (int converter_type) ; const char *src_get_version (void) ; /* ** Set a new SRC ratio. This allows step responses ** in the conversion ratio. ** Returns non zero on error. */ int src_set_ratio (SRC_STATE *state, double new_ratio) ; /* ** Reset the internal SRC state. ** Does not modify the quality settings. ** Does not free any memory allocations. ** Returns non zero on error. */ int src_reset (SRC_STATE *state) ; /* ** Return TRUE if ratio is a valid conversion ratio, FALSE ** otherwise. */ int src_is_valid_ratio (double ratio) ; /* ** Return an error number. */ int src_error (SRC_STATE *state) ; /* ** Convert the error number into a string. */ const char* src_strerror (int error) ; /* ** The following enums can be used to set the interpolator type ** using the function src_set_converter(). */ enum { SRC_SINC_BEST_QUALITY = 0, SRC_SINC_MEDIUM_QUALITY = 1, SRC_SINC_FASTEST = 2, SRC_ZERO_ORDER_HOLD = 3, SRC_LINEAR = 4 } ; /* ** Extra helper functions for converting from short to float and ** back again. */ void src_short_to_float_array (const short *in, float *out, int len) ; void src_float_to_short_array (const float *in, short *out, int len) ; #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ #endif /* SAMPLERATE_H */ /* ** Do not edit or modify anything in this comment block. ** The arch-tag line is a file identity tag for the GNU Arch ** revision control system. ** ** arch-tag: 5421ef3e-c898-4ec3-8671-ea03d943ee00 */ ================================================ FILE: lib/DllExportMacro.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef DLL_EXPORT_MACRO_H #define DLL_EXPORT_MACRO_H /** Exports symbols when compiled as part of the lib * Imports when included from some other target */ #if defined(_WIN32) || defined(WIN32) #ifdef _UNICORN_DLLEXPORT #define UNICORN_DLLEXPORT __declspec(dllexport) #else #define UNICORN_DLLEXPORT __declspec(dllimport) #endif #ifdef _LISTENER_DLLEXPORT #define LISTENER_DLLEXPORT __declspec(dllexport) #else #define LISTENER_DLLEXPORT __declspec(dllimport) #endif #ifndef ITUNES_PLUGIN #ifdef _LOGGER_DLLEXPORT #define LOGGER_DLLEXPORT __declspec(dllexport) #else #define LOGGER_DLLEXPORT __declspec(dllimport) #endif #else #define LOGGER_DLLEXPORT #endif #else #define UNICORN_DLLEXPORT #define LISTENER_DLLEXPORT #define LOGGER_DLLEXPORT #endif #endif ================================================ FILE: lib/listener/PlayerCommand.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PLAYER_COMMAND_H #define PLAYER_COMMAND_H enum PlayerCommand { CommandInit, CommandStart, CommandPause, CommandResume, CommandStop, CommandTerm, CommandBootstrap }; #endif ================================================ FILE: lib/listener/PlayerCommandParser.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QDebug> #include <QStringList> #include <QUrl> #ifdef Q_OS_WIN #include <windows.h> #endif #include "plugins/iTunes/ITunesComWrapper.h" #include "PlayerCommandParser.h" using std::invalid_argument; PlayerCommandParser::PlayerCommandParser( QString line ) throw( std::invalid_argument ) { line = line.trimmed(); if (line.isEmpty()) throw invalid_argument( "Command string seems to be empty" ); qDebug() << line; m_command = extractCommand( line ); //removes the command string from line line = line.trimmed(); QMap<QChar, QString> const args = extractArgs( line ); QString const required = requiredArgs( m_command ); for (int i = 0; i < required.length(); ++i) { QChar const c = required[i]; if (!args.contains( c )) throw invalid_argument( "Mandatory argument unspecified: " + c.toAscii() ); } m_playerId = args['c']; if (m_playerId.isEmpty()) throw invalid_argument( "Player ID cannot be zero length" ); switch (m_command) { case CommandStart: m_track = extractTrack( args ); break; case CommandBootstrap: m_username = args['u']; break; case CommandInit: m_applicationPath = args['f']; default: break; } } PlayerCommand PlayerCommandParser::extractCommand( QString& line ) { int const n = line.indexOf( ' ' ); if (n == -1) throw invalid_argument( "Unable to parse" ); QString const command = line.left( n ).toUpper(); // Trim off command from passed in string line = line.mid( n + 1 ); if (command == "START") return CommandStart; if (command == "STOP") return CommandStop; if (command == "PAUSE") return CommandPause; if (command == "RESUME") return CommandResume; if (command == "BOOTSTRAP") return CommandBootstrap; if (command == "INIT") return CommandInit; if (command == "TERM") return CommandTerm; throw invalid_argument( "Invalid command" ); } namespace mxcl { /** replaces '&&' and splits on remaining single '&' */ QStringList static inline split( QString line ) { QStringList parts; int start = 0, i = 0; int end = 0; while ((end = line.indexOf( '&', i )) != -1) { i = end + 1; if (line[i] == '&') { line.remove( end, 1 ); //convert && to & continue; } parts += line.mid( start, end - start ); start = i; } return parts << line.mid( start ); } } struct Pair { Pair( const QString& s ) { if (s[1] == '=') { key = s[0]; value = s.mid( 2 ); } } QChar key; QString value; }; QMap<QChar, QString> PlayerCommandParser::extractArgs( const QString& line ) { QMap<QChar, QString> map; // split by single & only, doubles are in fact & foreach (Pair pair, mxcl::split( line )) { if (pair.key == QChar()) throw invalid_argument( "Invalid pair: " + pair.key.toAscii() + '=' + std::string(pair.value.toUtf8().data()) ); if (map.contains( pair.key )) throw invalid_argument( "Field identifier occurred twice in request: " + pair.key.toAscii() ); map[pair.key] = pair.value.trimmed(); } return map; } QString PlayerCommandParser::requiredArgs( PlayerCommand c ) { switch (c) { case CommandStart: return "catblp"; case CommandBootstrap: return "cu"; case CommandInit: return "cf"; case CommandStop: case CommandPause: case CommandResume: case CommandTerm: default: // gcc 4.2 is stupid return "c"; } } Track PlayerCommandParser::extractTrack( const QMap<QChar, QString>& args ) { lastfm::MutableTrack track; track.setArtist( args['a'] ); track.setAlbumArtist( args['d'] ); track.setTitle( args['t'] ); track.setAlbum( args['b'] ); track.setMbid( Mbid( args['m'] ) ); track.setDuration( args['l'].toInt() ); track.setUrl( QUrl::fromLocalFile( QUrl::fromPercentEncoding( args['p'].toUtf8() ) ) ); track.setSource( Track::Player ); track.setExtra( "playerId", args['c'] ); track.setExtra( "playerName", playerName() ); #ifdef Q_OS_WIN if ( args['c'] == "itw" ) { ITunesComWrapper* com = new ITunesComWrapper; ITunesTrack comTrack = com->currentTrack(); bool podcast = comTrack.podcast(); bool video = comTrack.video(); QUrl path = QUrl::fromLocalFile( QString::fromStdWString( comTrack.path() ) ); qDebug() << QString::fromStdWString( comTrack.artist() ) << QString::fromStdWString( comTrack.track() ) << podcast << video << path; track.setUrl( path ); track.setPodcast( podcast ); track.setVideo( video ); delete com; } #endif //TODO should be done earlier, NOTE don't get the plugin to send a stamp // time as this is prolly unecessary, and I bet you get new bugs! track.stamp(); return track; } ================================================ FILE: lib/listener/PlayerCommandParser.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PLAYER_COMMAND_PARSER_H #define PLAYER_COMMAND_PARSER_H #include "common/HideStupidWarnings.h" #include "PlayerCommand.h" #include <lastfm/Track.h> #include <stdexcept> // ignore the warning about the exception specification #ifdef Q_OS_WIN #pragma warning( disable : 4290 ) #endif class PlayerCommandParser { public: PlayerCommandParser( QString line ) throw( std::invalid_argument ); PlayerCommand command() const { return m_command; } QString playerId() const { return m_playerId; } Track track() const { return m_track; } QString username() const { return m_username; } /** we use this to get a pretty name for the player, and its icon * Use the full path for the .exe file on Windows and Linux, and the bundle * directory on Mac OS X */ QString applicationPath() const { return m_applicationPath; } QString playerName() { QString& id = m_playerId; if (id == "osx") return "iTunes"; if (id == "itw") return "iTunes"; if (id == "foo") return "foobar2000"; if (id == "wa2") return "Winamp"; if (id == "wmp") return "Windows Media Player"; if (id == "ass") return "Last.fm Radio"; if (id == "bof") return "Last.fm Boffin"; return QObject::tr( "unknown media player" ); } private: PlayerCommand extractCommand( QString& line ); QMap<QChar, QString> extractArgs( const QString& line ); QString requiredArgs( PlayerCommand ); Track extractTrack( const QMap<QChar, QString>& args ); PlayerCommand m_command; QString m_playerId; Track m_track; QString m_username; QString m_applicationPath; }; #endif // PLAYER_COMMAND_PARSER_H ================================================ FILE: lib/listener/PlayerConnection.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "PlayerConnection.h" #include <QtAlgorithms> #include <QDebug> #include <QTimer> class Error { const char* m_msg; protected: Error( const char* msg ) : m_msg( msg ) {} virtual ~Error() {} public: const char* message() const { return m_msg; } virtual bool isFatal() const { return false; } }; struct NonFatalError : public Error { NonFatalError( const char* msg ) : Error( msg ) {} }; struct FatalError : public Error { FatalError( const char* msg ) : Error( msg ) {} virtual bool isFatal() const { return true; } }; PlayerConnection::PlayerConnection() : m_elapsed( 0 ), m_state( Stopped ) { } PlayerConnection::PlayerConnection( const QString& id, const QString& name, QObject* parent ) :QObject( parent ) , m_id( id ) , m_name( name ) , m_elapsed( 0 ) , m_state( Stopped ) { Q_ASSERT( id.size() ); } void PlayerConnection::forceTrackStarted( const Track& t ) { emit trackStarted( track(), t ); } void PlayerConnection::forcePaused() { emit paused(); } void PlayerConnection::handleCommand( PlayerCommand command, Track t ) { qDebug() << command; try { switch (command) { case CommandStart: if (t.isNull()) throw FatalError("Can't start a null track"); m_state = Playing; if ( m_stoppedTimer ) m_stoppedTimer->stop(); if (t == m_track && t.timestamp() == m_track.timestamp()) { emit resumed(); throw NonFatalError("Already playing this track"); } qSwap(m_track, t); m_elapsed = 0; emit trackStarted( m_track, t ); break; case CommandPause: if (m_track.isNull()) throw FatalError("Cannot pause a null track"); if (m_state == Paused) throw NonFatalError("Already paused"); m_state = Paused; emit paused(); break; case CommandResume: if (m_track.isNull()) throw FatalError("Can't resume null track"); if (m_state == Playing) throw NonFatalError("Already playing"); m_state = Playing; emit resumed(); break; case CommandTerm: case CommandInit: case CommandStop: // don't process the stop straight away because we could be skipping // track so wait a second to make sure we don't get a start command if ( !m_stoppedTimer ) { m_stoppedTimer = new QTimer( this ); m_stoppedTimer->setSingleShot( true ); m_stoppedTimer->setInterval( 1000 ); connect( m_stoppedTimer, SIGNAL(timeout()), this, SLOT(onStopped()) ); } m_stoppedTimer->start(); break; case CommandBootstrap: emit bootstrapReady( id() ); break; } } catch (Error& error) { qWarning() << error.message(); if (error.isFatal()) { m_state = Stopped; m_track = Track(); emit stopped(); } } } void PlayerConnection::onStopped() { m_track = Track(); if (m_state == Stopped) { qWarning() << "Already stopped"; } else { m_state = Stopped; m_elapsed = 0; emit stopped(); } } ================================================ FILE: lib/listener/PlayerConnection.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PLAYER_CONNECTION_H #define PLAYER_CONNECTION_H #include "lib/DllExportMacro.h" #include "PlayerCommand.h" #include "State.h" #include <lastfm/Track.h> #include <QTimer> #include <QPointer> /** delete yourself when the player closes/quits */ class LISTENER_DLLEXPORT PlayerConnection : public QObject { Q_OBJECT protected: QPointer<QTimer> m_stoppedTimer; QString const m_id; QString const m_name; uint m_elapsed; PlayerConnection(); State m_state; Track m_track; public: PlayerConnection( const QString& id, const QString& name, QObject* parent = 0 ); ~PlayerConnection() { bool const wasStopped = m_state == Stopped; clear(); if (!wasStopped) emit stopped(); } QString name() const { return m_name; } QString id() const { return m_id; } virtual Track track() const { return m_track; } virtual State state() const { return m_state; } /** 0 until we are paused and made non-current by the mediator * then we store elapsed_scrobble_time */ void setElapsed( uint i ) { m_elapsed = i; } uint elapsed() const { return m_elapsed; } void clear() { m_state = Stopped; m_track = Track(); m_elapsed = 0; } /** only pass the track for CommandStart */ void handleCommand( PlayerCommand, Track = Track() ); void forceTrackStarted( const Track& ); void forcePaused(); signals: void trackStarted( const lastfm::Track&, const lastfm::Track& ); void paused(); void resumed(); void stopped(); void bootstrapReady( const QString& playerId ); private slots: void onStopped(); }; #endif ================================================ FILE: lib/listener/PlayerListener.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "PlayerListener.h" #include "PlayerCommandParser.h" #include "PlayerConnection.h" #include <QLocalSocket> #include <QDir> #include <QFile> #include <QThread> #ifdef Q_OS_WIN #include "win/NamedPipeServer.h" #else #include <lastfm/misc.h> #endif PlayerListener::PlayerListener( QObject* parent ) : QLocalServer( parent ) { connect( this, SIGNAL(newConnection()), SLOT(onNewConnection()) ); // Create a user-unique name to listen on. // User-unique so that different logged-on users // can run their own scrobbler instances. #ifdef Q_OS_WIN NamedPipeServer* namedPipeServer = new NamedPipeServer( this ); connect( namedPipeServer, SIGNAL(lineReady(QString)), this, SLOT(processLine(QString)), Qt::BlockingQueuedConnection ); namedPipeServer->start(); #else QString const name = "lastfm_scrobsub"; // on windows we use named pipes which auto-delete // *nix platforms need more help: QString fullPath = lastfm::dir::runtimeData().absolutePath() + "/" + name; if( QFile::exists( fullPath )) QFile::remove( fullPath ); bool success = listen( fullPath ); Q_ASSERT( success ); #endif } void PlayerListener::onNewConnection() { qDebug() << hasPendingConnections(); while (hasPendingConnections()) { QObject* o = nextPendingConnection(); connect( o, SIGNAL(readyRead()), SLOT(onDataReady()) ); connect( o, SIGNAL(disconnected()), o, SLOT(deleteLater()) ); } } void PlayerListener::onDataReady() { QLocalSocket* socket = qobject_cast<QLocalSocket*>(sender()); if (!socket) return; while (socket->canReadLine()) { QString const line = QString::fromUtf8( socket->readLine() ); QString response = processLine( line ); socket->write( response.toUtf8() ); } } QString PlayerListener::processLine( const QString& line ) { QString response = "OK\n"; try { PlayerCommandParser parser( line ); QString const id = parser.playerId(); PlayerConnection* connection = 0; if (!m_connections.contains( id )) { connection = m_connections[id] = new PlayerConnection( parser.playerId(), parser.playerName() ); emit newConnection( connection ); } else connection = m_connections[id]; switch (parser.command()) { case CommandBootstrap: emit bootstrapCompleted( parser.playerId() ); break; case CommandTerm: delete connection; m_connections.remove( parser.playerId() ); break; default: connection->handleCommand( parser.command(), parser.track() ); break; } } catch (std::invalid_argument& e) { qWarning() << e.what(); response = "ERROR: " + QString::fromStdString(e.what()) + "\n"; } return response; } ================================================ FILE: lib/listener/PlayerListener.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PLAYER_LISTENER_H #define PLAYER_LISTENER_H #include <QLocalServer> #include <QMap> #include "common/HideStupidWarnings.h" #include "PlayerConnection.h" #include "lib/DllExportMacro.h" /** listens to external clients via a TcpSocket and notifies a receiver to their * commands */ class LISTENER_DLLEXPORT PlayerListener : public QLocalServer { Q_OBJECT public: explicit PlayerListener( QObject* parent = 0 ); signals: void newConnection( class PlayerConnection* ); void bootstrapCompleted( const QString& playerId ); private slots: void onNewConnection(); void onDataReady(); QString processLine( const QString& line ); private: QMap<QString, PlayerConnection*> m_connections; }; #endif ================================================ FILE: lib/listener/PlayerMediator.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "PlayerMediator.h" PlayerMediator::PlayerMediator( QObject* parent ) : QObject( parent ) {} void PlayerMediator::follow( PlayerConnection* connection ) { if (m_connections.contains( connection )) { qWarning() << "Already following:" << connection; return; } m_connections += connection; connect( connection, SIGNAL(trackStarted(lastfm::Track,lastfm::Track)), SLOT(onActivity()) ); connect( connection, SIGNAL(resumed()), SLOT(onActivity()) ); connect( connection, SIGNAL(stopped()), SLOT(onActivity()) ); connect( connection, SIGNAL(bootstrapReady(QString)), SLOT(onActivity()) ); connect( connection, SIGNAL(destroyed()), SLOT(onDestroyed()) ); assess( connection ); } void PlayerMediator::onActivity() { PlayerConnection* connection = qobject_cast<PlayerConnection*>(sender()); if (m_active == connection) { if (connection->state() == Stopped) { foreach (PlayerConnection* connection, m_connections) if (connection != m_active && assess( connection )) return; } } else assess( connection ); } bool PlayerMediator::assess( PlayerConnection* connection ) { Q_ASSERT( connection ); if (!m_active) goto set_active; if ( m_active->state() == Stopped || m_active->state() == Paused || connection->id() == "ass" ) // the radio connection steals from all other sources { switch ( connection->state() ) { case Playing: goto set_active; case TuningIn: case Buffering: qWarning() << "Unsupported state for PlayerConnection"; break; default: case Stopped: case Paused: break; } } return false; set_active: if (!connection) return false; m_active = connection; emit activeConnectionChanged( connection ); return true; } void PlayerMediator::onDestroyed() { m_connections.removeAll( (PlayerConnection*) this->sender() ); } ================================================ FILE: lib/listener/PlayerMediator.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QList> #include <QPointer> #include "lib/DllExportMacro.h" #include "PlayerConnection.h" class PlayerConnection; /** Usage, add PlayerConnections, seek() for the active one, when active * connection is available the newActiveConnection() signal will be emitted * the ActionConnection will then stop seeking until you next call seek() */ class LISTENER_DLLEXPORT PlayerMediator : public QObject { Q_OBJECT QList<PlayerConnection*> m_connections; protected: QPointer<PlayerConnection> m_active; virtual bool assess( PlayerConnection* ); public: PlayerMediator( QObject* parent ); PlayerConnection* activeConnection() const { return m_active; } public slots: void follow( PlayerConnection* ); signals: void activeConnectionChanged( PlayerConnection* ); private slots: void onActivity(); void onDestroyed(); }; ================================================ FILE: lib/listener/State.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PLAYER_STATE_H #define PLAYER_STATE_H enum State { Unknown, Stopped, TuningIn, Buffering, Playing, Paused }; #include <QDebug> inline QDebug operator<<( QDebug d, State state ) { #define _( x ) x: return d << #x switch (state) { case _(Unknown); case _(Stopped); case _(TuningIn); case _(Playing); case _(Buffering); case _(Paused); } return d; } #endif ================================================ FILE: lib/listener/legacy/LegacyPlayerListener.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "LegacyPlayerListener.h" #include "../PlayerCommandParser.h" #include "../PlayerConnection.h" #include <QTcpSocket> LegacyPlayerListener::LegacyPlayerListener( QObject* parent ) : QTcpServer( parent ) { connect( this, SIGNAL(newConnection()), SLOT(onNewConnection()) ); if (!listen( QHostAddress::LocalHost, port() )) qWarning() << "Couldn't start legacy player listener"; } void LegacyPlayerListener::onNewConnection() { while (hasPendingConnections()) { QTcpSocket* socket = nextPendingConnection(); connect( socket, SIGNAL(readyRead()), SLOT(onDataReady()) ); } } void LegacyPlayerListener::onDataReady() { QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender()); if (!socket) return; connect( socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()) ); while (socket->canReadLine()) { QString line = QString::fromUtf8( socket->readLine() ); try { PlayerCommandParser parser( line ); QString const id = parser.playerId(); PlayerConnection* connection = 0; if (!m_connections.contains( id )) { connection = m_connections[id] = new PlayerConnection( parser.playerId(), parser.playerName() ); emit newConnection( connection ); } else connection = m_connections[id]; switch (parser.command()) { case CommandTerm: m_connections.remove( id ); // FALL THROUGH default: connection->handleCommand( parser.command(), parser.track() ); break; } socket->write( "OK\n" ); } catch (std::invalid_argument& e) { QString const error = QString::fromUtf8( e.what() ); qWarning() << line << error; QString s = "ERROR: " + error + "\n"; socket->write( s.toUtf8() ); } } socket->close(); } ================================================ FILE: lib/listener/legacy/LegacyPlayerListener.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef LEGACY_PLAYER_LISTENER_H #define LEGACY_PLAYER_LISTENER_H #include "lib/DllExportMacro.h" #include "../PlayerConnection.h" #include <QTcpServer> #include <QMap> class PlayerConnection; /** listens to external clients via a TcpSocket and notifies a receiver to their * commands */ class LISTENER_DLLEXPORT LegacyPlayerListener : public QTcpServer { Q_OBJECT public: LegacyPlayerListener( QObject* parent ); static uint port() { return 33367; } signals: void newConnection( class PlayerConnection* ); private slots: void onNewConnection(); void onDataReady(); private: /** handles the TERM command as conecerning this class */ void term( QTcpSocket* ); QMap<QString, PlayerConnection*> m_connections; }; #endif ================================================ FILE: lib/listener/listener.pro ================================================ TARGET = listener TEMPLATE = lib QT = core xml network CONFIG += unicorn logger unix:!mac { CONFIG += staticlib QMAKE_DISTCLEAN += -f ../../_bin/liblistener.a } include( ../../admin/include.qmake ) DEFINES += _LISTENER_DLLEXPORT LASTFM_COLLAPSE_NAMESPACE win32:LIBS += Advapi32.lib User32.lib SOURCES += \ PlayerMediator.cpp \ PlayerListener.cpp \ PlayerConnection.cpp \ PlayerCommandParser.cpp \ legacy/LegacyPlayerListener.cpp HEADERS += \ State.h \ PlayerMediator.h \ PlayerListener.h \ PlayerConnection.h \ PlayerCommandParser.h \ PlayerCommand.h \ legacy/LegacyPlayerListener.h unix:!mac { QT += dbus SOURCES -= legacy/LegacyPlayerListener.cpp HEADERS -= legacy/LegacyPlayerListener.h SOURCES += mpris2/Mpris2Listener.cpp \ mpris2/Mpris2Service.cpp HEADERS += mpris2/Mpris2Listener.h \ mpris2/Mpris2Service.h } mac { SOURCES += mac/ITunesListener.cpp OBJECTIVE_SOURCES += mac/SpotifyListener.mm HEADERS += mac/ITunesListener.h \ mac/SpotifyListener.h LIBS += -weak_framework AppKit } win32 { SOURCES += win/SpotifyListener.cpp \ win/NamedPipeServer.cpp \ ../../plugins/iTunes/ITunesTrack.cpp \ ../../plugins/iTunes/ITunesComWrapper.cpp \ $$ROOT_DIR/plugins/scrobsub/EncodingUtils.cpp \ $$ROOT_DIR/lib/3rdparty/iTunesCOMAPI/iTunesCOMInterface_i.c HEADERS += win/SpotifyListener.h \ win/NamedPipeServer.h \ ../../plugins/iTunes/ITunesTrack.h \ ../../plugins/iTunes/ITunesComWrapper.h \ ../../plugins/iTunes/ITunesEventInterface.h \ ../../plugins/iTunes/ITunesExceptions.h \ $$ROOT_DIR/plugins/scrobsub/EncodingUtils.h LIBS += -lcomsuppw DEFINES += _WIN32_DCOM } ================================================ FILE: lib/listener/mac/ITunesListener.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <lastfm/misc.h> #include "ITunesListener.h" #include "../PlayerConnection.h" struct ITunesConnection : PlayerConnection { // PlayerConnection has compile-time constants for these. QString const m_runtime_name; QString const m_runtime_id; QString runtimeName() const { return m_runtime_name; } QString runtimeId() const { return m_runtime_id; } ITunesConnection(bool isAppleMusic) : PlayerConnection( "osx", "iTunes" ) , m_runtime_name( isAppleMusic ? "Apple Music" : "iTunes" ) , m_runtime_id( isAppleMusic ? "mac" : "osx" ) {} void start( const Track& t ) { MutableTrack mt( t ); mt.setSource( Track::Player ); mt.setExtra( "playerId", runtimeId() ); mt.setExtra( "playerName", runtimeName() ); mt.stamp(); handleCommand( CommandStart, t ); } void pause() { handleCommand( CommandPause ); } void resume() { handleCommand( CommandResume ); } void stop() { handleCommand( CommandStop ); } }; ITunesListener::ITunesListener( QObject* parent ) : QObject( parent ) , m_previousPid() , m_playerAppId( getPlayerAppId() ) , m_connection( 0 ) { qDebug() << "Detected player app ID: " << m_playerAppId; qRegisterMetaType<Track>("Track"); QString mediaTypeFields = isAppleMusic() ? "\"false\" & \"\n\" & \"none\"\n" : "podcast & \"\n\" & video kind\n"; m_currentTrackScript = AppleScript( "tell application id \"" + m_playerAppId + "\" to tell current track\n" "try\n" "set L to location\n" "set L to POSIX path of L\n" "on error\n" "set L to \"\"\n" "end try\n" "return artist & \"\n\" & " "album artist & \"\n\" & " "album & \"\n\" & " "name & \"\n\" & " "(duration as integer) & \"\n\" & " "L & \"\n\" & " "persistent ID & \"\n\" & " + mediaTypeFields + "end tell" ); QString name = m_playerAppId + ".playerInfo"; CFNotificationCenterAddObserver( CFNotificationCenterGetDistributedCenter(), this, callback, CFStringCreateWithCString( NULL, name.toStdString().c_str(), kCFStringEncodingASCII ), NULL, CFNotificationSuspensionBehaviorDeliverImmediately ); QMetaObject::invokeMethod( this, "setupCurrentTrack", Qt::QueuedConnection ); } ITunesListener::~ITunesListener() { delete m_connection; } bool ITunesListener::isAppleMusic() { return m_playerAppId == "com.apple.Music"; } void ITunesListener::callback( CFNotificationCenterRef, void* observer, CFStringRef, const void*, CFDictionaryRef info ) { static_cast<ITunesListener*>(observer)->callback( info ); } /******************************************************************************* * code-clarity-class used by callback() */ class ITunesDictionaryHelper { CFDictionaryRef const m_info; // leave here or risk an initialisation bug public: ITunesDictionaryHelper( CFDictionaryRef info ) : m_info( info ), state( getState() ) {} void determineTrackInformation(); QString artist, name, album, path, pid; int duration; ITunesListener::State const state; // *MUST* be after m_info private: template <typename T> T token( CFStringRef t ) { return (T) CFDictionaryGetValue( m_info, t ); } ITunesListener::State getState() { CFStringRef state = token<CFStringRef>( CFSTR("Player State") ); #define compare( x ) if (CFStringCompare( state, CFSTR( #x ), 0 ) == kCFCompareEqualTo) return ITunesListener::x compare( Playing ); compare( Paused ); compare( Stopped ); #undef compare return ITunesListener::Unknown; } }; template <> QString ITunesDictionaryHelper::token<QString>( CFStringRef t ) { CFStringRef s = token<CFStringRef>( t ); return lastfm::CFStringToQString( s ); } template <> int ITunesDictionaryHelper::token<int>( CFStringRef t ) { int i = 0; CFNumberRef n = token<CFNumberRef>( t ); if (n) CFNumberGetValue( n, kCFNumberIntType, &i ); return i; } void ITunesDictionaryHelper::determineTrackInformation() { duration = token<int>( CFSTR("Total Time") ) / 1000; artist = token<QString>( CFSTR("Artist") ); album = token<QString>( CFSTR("Album") ); name = token<QString>( CFSTR("Name") ); pid = QString::number( token<int>( CFSTR("PersistentID") ) ); // Get path decoded - iTunes encodes the file location as URL CFStringRef location = token<CFStringRef>( CFSTR("Location") ); QUrl url = QUrl::fromEncoded( lastfm::CFStringToUtf8( location ) ); path = url.toString().remove( "file://localhost" ); } /******************************************************************************/ static inline QString encodeAmp( QString data ) { return data.replace( '&', "&&" ); } void ITunesListener::callback( CFDictionaryRef info ) { if ( !m_connection ) emit newConnection( m_connection = new ITunesConnection(isAppleMusic()) ); ITunesDictionaryHelper dict( info ); State const previousState = m_state; m_state = dict.state; if ( m_state == Paused ) m_connection->pause(); else if ( m_state == Stopped ) m_connection->stop(); else if ( m_state == Playing ) { QString output = m_currentTrackScript.exec(); qDebug() << output; if ( !output.isEmpty() ) { QTextStream s( &output, QIODevice::ReadOnly | QIODevice::Text ); QString artist = s.readLine(); QString albumArtist = s.readLine(); QString album = s.readLine(); QString track = s.readLine(); QString duration = s.readLine(); QString path = s.readLine(); QString pid = s.readLine(); bool podcast = s.readLine() == "true"; QString videoKind = s.readLine(); bool video = videoKind != "none" && videoKind != "music video"; // if the track is restarted it has the same pid if ( m_previousPid == pid && previousState == Paused ) m_connection->resume(); else { MutableTrack t; t.setArtist( artist ); t.setAlbumArtist( albumArtist ); t.setTitle( track ); t.setAlbum( album ); t.setDuration( duration.toInt() ); t.setUrl( QUrl::fromLocalFile( path ) ); t.setPodcast( podcast ); t.setVideo( video ); m_connection->start( t ); } m_previousPid = pid; } else m_connection->stop(); } else qWarning() << "Unknown state."; } QString //static ITunesListener::getPlayerAppId() { const char* code = "try\n" "tell me to get application id \"com.apple.Music\"\n" "return \"com.apple.Music\"\n" "on error\n" "return \"com.apple.iTunes\"\n" "end try\n"; return AppleScript( code ).exec(); } bool //static ITunesListener::isPlaying() { QString code = "tell application id \"" + m_playerAppId + "\" to if running then return player state is playing"; return AppleScript( code ).exec() == "true"; } void ITunesListener::setupCurrentTrack() { if ( !isPlaying() ) return; QString output = m_currentTrackScript.exec(); qDebug() << output; if ( !output.isEmpty() ) { QTextStream s( &output, QIODevice::ReadOnly | QIODevice::Text ); QString artist = s.readLine(); QString albumArtist = s.readLine(); QString album = s.readLine(); QString track = s.readLine(); QString duration = s.readLine(); QString path = s.readLine(); QString persistentID = s.readLine(); bool podcast = s.readLine() == "true"; QString videoKind = s.readLine(); bool video = videoKind != "none" && videoKind != "music video"; m_previousPid = persistentID; MutableTrack t; t.setArtist( artist ); t.setAlbumArtist( albumArtist ); t.setTitle( track ); t.setAlbum( album ); t.setDuration( duration.toInt() ); t.setUrl( QUrl::fromLocalFile( path ) ); t.setPodcast( podcast ); t.setVideo( video ); if ( !m_connection ) emit newConnection( m_connection = new ITunesConnection(isAppleMusic()) ); m_connection->start( t ); } } ================================================ FILE: lib/listener/mac/ITunesListener.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef ITUNES_SCRIPT_H #define ITUNES_SCRIPT_H #include <QThread> #include <CoreFoundation/CoreFoundation.h> #include "lib/unicorn/mac/AppleScript.h" /** @author Max Howell <max@last.fm> */ class ITunesListener : public QObject { Q_OBJECT public: ITunesListener( QObject* parent ); ~ITunesListener(); enum State { Unknown = -1, Playing, Paused, Stopped }; static QString getPlayerAppId(); signals: void newConnection( class PlayerConnection* ); private: /** iTunes notification center callback */ static void callback( CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef ); void callback( CFDictionaryRef ); bool isPlaying(); bool isAppleMusic(); private slots: void setupCurrentTrack(); private: State m_state; QString m_previousPid; QString m_playerAppId; struct ITunesConnection* m_connection; AppleScript m_currentTrackScript; }; #endif ================================================ FILE: lib/listener/mac/SpotifyListener.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SPOTIFY_LISTENER_H #define SPOTIFY_LISTENER_H #include <QObject> #include <QPointer> #include <lastfm/Track.h> class SpotifyListenerMac : public QObject { Q_OBJECT public: SpotifyListenerMac( QObject* parent ); ~SpotifyListenerMac(); signals: void newConnection( class PlayerConnection* ); private slots: void loop(); private: QString m_lastPlayerState; lastfm::Track m_lastTrack; QPointer<struct SpotifyConnection> m_connection; }; #endif ================================================ FILE: lib/listener/mac/SpotifyListener.mm ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <AppKit/NSWorkspace.h> #include <lastfm/Track.h> #include <lastfm/misc.h> #include "lib/unicorn/mac/AppleScript.h" #include "../PlayerConnection.h" #include "SpotifyListener.h" struct SpotifyConnection : public PlayerConnection { SpotifyConnection() : PlayerConnection( "spt", "Spotify" ) {} void start( const Track& t ) { MutableTrack mt( t ); mt.setSource( Track::Player ); mt.setExtra( "playerId", this->id() ); mt.setExtra( "playerName", name() ); mt.stamp(); handleCommand( CommandStart, t ); } void pause() { handleCommand( CommandPause ); } void resume() { handleCommand( CommandResume ); } void stop() { handleCommand( CommandStop ); } }; SpotifyListenerMac::SpotifyListenerMac( QObject* parent ) :QObject( parent ) { if ( [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:@"com.spotify.client"] != nil ) { QTimer* timer = new QTimer( this ); timer->start( 1000 ); connect( timer, SIGNAL(timeout()), SLOT(loop())); } } SpotifyListenerMac::~SpotifyListenerMac() { delete m_connection; } void SpotifyListenerMac::loop() { static AppleScript playerStateScript( "tell application id \"com.spotify.client\" to if running then return player state" ); QString playerState = playerStateScript.exec(); if ( playerState == "playing" || playerState == "stopped" || playerState == "paused" ) { if ( !m_connection ) emit newConnection( m_connection = new SpotifyConnection ); static AppleScript titleScript( "tell application id \"com.spotify.client\" to return name of current track" ); static AppleScript albumScript( "tell application id \"com.spotify.client\" to return album of current track" ); static AppleScript artistScript( "tell application id \"com.spotify.client\" to return artist of current track" ); static AppleScript albumArtistScript( "tell application id \"com.spotify.client\" to return album artist of current track" ); static AppleScript durationScript( "tell application id \"com.spotify.client\" to return duration of current track" ); if ( playerState == "playing" ) { lastfm::MutableTrack t; t.setTitle( titleScript.exec() ); t.setAlbum( albumScript.exec() ); t.setArtist( artistScript.exec() ); t.setAlbumArtist( albumArtistScript.exec() ); t.setDuration( durationScript.exec().toInt() ); if ( t != m_lastTrack ) { if ( t.album().title().startsWith( "http://" ) || t.album().title().startsWith( "https://" ) || t.album().title().startsWith( "spotify:" ) ) { // this is an advert so don't try to display metadata // and stop any currently playing track if ( m_lastPlayerState == "playing" || m_lastPlayerState == "paused" ) m_connection->stop(); } else { m_connection->start( t ); } } else if ( m_lastPlayerState == "paused" ) m_connection->resume(); m_lastTrack = t; } else if ( m_lastPlayerState != playerState ) { if ( playerState == "stopped" ) { m_connection->stop(); m_lastTrack = Track(); } else if ( playerState == "paused" ) m_connection->pause(); } } else { if ( m_lastPlayerState == "playing" || m_lastPlayerState == "paused" ) m_connection->stop(); delete m_connection; } m_lastPlayerState = playerState; } ================================================ FILE: lib/listener/mpris2/Mpris2Listener.cpp ================================================ /* Copyright (C) 2013 John Stamp <jstamp@mehercule.net> This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "Mpris2Listener.h" #include "Mpris2Service.h" #include "../PlayerConnection.h" #include <QDBusConnectionInterface> #include <QRegExp> class Mpris2Connection : public PlayerConnection { public: Mpris2Connection() : PlayerConnection( "mpris2", "" ) {} void start( const Track& t ) { MutableTrack mt( t ); mt.setSource( Track::Player ); mt.setExtra( "playerId", id() ); mt.stamp(); handleCommand( CommandStart, t ); } void pause() { handleCommand( CommandPause ); } void resume() { handleCommand( CommandResume ); } void stop() { handleCommand( CommandStop ); } }; Mpris2Listener::Mpris2Listener( QObject * parent ) : QObject( parent ) { stop(); QStringList serviceNames = QDBusConnection::sessionBus().interface()->registeredServiceNames().value().filter(QLatin1String("org.mpris.MediaPlayer2.")); foreach( const QString& name, serviceNames ) { if ( !name.startsWith( "org.mpris.MediaPlayer2.") || name.endsWith( "lastfm-scrobbler" ) ) continue; addService( name ); } connect( QDBusConnection::sessionBus().interface(), SIGNAL(serviceOwnerChanged(QString,QString,QString)), this, SLOT(onServiceOwnerChanged(QString,QString,QString))); } Mpris2Listener::~Mpris2Listener() { } void Mpris2Listener::createConnection() { if ( !m_connection ) emit newConnection( m_connection = new Mpris2Connection ); } void Mpris2Listener::addService( const QString& name ) { if ( m_services.keys().contains( name ) ) return; Mpris2Service* service = new Mpris2Service( name, this ); m_services.insert( name, service ); connect( service, SIGNAL( stateChanged( const QString& )), this, SLOT(onChangedState( const QString& )) ); } void Mpris2Listener::stop() { if ( m_connection ) m_connection->stop(); m_currentService = QString(); m_lastPlayerState = "Stopped"; m_lastTrack = lastfm::Track(); } void Mpris2Listener::removeService(const QString& name ) { if ( m_services.keys().contains( name ) ) { if ( name == m_currentService ) stop(); Mpris2Service * service = m_services.take( name ); delete service; } } void Mpris2Listener::onServiceOwnerChanged( const QString& name, const QString& oldOwner, const QString& newOwner ) { if ( !name.startsWith( "org.mpris.MediaPlayer2.") || name.endsWith( "lastfm-scrobbler" ) ) return; if ( oldOwner.isEmpty() && !newOwner.isEmpty() ) addService( name ); else if ( newOwner.isEmpty() ) removeService( name ); } void Mpris2Listener::onChangedState( const QString& state ) { Mpris2Service* service = static_cast<Mpris2Service*>(sender()); // Once a player enters the Playing state, we keep monitoring it until it's // Stopped. That way players running simultaneously don't fight for the // scrobbler's attention. if ( m_currentService.isEmpty() && state == "Playing" ) m_currentService = service->name(); if ( service->name() == m_currentService ) { if ( state == "Playing" ) { MutableTrack t; t.setTitle( service->title() ); t.setArtist( service->artist() ); if ( service->length() == 0 ) t.setDuration( 320 ); else t.setDuration( service->length() ); if ( !service->url().isEmpty() ) t.setUrl( service->url() ); // Let PlaybackControlsWidget & ProgressBar know the friendly app name t.setExtra( "playerName", service->identity() ); // We also want to add info from the optional DesktopEntry property // and a hint from the service name itself. This will help // PlaybackControlsWidget find the right application icon to use. if ( !service->desktopEntry().isEmpty() ) t.setExtra( "desktopEntry", service->desktopEntry() ); QRegExp rx( "^org\\.mpris\\.MediaPlayer2\\.([^.]+)" ); if ( rx.indexIn( service->name() ) >= 0 ) t.setExtra( "serviceName", rx.cap( 1 ) ); if ( m_lastPlayerState == "Paused" && t == m_lastTrack ) { m_connection->resume(); } else { m_connection->start( t ); m_lastTrack = t; } } else if ( state == "Paused" ) { m_connection->pause(); } else if ( state == "Stopped" ) { stop(); } m_lastPlayerState = state; } } ================================================ FILE: lib/listener/mpris2/Mpris2Listener.h ================================================ /* Copyright (C) 2013 John Stamp <jstamp@mehercule.net> This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef MPRIS2LISTENER_H #define MPRIS2LISTENER_H #include <QHash> #include <QPointer> #include <lastfm/Track.h> class Mpris2Service; class Mpris2Connection; class PlayerConnection; class Mpris2Listener : public QObject { Q_OBJECT public: Mpris2Listener( QObject * parent ); ~Mpris2Listener(); void createConnection(); signals: void newConnection( PlayerConnection* ); private: QHash<QString, Mpris2Service*> m_services; QString m_currentService; QPointer<Mpris2Connection> m_connection; lastfm::Track m_lastTrack; QString m_lastPlayerState; void addService( const QString& name ); void removeService( const QString& name ); void stop(); private slots: void onServiceOwnerChanged( const QString& name, const QString& oldOwner, const QString& newOwner ); void onChangedState( const QString& state ); }; #endif ================================================ FILE: lib/listener/mpris2/Mpris2Service.cpp ================================================ /* Copyright (C) 2013 John Stamp <jstamp@mehercule.net> This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "Mpris2Service.h" #include <QDBusInterface> #include <QDBusReply> #include <QDBusMetaType> #define MPRIS2_PATH "/org/mpris/MediaPlayer2" #define MPRIS2_ROOT_IFACE "org.mpris.MediaPlayer2" #define MPRIS2_PLAYER_IFACE "org.mpris.MediaPlayer2.Player" #define DBUS_PROPS_IFACE "org.freedesktop.DBus.Properties" static QVariantMap demarshallMetadata(const QVariant &value) { if ( !value.canConvert<QDBusArgument>() ) return QVariantMap(); QVariantMap metadata; QDBusArgument arg = value.value<QDBusArgument>(); arg >> metadata; return metadata; } Mpris2Service::Mpris2Service( const QString& name, QObject * parent ) : QObject( parent ) { m_state = "Stopped"; m_propsIface = new QDBusInterface( name, MPRIS2_PATH, DBUS_PROPS_IFACE, QDBusConnection::sessionBus(), this ); QDBusConnection::sessionBus().connect( name, MPRIS2_PATH, DBUS_PROPS_IFACE, "PropertiesChanged", this, SLOT( propsChanged( QString, QVariantMap, QStringList ) ) ); } Mpris2Service::~Mpris2Service() { } QVariant Mpris2Service::getProp( const QString& interface, const QString& prop ) const { QDBusReply<QDBusVariant> reply = m_propsIface->call( "Get", interface, prop ); if ( !reply.isValid() ) return QVariant(); return reply.value().variant(); } QString Mpris2Service::name() const { return m_propsIface->service(); } QString Mpris2Service::identity() const { return getProp( MPRIS2_ROOT_IFACE, "Identity" ).toString(); } QString Mpris2Service::desktopEntry() const { return getProp( MPRIS2_ROOT_IFACE, "DesktopEntry" ).toString(); } QString Mpris2Service::artist() const { QStringList entries = m_metadata.value( "xesam:artist" ).toStringList(); if ( !entries.isEmpty() ) return entries.first(); return QString(); } QString Mpris2Service::title() const { return m_metadata.value( "xesam:title" ).toString(); } uint Mpris2Service::length() const { return m_metadata.value( "mpris:length" ).toLongLong() / 1000000; } QString Mpris2Service::url() const { return m_metadata.value( "xesam:url" ).toString(); } void Mpris2Service::propsChanged( const QString& str, const QVariantMap& changedProperties, const QStringList& invalidatedProperties ) { if ( changedProperties.contains( "Metadata" ) ) m_metadata = demarshallMetadata( changedProperties.value( "Metadata" ) ); else if ( invalidatedProperties.contains( "Metadata" ) ) m_metadata = demarshallMetadata( getProp( MPRIS2_PLAYER_IFACE, "Metadata" ) ); if ( changedProperties.contains( "PlaybackStatus" ) && changedProperties.value( "PlaybackStatus" ).toString() != m_state ) { m_state = changedProperties.value( "PlaybackStatus" ).toString(); emit stateChanged( m_state ); } else if ( invalidatedProperties.contains( "PlaybackStatus" ) ) { m_state = getProp( MPRIS2_PLAYER_IFACE, "PlaybackStatus" ).toString(); } } ================================================ FILE: lib/listener/mpris2/Mpris2Service.h ================================================ /* Copyright (C) 2013 John Stamp <jstamp@mehercule.net> This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef MPRIS2SERVICE_H #define MPRIS2SERVICE_H #include <QString> #include <QVariantMap> class QDBusInterface; class Mpris2Service : public QObject { Q_OBJECT public: Mpris2Service( const QString& name, QObject * parent ); ~Mpris2Service(); QString name() const; QString identity() const; QString desktopEntry() const; QString artist() const; QString title() const; uint length() const; QString url() const; signals: void stateChanged( const QString& ); private: QString m_state; QDBusInterface* m_propsIface; QVariantMap m_metadata; QVariant getProp( const QString& interface, const QString& prop ) const; private slots: void propsChanged( const QString& interface, const QVariantMap& changedProperties, const QStringList& invalidatedProperties ); }; #endif ================================================ FILE: lib/listener/tests/TestPlayerCommandParser.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QtTest> #include "PlayerCommandParser.h" class TestPlayerCommandParser : public QObject { Q_OBJECT private slots: void testStart(); void testStop(); void testResume(); void testPause(); void testBootstrap(); void testEmptyLine(); void testMissingArgument(); void testInvalidCommand(); void testDuplicatedArgument(); void testUnicode(); }; void TestPlayerCommandParser::testStart() { PlayerCommandParser pcp ( "START c=testapp" "&a=Test Artist" "&t=Test Title" "&b=Test Album" "&l=100" "&p=/home/tester/test.mp3" ); QCOMPARE( pcp.command(), CommandStart ); QCOMPARE( pcp.playerId(), QString( "testapp" ) ); QCOMPARE( pcp.track().artist(), Artist( "Test Artist" ) ); QCOMPARE( pcp.track().title(), QString( "Test Title" ) ); QCOMPARE( pcp.track().album().title(), QString( "Test Album" ) ); QCOMPARE( pcp.track().duration(), 100u ); QCOMPARE( pcp.track().url().path(), QString( "/home/tester/test.mp3" ) ); } void TestPlayerCommandParser::testStop() { PlayerCommandParser pcp ( "STOP c=testapp" ); QCOMPARE( pcp.command(), CommandStop ); QCOMPARE( pcp.playerId(), QString( "testapp" ) ); } void TestPlayerCommandParser::testResume() { PlayerCommandParser pcp ( "RESUME c=testapp" ); QCOMPARE( pcp.command(), CommandResume ); QCOMPARE( pcp.playerId(), QString( "testapp" ) ); } void TestPlayerCommandParser::testPause() { PlayerCommandParser pcp ( "PAUSE c=testapp" ); QCOMPARE( pcp.command(), CommandPause ); QCOMPARE( pcp.playerId(), QString( "testapp" ) ); } void TestPlayerCommandParser::testBootstrap() { PlayerCommandParser pcp ( "BOOTSTRAP c=testapp&u=TestUser" ); QCOMPARE( pcp.command(), CommandBootstrap ); QCOMPARE( pcp.playerId(), QString( "testapp" ) ); QCOMPARE( pcp.username(), QString( "TestUser" ) ); } void TestPlayerCommandParser::testEmptyLine() { // The PlayerCommandParser should throw an exception on empty lines try { PlayerCommandParser pcp ( "" ); QFAIL( "PlayerCommandParser did not throw an exception on an empty line." ); } catch ( PlayerCommandParser::Exception e ) { // Success } } void TestPlayerCommandParser::testMissingArgument() { // The PlayerCommandParser should throw an exception when arguments are missing try { PlayerCommandParser pcp ( "START c=testap" ); QFAIL( "PlayerCommandParser did not throw an exception when arguments are missing." ); } catch ( PlayerCommandParser::Exception e ) { // Success } } void TestPlayerCommandParser::testInvalidCommand() { // The PlayerCommandParser should throw an exception when passed a invalid command try { PlayerCommandParser pcp ( "SUPERSTART c=testap" ); QFAIL( "PlayerCommandParser did not throw an exception when passed a invalid command." ); } catch ( PlayerCommandParser::Exception e ) { // Success } } void TestPlayerCommandParser::testDuplicatedArgument() { // The PlayerCommandParser should throw an exception when arguments are duplicated try { PlayerCommandParser pcp ( "START c=testap&c=testapp2" ); QFAIL( "PlayerCommandParser did not throw an exception when arguments are duplicated." ); } catch ( PlayerCommandParser::Exception e ) { // Success } } void TestPlayerCommandParser::testUnicode() { PlayerCommandParser pcp( "START c=testapp" "&a=佐橋俊彦" "&t=対峙" "&b=TV Animation ジパング original Soundtrack" "&l=123" "&p=/home/tester/15 対峙.mp3" ); QCOMPARE( pcp.command(), CommandStart ); QCOMPARE( pcp.playerId(), QString( "testapp" ) ); QCOMPARE( pcp.track().artist(), Artist( "佐橋俊彦" ) ); QCOMPARE( pcp.track().title(), QString( "対峙" ) ); QCOMPARE( pcp.track().album().title(), QString( "TV Animation ジパング original Soundtrack" ) ); QCOMPARE( pcp.track().duration(), 123u ); QCOMPARE( pcp.track().url().path(), QString( "/home/tester/15 対峙.mp3" ) ); } QTEST_APPLESS_MAIN(TestPlayerCommandParser) #include "TestPlayerCommandParser.moc" ================================================ FILE: lib/listener/tests/test_liblistener.pro ================================================ TEMPLATE = app QT = testlib CONFIG += core types INCLUDEPATH += .. include( admin/include.qmake ) DEFINES += LASTFM_COLLAPSE_NAMESPACE SOURCES = TestPlayerCommandParser.cpp ../PlayerCommandParser.cpp ================================================ FILE: lib/listener/win/NamedPipeServer.cpp ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QDebug> #include <windows.h> #include <stdio.h> #include <tchar.h> #include <strsafe.h> #include "NamedPipeServer.h" #include "common/c++/win/scrobSubPipeName.cpp" #define CONNECTING_STATE 0 #define READING_STATE 1 #define WRITING_STATE 2 #define INSTANCES 8 #define PIPE_TIMEOUT 5000 #define BUFSIZE 4096 typedef struct { OVERLAPPED oOverlap; HANDLE hPipeInst; TCHAR chRequest[BUFSIZE]; DWORD cbRead; TCHAR chReply[BUFSIZE]; DWORD cbToWrite; DWORD dwState; BOOL fPendingIO; } PIPEINST, *LPPIPEINST; PIPEINST Pipe[INSTANCES]; HANDLE hEvents[INSTANCES]; VOID DisconnectAndReconnect(DWORD i); BOOL ConnectToNewClient(HANDLE hPipe, LPOVERLAPPED lpo); NamedPipeServer::NamedPipeServer(QObject *parent) :QThread(parent) { } void NamedPipeServer::run() { DWORD i, dwWait, cbRet, dwErr; BOOL fSuccess; QByteArray response; std::string s; DWORD r = scrobSubPipeName( &s ); if (r != 0) throw std::runtime_error( formatWin32Error( r ) ); QString const name = QString::fromStdString( s ); // The initial loop creates several instances of a named pipe // along with an event object for each instance. An // overlapped ConnectNamedPipe operation is started for // each instance. for (i = 0; i < INSTANCES; i++) { // Create an event object for this instance. hEvents[i] = CreateEvent( NULL, // default security attribute TRUE, // manual-reset event TRUE, // initial state = signaled NULL); // unnamed event object if (hEvents[i] == NULL) { printf("CreateEvent failed with %d.\n", GetLastError()); return; } Pipe[i].oOverlap.hEvent = hEvents[i]; Pipe[i].hPipeInst = CreateNamedPipe( (const wchar_t *)name.utf16(), // pipe name PIPE_ACCESS_DUPLEX | // read/write access FILE_FLAG_OVERLAPPED, // overlapped mode PIPE_TYPE_MESSAGE | // message-type pipe PIPE_READMODE_MESSAGE | // message-read mode PIPE_WAIT, // blocking mode INSTANCES, // number of instances BUFSIZE*sizeof(TCHAR), // output buffer size BUFSIZE*sizeof(TCHAR), // input buffer size PIPE_TIMEOUT, // client time-out NULL); // default security attributes if (Pipe[i].hPipeInst == INVALID_HANDLE_VALUE) { printf("CreateNamedPipe failed with %d.\n", GetLastError()); return; } // Call the subroutine to connect to the new client Pipe[i].fPendingIO = ConnectToNewClient( Pipe[i].hPipeInst, &Pipe[i].oOverlap); Pipe[i].dwState = Pipe[i].fPendingIO ? CONNECTING_STATE : // still connecting READING_STATE; // ready to read } while (1) { // Wait for the event object to be signaled, indicating // completion of an overlapped read, write, or // connect operation. dwWait = WaitForMultipleObjects( INSTANCES, // number of event objects hEvents, // array of event objects FALSE, // does not wait for all INFINITE); // waits indefinitely // dwWait shows which pipe completed the operation. i = dwWait - WAIT_OBJECT_0; // determines which pipe if (i < 0 || i > (INSTANCES - 1)) { printf("Index out of range.\n"); return; } // Get the result if the operation was pending. if (Pipe[i].fPendingIO) { fSuccess = GetOverlappedResult( Pipe[i].hPipeInst, // handle to pipe &Pipe[i].oOverlap, // OVERLAPPED structure &cbRet, // bytes transferred FALSE); // do not wait switch (Pipe[i].dwState) { // Pending connect operation case CONNECTING_STATE: if (! fSuccess) { printf("Error %d.\n", GetLastError()); return; } Pipe[i].dwState = READING_STATE; break; // Pending read operation case READING_STATE: if (! fSuccess || cbRet == 0) { DisconnectAndReconnect(i); continue; } Pipe[i].cbRead = cbRet; Pipe[i].dwState = WRITING_STATE; break; // Pending write operation case WRITING_STATE: if (! fSuccess || cbRet != Pipe[i].cbToWrite) { DisconnectAndReconnect(i); continue; } Pipe[i].dwState = READING_STATE; break; default: { printf("Invalid pipe state.\n"); return; } } } // The pipe state determines which operation to do next. switch (Pipe[i].dwState) { // READING_STATE: // The pipe instance is connected to the client // and is ready to read a request from the client. case READING_STATE: fSuccess = ReadFile( Pipe[i].hPipeInst, Pipe[i].chRequest, BUFSIZE*sizeof(TCHAR), &Pipe[i].cbRead, &Pipe[i].oOverlap); // The read operation completed successfully. if (fSuccess && Pipe[i].cbRead != 0) { Pipe[i].fPendingIO = FALSE; Pipe[i].dwState = WRITING_STATE; continue; } // The read operation is still pending. dwErr = GetLastError(); if (! fSuccess && (dwErr == ERROR_IO_PENDING)) { Pipe[i].fPendingIO = TRUE; continue; } // An error occurred; disconnect from the client. DisconnectAndReconnect(i); break; // WRITING_STATE: // The request was successfully read from the client. // Get the reply data and write it to the client. case WRITING_STATE: response = emit lineReady( QString::fromUtf8( (char*)Pipe[i].chRequest, Pipe[i].cbRead ) ).toUtf8(); StringCchCopy( Pipe[i].chReply, BUFSIZE, (LPCTSTR)response.data() ); Pipe[i].cbToWrite = response.size(); fSuccess = WriteFile( Pipe[i].hPipeInst, Pipe[i].chReply, Pipe[i].cbToWrite, &cbRet, &Pipe[i].oOverlap); // The write operation completed successfully. if (fSuccess && cbRet == Pipe[i].cbToWrite) { Pipe[i].fPendingIO = FALSE; Pipe[i].dwState = READING_STATE; continue; } // The write operation is still pending. dwErr = GetLastError(); if (! fSuccess && (dwErr == ERROR_IO_PENDING)) { Pipe[i].fPendingIO = TRUE; continue; } // An error occurred; disconnect from the client. DisconnectAndReconnect(i); break; default: { printf("Invalid pipe state.\n"); return; } } } } // DisconnectAndReconnect(DWORD) // This function is called when an error occurs or when the client // closes its handle to the pipe. Disconnect from this client, then // call ConnectNamedPipe to wait for another client to connect. VOID DisconnectAndReconnect(DWORD i) { // Disconnect the pipe instance. if (! DisconnectNamedPipe(Pipe[i].hPipeInst) ) { printf("DisconnectNamedPipe failed with %d.\n", GetLastError()); } // Call a subroutine to connect to the new client. Pipe[i].fPendingIO = ConnectToNewClient( Pipe[i].hPipeInst, &Pipe[i].oOverlap); Pipe[i].dwState = Pipe[i].fPendingIO ? CONNECTING_STATE : // still connecting READING_STATE; // ready to read } // ConnectToNewClient(HANDLE, LPOVERLAPPED) // This function is called to start an overlapped connect operation. // It returns TRUE if an operation is pending or FALSE if the // connection has been completed. BOOL ConnectToNewClient(HANDLE hPipe, LPOVERLAPPED lpo) { BOOL fConnected, fPendingIO = FALSE; // Start an overlapped connection for this pipe instance. fConnected = ConnectNamedPipe(hPipe, lpo); // Overlapped ConnectNamedPipe should return zero. if (fConnected) { printf("ConnectNamedPipe failed with %d.\n", GetLastError()); return 0; } switch (GetLastError()) { // The overlapped connection in progress. case ERROR_IO_PENDING: fPendingIO = TRUE; break; // Client is already connected, so signal an event. case ERROR_PIPE_CONNECTED: if (SetEvent(lpo->hEvent)) break; // If an error occurs during the connect operation... default: { printf("ConnectNamedPipe failed with %d.\n", GetLastError()); return 0; } } return fPendingIO; } ================================================ FILE: lib/listener/win/NamedPipeServer.h ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef NAMEDPIPESERVER_H #define NAMEDPIPESERVER_H #include <QThread> class NamedPipeServer : public QThread { Q_OBJECT public: explicit NamedPipeServer( QObject* parent = 0 ); private: void run(); signals: QString lineReady( const QString& line ); }; #endif // NAMEDPIPESERVER_H ================================================ FILE: lib/listener/win/SpotifyListener.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <lastfm/Track.h> #include "SpotifyListener.h" #include "../PlayerConnection.h" #include <lastfm/misc.h> class SpotifyConnection : public PlayerConnection { public: SpotifyConnection() : PlayerConnection( "spt", "Spotify" ) {} void start( const Track& t ) { MutableTrack mt( t ); mt.setSource( Track::Player ); mt.setExtra( "playerId", id() ); mt.setExtra( "playerName", name() ); mt.stamp(); handleCommand( CommandStart, t ); } void pause() { handleCommand( CommandPause ); } void resume() { handleCommand( CommandResume ); } void stop() { handleCommand( CommandStop ); } }; SpotifyListenerWin::SpotifyListenerWin( QObject* parent ) :QObject( parent ), m_lastPlayerState( Stopped ) { QTimer* timer = new QTimer( this ); timer->start( 1000 ); connect( timer, SIGNAL(timeout()), SLOT(loop())); } SpotifyListenerWin::~SpotifyListenerWin() { delete m_connection; } BOOL CALLBACK SpotifyListenerWin::callback( HWND hWnd, LPARAM lParam ) { SpotifyListenerWin* self = reinterpret_cast<SpotifyListenerWin*>(lParam); /// Getting the filename of the app would be more robust //wchar_t filename[256]; //GetWindowModuleFileName( hWnd, filename, 256 ); //if ( QString::fromWCharArray( filename ).contains( "spotify.exe" ) ) { wchar_t title[256]; GetWindowText( hWnd, title, 256 ); QString windowTitle = QString::fromWCharArray( title ); if ( windowTitle.startsWith( "Spotify" ) ) { QRegExp re( QString( "^Spotify - (.+) %1 (.+)").arg( QChar( 0x2013 ) ), Qt::CaseSensitive, QRegExp::RegExp2 ); // A window that follows the Spotify track playing format takes priority if ( re.indexIn( windowTitle ) == 0 ) self->m_windowTitle = windowTitle; if ( self->m_windowTitle.isEmpty() ) self->m_windowTitle = windowTitle; } } return TRUE; } void SpotifyListenerWin::loop() { m_windowTitle.clear(); EnumWindows( SpotifyListenerWin::callback, reinterpret_cast<LPARAM>(this) ); // stopped = // paused = Spotify // playing = Spotify - Allo, Darlin' Kiss Your Lips State playerState = Stopped; if ( m_windowTitle.startsWith( "Spotify" ) ) { if ( !m_connection ) emit newConnection( m_connection = new SpotifyConnection ); QRegExp re( QString( "^Spotify - (.+) %1 (.+)").arg( QChar( 0x2013 ) ), Qt::CaseSensitive, QRegExp::RegExp2 ); playerState = re.indexIn( m_windowTitle ) == 0 ? Playing : Paused; if ( m_lastPlayerState != playerState ) { if ( playerState == Stopped ) { m_connection->stop(); m_lastTrack = Track(); } else if ( playerState == Paused ) { if ( m_lastPlayerState == Playing ) m_connection->pause(); } else if ( playerState == Playing ) { MutableTrack t; t.setTitle( re.capturedTexts().at( 2 ) ); t.setArtist( re.capturedTexts().at( 1 ) ); // we don't know the duration, but we don't display it so just guess t.setDuration( 320 ); if ( m_lastPlayerState == Paused && t == m_lastTrack ) m_connection->resume(); else m_connection->start( t ); m_lastTrack = t; } } else if ( playerState == Playing ) { // when going from one song to the next we stay in the play state MutableTrack t; t.setTitle( re.capturedTexts().at( 2 ) ); t.setArtist( re.capturedTexts().at( 1 ) ); // we don't know the duration, but we don't display it so just guess t.setDuration( 320 ); if ( t != m_lastTrack ) m_connection->start( t ); m_lastTrack = t; } } else { if ( m_lastPlayerState == Playing || m_lastPlayerState == Paused ) m_connection->stop(); delete m_connection; } m_lastPlayerState = playerState; } //void //SpotifyListener::blah() //{ // static AppleScript playerStateScript( "tell application \"Spotify\" to if running then return player state" ); // QString playerState = playerStateScript.exec(); // if ( !playerState.isEmpty() ) // { // if ( !m_connection ) // emit newConnection( m_connection = new SpotifyConnection ); // static AppleScript titleScript( "tell application \"Spotify\" to return name of current track" ); // static AppleScript albumScript( "tell application \"Spotify\" to return album of current track" ); // static AppleScript artistScript( "tell application \"Spotify\" to return artist of current track" ); // static AppleScript durationScript( "tell application \"Spotify\" to return duration of current track" ); // if ( playerState == "playing" ) // { // lastfm::MutableTrack t; // t.setTitle( titleScript.exec() ); // t.setAlbum( albumScript.exec() ); // t.setArtist( artistScript.exec() ); // t.setDuration( durationScript.exec().toInt() ); // if ( t != m_lastTrack ) // m_connection->start( t ); // m_lastTrack = t; // } // else if ( m_lastPlayerState != playerState ) // { // if ( playerState == "stopped" ) // { // m_connection->stop(); // m_lastTrack = Track(); // } // else if ( playerState == "paused" ) // m_connection->pause(); // else if ( playerState == "playing" ) // { // lastfm::MutableTrack t; // t.setTitle( titleScript.exec() ); // t.setAlbum( albumScript.exec() ); // t.setArtist( artistScript.exec() ); // t.setDuration( durationScript.exec().toInt() ); // if ( m_lastPlayerState == "paused" && t == m_lastTrack ) // m_connection->resume(); // else // m_connection->start( t ); // m_lastTrack = t; // } // } // } // else // { // if ( m_lastPlayerState == "playing" || m_lastPlayerState == "paused" ) // m_connection->stop(); // delete m_connection; // } // m_lastPlayerState = playerState; //} ================================================ FILE: lib/listener/win/SpotifyListener.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SPOTIFY_LISTENER_H #define SPOTIFY_LISTENER_H #include <Windows.h> #include <QObject> #include <QPointer> #include <lastfm/Track.h> #include "lib/DllExportMacro.h" class LISTENER_DLLEXPORT SpotifyListenerWin : public QObject { Q_OBJECT private: enum State { Playing, Paused, Stopped }; public: SpotifyListenerWin( QObject* parent ); ~SpotifyListenerWin(); signals: void newConnection( class PlayerConnection* ); private slots: void loop(); private: static BOOL CALLBACK callback( HWND hWnd, LPARAM lParam ); private: State m_lastPlayerState; lastfm::Track m_lastTrack; QPointer<class SpotifyConnection> m_connection; QString m_windowTitle; }; #endif ================================================ FILE: lib/logger/logger.pro ================================================ TARGET = logger TEMPLATE = lib QT -= gui CONFIG += dll unix:!mac { CONFIG -= dll CONFIG += staticlib QMAKE_DISTCLEAN += -f ../../_bin/liblogger.a } include( ../../admin/include.qmake ) DEFINES += _LOGGER_DLLEXPORT LASTFM_COLLAPSE_NAMESPACE SOURCES += $$ROOT_DIR/common/c++/Logger.cpp HEADERS += $$ROOT_DIR/common/c++/Logger.h ================================================ FILE: lib/unicorn/AnimatedPushButton.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef ANIMATED_PUSHBUTTON_H #define ANIMATED_PUSHBUTTON_H #include <QPushButton> #include <QMovie> #include <QDebug> #include <iostream> class AnimatedPushButton : public QPushButton { public: AnimatedPushButton( QMovie* movie, const QString& text, QWidget* parent = 0 ) :QPushButton( text, parent ), m_movie( movie ) { m_movie->start(); setIcon(QIcon( m_movie->currentPixmap())); m_movie->stop(); } private: QMovie* m_movie; }; #endif //ANIMATED_PUSHBUTTON_H ================================================ FILE: lib/unicorn/AnimatedStatusBar.cpp ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "AnimatedStatusBar.h" #include <QTimeLine> #include "qtwin.h" #include <QDebug> AnimatedStatusBar::AnimatedStatusBar( QWidget* parent ) :QStatusBar( parent ) { m_timeline = new QTimeLine( 50, this ); m_timeline->setUpdateInterval( 20 ); m_timeline->setCurveShape( QTimeLine::EaseInCurve ); connect( m_timeline, SIGNAL( frameChanged(int)), SLOT(onFrameChanged(int))); connect( m_timeline, SIGNAL( finished()), SLOT(onFinished())); } void AnimatedStatusBar::showAnimated() { if( isVisible() && height() > 0 ) return; window()->setMinimumHeight( window()->height()); m_timeline->setFrameRange( 0, sizeHint().height()); m_timeline->setDirection( QTimeLine::Forward ); setFixedHeight( 0 ); show(); m_windowHeight = window()->height(); m_timeline->start(); } void AnimatedStatusBar::hideAnimated() { if( !isVisible() || height() == 0 ) return; window()->setMaximumHeight( window()->height()); m_timeline->setFrameRange( 0, sizeHint().height()); m_timeline->setDirection( QTimeLine::Backward ); m_windowHeight = window()->height(); m_timeline->start(); } void AnimatedStatusBar::onFinished() { if( m_timeline->direction() == QTimeLine::Backward ) { hide(); } setMaximumHeight( QWIDGETSIZE_MAX ); setMinimumHeight( 0 ); window()->setMinimumHeight( 0 ); window()->setMaximumHeight( QWIDGETSIZE_MAX ); } void AnimatedStatusBar::onFrameChanged( int f ) { setFixedHeight( f ); if( m_timeline->direction() == QTimeLine::Forward ) window()->resize( window()->width(), m_windowHeight + f ); else window()->resize( window()->width(), m_windowHeight - (sizeHint().height() - f ) ); } ================================================ FILE: lib/unicorn/AnimatedStatusBar.h ================================================ /* Copyright 2009 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef ANIMATED_STATUS_BAR_H #define ANIMATED_STATUS_BAR_H #include <lib/DllExportMacro.h> #include <QStatusBar> class UNICORN_DLLEXPORT AnimatedStatusBar: public QStatusBar { Q_OBJECT public: AnimatedStatusBar( QWidget* parent = 0 ); public slots: void showAnimated(); void hideAnimated(); private slots: void onFrameChanged(int); void onFinished(); private: class QTimeLine* m_timeline; int m_windowHeight; }; #endif //ANIMATED_STATUS_BAR_H ================================================ FILE: lib/unicorn/CrashReporter/CrashReporter.cpp ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QCoreApplication> #ifndef Q_OS_MAC #ifdef Q_OS_WIN #include <client/windows/handler/exception_handler.h> #include <client/windows/sender/crash_report_sender.h> #endif #include <lastfm/misc.h> #include "CrashReporter.h" #ifdef Q_OS_WIN32 bool FilterCallback(void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion) { return true; } bool MinidumpCallback(const wchar_t* dump_path, const wchar_t* minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool succeeded) { google_breakpad::CrashReportSender* crashReportSender = new google_breakpad::CrashReportSender( QString( "" ).toStdWString() ); std::map<std::wstring, std::wstring> parameters; std::wstring yeah; google_breakpad::ReportResult result = crashReportSender->SendCrashReport( QString( "http://oops.last.fm/report/add" ).toStdWString(), parameters, dump_path, &yeah); return true; } #endif unicorn::CrashReporter::CrashReporter(QObject *parent) : QObject(parent) { #ifdef Q_OS_WIN32 google_breakpad::ExceptionHandler* handler = new google_breakpad::ExceptionHandler( lastfm::dir::logs().absolutePath().toStdWString(), FilterCallback, MinidumpCallback, this, google_breakpad::ExceptionHandler::HANDLER_ALL); #endif } unicorn::CrashReporter::~CrashReporter() { } #endif ================================================ FILE: lib/unicorn/CrashReporter/CrashReporter.h ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef CRASHREPORTER_H #define CRASHREPORTER_H #include <QObject> #include "lib/DllExportMacro.h" namespace unicorn { class UNICORN_DLLEXPORT CrashReporter : public QObject { Q_OBJECT public: explicit CrashReporter(QObject *parent = 0); ~CrashReporter(); }; } #endif // CRASHREPORTER_H ================================================ FILE: lib/unicorn/CrashReporter/CrashReporter_mac.mm ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <Cocoa/Cocoa.h> #include <Breakpad/Breakpad.h> #include "CrashReporter.h" BreakpadRef gBreakpad; unicorn::CrashReporter::CrashReporter(QObject *parent) : QObject(parent) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; gBreakpad = 0; NSDictionary *plist = [[NSBundle mainBundle] infoDictionary]; if (plist) { // Note: version 1.0.0.4 of the framework changed the type of the argument // from CFDictionaryRef to NSDictionary * on the next line: gBreakpad = BreakpadCreate(plist); } [pool release]; } unicorn::CrashReporter::~CrashReporter() { BreakpadRelease( gBreakpad ); } ================================================ FILE: lib/unicorn/DesktopServices.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QDesktopServices> #include <QCoreApplication> #include <QUrl> #include <lastfm/UrlBuilder.h> #include "DesktopServices.h" unicorn::DesktopServices::DesktopServices() { } void unicorn::DesktopServices::openUrl( QUrl url ) { if ( lastfm::UrlBuilder::isHost( url ) ) { url.addQueryItem( "utm_source", "last.fm" ); url.addQueryItem( "utm_medium", "application" ); url.addQueryItem( "utm_campaign", "last.fm_desktop_application" ); url.addQueryItem( "utm_content", QCoreApplication::applicationVersion() ); #ifdef WIN32 url.addQueryItem( "utm_term", "WIN" ); #elif __APPLE__ url.addQueryItem( "utm_term", "OSX" ); #elif defined (Q_WS_X11) url.addQueryItem( "utm_term", "X11" ); #endif } QDesktopServices::openUrl( url ); } ================================================ FILE: lib/unicorn/DesktopServices.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef DESKTOPSERVICES_H #define DESKTOPSERVICES_H #include <QUrl> #include "lib/DllExportMacro.h" namespace unicorn { class UNICORN_DLLEXPORT DesktopServices { public: DesktopServices(); static void openUrl( QUrl url ); }; } #endif // DESKTOPSERVICES_H ================================================ FILE: lib/unicorn/LoginProcess.cpp ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by William Viana Soares and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "LoginProcess.h" #include "QMessageBoxBuilder.h" #include "UnicornApplication.h" #include <lastfm/ws.h> #include <lastfm/misc.h> #include <lastfm/XmlQuery.h> #include <QApplication> #include <QDesktopServices> #include <QUrl> #include <QTcpServer> #include <QTcpSocket> #include "lib/unicorn/dialogs/ProxyDialog.h" #include "lib/unicorn/DesktopServices.h" #ifdef WIN32 #include <windows.h> #endif namespace unicorn { LoginProcess::LoginProcess( QObject* parent ) : QObject( parent ) { } LoginProcess::~LoginProcess() { } void LoginProcess::authenticate() { connect( unicorn::Session::getToken(), SIGNAL( finished() ), SLOT( onGotToken() ) ); } void LoginProcess::onGotToken() { lastfm::XmlQuery lfm; QUrl authUrl; if ( lfm.parse( static_cast<QNetworkReply*>( sender() ) ) ) { m_token = lfm["token"].text(); authUrl.setUrl( "http://www.last.fm/api/auth/" ); authUrl.addQueryItem( "api_key", lastfm::ws::ApiKey ); authUrl.addQueryItem( "token", m_token ); QDesktopServices::openUrl( authUrl ); } else { handleError( lfm ); } emit authUrlChanged( authUrl.toString() ); } void LoginProcess::getSession() { connect( unicorn::Session::getSession( m_token ), SIGNAL( finished() ), SLOT( onGotSession() ) ); } void LoginProcess::onGotSession() { lastfm::XmlQuery lfm; if ( lfm.parse( static_cast<QNetworkReply*>( sender() ) ) ) { QString username = lfm["session"]["name"].text(); QString sessionKey = lfm["session"]["key"].text(); unicorn::Application* app = qobject_cast<unicorn::Application*>( qApp ); app->changeSession( username, sessionKey ); } else { handleError( lfm ); } } void LoginProcess::handleError( const lastfm::XmlQuery& lfm ) { qWarning() << lfm.parseError().message() << lfm.parseError().enumValue(); if ( lfm.parseError().enumValue() == lastfm::ws::MalformedResponse ) { // ask for proxy settings unicorn::ProxyDialog proxy; proxy.exec(); } else { QString errorText; if ( lfm.parseError().enumValue() == lastfm::ws::UnknownError ) errorText = tr( "There was a network error: %1" ).arg( QString::number( static_cast<QNetworkReply*>( sender() )->error() ) ); else if ( lfm.parseError().enumValue() == lastfm::ws::TokenNotAuthorised ) errorText = tr( "You have not authorised this application" ); else errorText = lfm.parseError().message().trimmed() + ": " + QString::number( lfm.parseError().enumValue() ); QMessageBoxBuilder( 0 ) .setIcon( QMessageBox::Critical ) .setTitle( tr("Authentication Error") ) .setText( errorText ) .exec(); } } }// namespace unicorn ================================================ FILE: lib/unicorn/LoginProcess.h ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by William Viana Soares and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef LOGIN_PROCESS_H #define LOGIN_PROCESS_H #include <QObject> #include <QPointer> #include <lastfm/ws.h> #include "UnicornSession.h" #include "lib/DllExportMacro.h" namespace unicorn { /** * This class encapsulates the whole login process. * * Call the authenticate function to start the login process * and connect to the gotSession signal to be notified when * the process finishes. */ class UNICORN_DLLEXPORT LoginProcess : public QObject { Q_OBJECT public: LoginProcess( QObject* parent = 0 ); ~LoginProcess(); private: void handleError( const lastfm::XmlQuery& lfm ); signals: void authUrlChanged( const QString& authUrl ); public slots: void authenticate(); void getSession(); private slots: void onGotToken(); void onGotSession(); private: QString m_token; }; }// namespace unicorn #endif // LOGIN_PROCESS_H ================================================ FILE: lib/unicorn/PlayBus/Bus.cpp ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "Bus.h" unicorn::Bus::Bus( QObject* parent ) :PlayBus( "unicorn", parent ) { connect( this, SIGNAL( message(QByteArray)), SLOT( onMessage(QByteArray))); connect( this, SIGNAL( queryRequest( QString, QByteArray )), SLOT( onQuery( QString, QByteArray ))); } bool unicorn::Bus::isWizardRunning() { return sendQuery( "WIZARDRUNNING" ) == "TRUE"; } QMap<QString, QString> unicorn::Bus::getSessionData() { QByteArray ba = sendQuery( "SESSION" ); QMap<QString, QString> sessionData; if( ba.length() > 0 ) { QDataStream ds( ba ); ds >> sessionData; } return sessionData; } void unicorn::Bus::announceSessionChange( unicorn::Session& s ) { qDebug() << "Session change, let's spread the message through the bus!"; QByteArray ba; QDataStream ds( &ba, QIODevice::WriteOnly | QIODevice::Truncate ); ds << QString( "SESSIONCHANGED" ); ds << ( s ); sendMessage( ba ); } void unicorn::Bus::onMessage( const QByteArray& message ) { qDebug() << "Message received"; qDebug() << "Message: " << message; QDataStream ds( message ); QString stringMessage; ds >> stringMessage; qDebug() << stringMessage; if( stringMessage == "SESSIONCHANGED" ) { qDebug() << "and it's a session change alert"; unicorn::Session* session = new unicorn::Session( ds ); emit sessionChanged( *session ); delete session; } else if( message.startsWith( "LOVED=" )) { QByteArray sessionData = message.right( message.size() - 6); emit lovedStateChanged( sessionData == "true" ); } } void unicorn::Bus::onQuery( const QString& uuid, const QByteArray& message ) { qDebug() << "query received" << message; if( message == "WIZARDRUNNING" ) emit wizardRunningQuery( uuid ); else if( message == "SESSION" ) emit sessionQuery( uuid ); } ================================================ FILE: lib/unicorn/PlayBus/Bus.h ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #pragma once #include "../UnicornSession.h" #include "PlayBus.h" namespace unicorn { class Bus : public PlayBus { Q_OBJECT public: Bus( QObject* parent = 0 ); bool isWizardRunning(); QMap<QString, QString> getSessionData(); void announceSessionChange( unicorn::Session& s ); private slots: void onMessage( const QByteArray& message ); void onQuery( const QString& uuid, const QByteArray& message ); signals: void wizardRunningQuery( const QString& uuid ); void sessionQuery( const QString& uuid ); void sessionChanged( const unicorn::Session& s ); void rosterUpdated(); void lovedStateChanged(bool loved); }; } ================================================ FILE: lib/unicorn/PlayBus/PlayBus.cpp ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "PlayBus.h" unicorn::PlayBus::PlayBus( const QString& name, QObject* parent ) :QObject( parent ), m_busName( name ), m_queryMessages( false ) #ifdef Q_OS_WIN ,m_sharedMemory( name ) #endif { #ifndef Q_OS_WIN m_busName = lastfm::dir::runtimeData().absolutePath() + "/" + m_busName; #endif connect( &m_server, SIGNAL( newConnection()), SLOT( onIncomingConnection())); } unicorn::PlayBus::~PlayBus() { m_server.close(); #ifdef Q_OS_WIN m_sharedMemory.detach(); #endif } void unicorn::PlayBus::board() { reinit(); } const QByteArray& unicorn::PlayBus::lastMessage() const { return m_lastMessage; } void unicorn::PlayBus::setQueryMessages( bool b ) { m_queryMessages = b; } QByteArray unicorn::PlayBus::sendQuery( QByteArray request, int timeout ) { QUuid quuid = QUuid::createUuid(); QString uuid = quuid; m_dispatchedQueries << uuid; sendMessage( (uuid + " " + request).toUtf8() ); SignalBlocker blocker( this, SIGNAL( queryRequest(QString,QByteArray)), timeout ); while( blocker.start() ) if( m_lastQueryUuid == uuid ) return m_lastQueryResponse; return QByteArray(); } void unicorn::PlayBus::sendQueryResponse( QString uuid, QByteArray message ) { sendMessage( ( uuid + " " ).toUtf8() + message ); } /** send the message around the bus */ void unicorn::PlayBus::sendMessage( const QByteArray& msg ) { foreach( QLocalSocket* socket, m_sockets ) { socket->write( msg + "\n" ); socket->flush(); } } void unicorn::PlayBus::onSocketConnected() { //WIN32 supports GUIDs which almost certainly will be unique according to Qt. #ifndef WIN32 //throw-away uuid generation to initialize random seed QUuid::createUuid(); #endif QLocalSocket* socket = qobject_cast<QLocalSocket*>(sender()); addSocket( socket ); } void unicorn::PlayBus::reinit() { if( m_server.isListening()) return; foreach( QLocalSocket* socket, m_sockets ) { m_sockets.removeAll(socket); socket->disconnect(); socket->close(); socket->deleteLater(); } #ifndef Q_OS_WIN if( m_server.listen( m_busName )) { return; } #else if( m_sharedMemory.create( 1 )) { emit message( "Now Listening" ); m_server.listen( m_busName ); return; } else { emit message( "Connecting to server" ); } #endif m_server.close(); QLocalSocket* socket = new QLocalSocket( this ); connect( socket, SIGNAL( connected()), SLOT( onSocketConnected())); connect( socket, SIGNAL( disconnected()), SLOT( reinit())); connect( socket, SIGNAL( error(QLocalSocket::LocalSocketError)), SLOT( onError(QLocalSocket::LocalSocketError))); socket->connectToServer( m_busName ); } void unicorn::PlayBus::onError( const QLocalSocket::LocalSocketError& e ) { #ifdef Q_OS_WIN Q_UNUSED( e ) #endif if( e == QLocalSocket::ConnectionRefusedError ) { QFile::remove( m_busName ); } QLocalSocket* s = qobject_cast<QLocalSocket*>(sender()); s->close(); s->deleteLater(); QTimer::singleShot( 10, this, SLOT(reinit())); } void unicorn::PlayBus::onIncomingConnection() { QLocalSocket* socket = 0; while( (socket = m_server.nextPendingConnection()) ) { socket->setParent( this ); addSocket( socket ); } } void unicorn::PlayBus::processCommand( const QByteArray& data ) { m_lastMessage = data; QRegExp queryRE("^(\\{.{8}-.{4}-.{4}-.{4}-.{12}\\}) .*$"); if( queryRE.indexIn( data ) > -1 ) { QString uuid = queryRE.cap(1); // ignore any queries that have already been seen if( !m_dispatchedQueries.contains( uuid ) && m_servicedQueries.contains( uuid )) { if( m_queryMessages ) emit message( data ); return; } m_lastQueryUuid = uuid; m_lastQueryResponse = data.mid( 39 ); //remove uuid and seperator m_servicedQueries << m_lastQueryUuid; emit queryRequest( m_lastQueryUuid, m_lastQueryResponse); if( !m_queryMessages ) return; } emit message( data ); } void unicorn::PlayBus::onSocketData() { QLocalSocket* socket = qobject_cast<QLocalSocket*>(sender()); while( socket->canReadLine() ) { QByteArray data = socket->readLine(); data.chop( 1 ); // remove trailing /n foreach( QLocalSocket* aSocket, m_sockets ) { if( aSocket == socket ) continue; aSocket->write( data + "\n" ); aSocket->flush(); } processCommand( data ); } } void unicorn::PlayBus::onSocketDestroyed( QObject* o ) { QLocalSocket* s = dynamic_cast<QLocalSocket*>(o); if( !s ) return; s->blockSignals(true); m_sockets.removeAll( s ); } void unicorn::PlayBus::addSocket( QLocalSocket* socket ) { connect( socket, SIGNAL(readyRead()), SLOT(onSocketData())); QSignalMapper* mapper = new QSignalMapper(socket); mapper->setMapping( socket, socket ); connect( mapper, SIGNAL(mapped(QObject*)), SLOT(onSocketDestroyed(QObject*))); connect( socket, SIGNAL(disconnected()), mapper, SLOT( map())); connect( socket, SIGNAL(destroyed(QObject*)), SLOT(onSocketDestroyed(QObject*))); m_sockets << socket; } const QStringList unicorn::PlayBus::nodeList( const QString& data ) { QString str( data ); str = str.mid( 7 ); // remove NODES [ str.chop( 1 ); // remove ] return str.split( "," ); } ================================================ FILE: lib/unicorn/PlayBus/PlayBus.h ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #pragma once #include <QLocalServer> #include <QLocalSocket> #include <QList> #include <QString> #include <QDir> #include <QFile> #include <QUuid> #include <QCoreApplication> #include <QDateTime> #include <QSignalMapper> #include "../SignalBlocker.h" #ifdef Q_OS_WIN #include <QSharedMemory> #else #include <lastfm/misc.h> #endif namespace unicorn { /** @author Jono Cole <jono@last.fm> * @brief An interprocess message bus. * * This is a client side implemented bus, meaning that * if no bus is running then a client will become the master node. * * If the master node drops it's connection then another client node * will become the master and all other clients will connect to this. * * This bus is designed to be run on a single host using local servers. * the server mediation is handled at the kernel level by exploiting the * logic that allows only one process to listen on a named pipe. * * This code will not work across distributed hosts as no master node * mediation is carried out. This could be extended by using a known * mediation algorithm. */ class PlayBus : public QObject { Q_OBJECT public: /** @param name The name of the bus to connect to or create */ PlayBus( const QString& name, QObject* parent = 0 ); ~PlayBus(); void board(); const QByteArray& lastMessage() const; void setQueryMessages( bool b ); public slots: QByteArray sendQuery( QByteArray request, int timeout = 200 ); void sendQueryResponse( QString uuid, QByteArray message ); /** send the message around the bus */ void sendMessage( const QByteArray& msg ); signals: /** a new message has been received from the bus */ void message( const QByteArray& msg ); void queryRequest( const QString& uuid, const QByteArray& message); private slots: void onSocketConnected(); void reinit(); void onError( const QLocalSocket::LocalSocketError& e ); void onIncomingConnection(); void processCommand( const QByteArray& data ); void onSocketData(); void onSocketDestroyed( QObject* o ); private: void addSocket( QLocalSocket* socket ); const QStringList nodeList( const QString& data ); QString m_busName; QLocalServer m_server; QList<QLocalSocket*> m_sockets; QByteArray m_lastMessage; QList<QString> m_dispatchedQueries; QList<QString> m_servicedQueries; QByteArray m_lastQueryResponse; QString m_lastQueryUuid; bool m_queryMessages; #ifdef Q_OS_WIN QSharedMemory m_sharedMemory; #endif }; } ================================================ FILE: lib/unicorn/QMessageBoxBuilder.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "QMessageBoxBuilder.h" #include <QApplication> #include <QGridLayout> #include <QLabel> unicorn::MessageDialog::MessageDialog( QWidget* parent ) :QDialog( parent ? ( parent->isVisible() ? parent : 0 ) : 0 ), m_clickedButton( QDialogButtonBox::NoButton ) { setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); setSizeGripEnabled( false ); //setFixedWidth( 500 ); setWindowFlags( Qt::Dialog ); QGridLayout* l = new QGridLayout( this ); l->addWidget( icon = new QLabel, 0, 0, 3, 1, Qt::AlignTop | Qt::AlignLeft ); l->addWidget( label = new QLabel, 0, 1, 1, 1, Qt::AlignTop ); label->setAlignment( Qt::AlignLeft | Qt::AlignTop ); l->addWidget( informativeText = new QLabel, 1, 1, 1, 1, Qt::AlignTop ); label->setWordWrap( true ); informativeText->setWordWrap( true ); informativeText->setAttribute( Qt::WA_MacSmallSize ); QFont f = label->font(); f.setBold( true ); label->setFont( f ); l->setColumnStretch( 1, 1 ); l->setRowStretch( 3, 1 ); l->addWidget( checkbox = new QCheckBox( tr( "Don't ask this again" )), 2, 1, 1, 1 ); checkbox->setVisible( false ); checkbox->setFocusPolicy( Qt::NoFocus ); l->addWidget( buttons = new QDialogButtonBox, 3, 0, 1, 2 ); buttons->setStandardButtons( QDialogButtonBox::Ok ); connect( buttons, SIGNAL( clicked(QAbstractButton*)), SLOT( onButtonClicked(QAbstractButton*))); buttons->setFocus( Qt::ActiveWindowFocusReason ); } void unicorn::MessageDialog::onButtonClicked(QAbstractButton* b) { m_clickedButton = int(buttons->standardButton( b )); switch( buttons->buttonRole( b )) { case QDialogButtonBox::AcceptRole: case QDialogButtonBox::YesRole: accept(); break; case QDialogButtonBox::RejectRole: case QDialogButtonBox::NoRole: reject(); break; default: break; } } QMessageBoxBuilder& QMessageBoxBuilder::setTitle( const QString& title ) { #ifdef Q_WS_MAC box.setText( title + "\t\t\t" ); #else box.setWindowTitle( title ); #endif return *this; } QMessageBoxBuilder& QMessageBoxBuilder::setText( const QString& text ) { #ifdef Q_WS_MAC box.setInformativeText( text ); #else box.setText( text ); #endif return *this; } int QMessageBoxBuilder::exec( bool* dontAskAgain ) { QApplication::setOverrideCursor( Qt::ArrowCursor ); box.activateWindow(); box.exec(); QApplication::restoreOverrideCursor(); if( dontAskAgain ) *dontAskAgain = box.isDontShowAgainChecked(); return box.clickedButton(); } ================================================ FILE: lib/unicorn/QMessageBoxBuilder.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef MESSAGE_BOX_BUILDER_H #define MESSAGE_BOX_BUILDER_H #include <lib/DllExportMacro.h> #include <QtGui/QMessageBox> #include <QAbstractButton> #include <QPushButton> #include <QDialogButtonBox> #include <QBoxLayout> #include <QCheckBox> #include <QStyle> #include <QDebug> #include <QLabel> #include <QDebug> namespace unicorn { class UNICORN_DLLEXPORT MessageDialog : public QDialog { Q_OBJECT public: MessageDialog( QWidget* parent ); void setStandardButtons( QMessageBox::StandardButtons b ) { buttons->setStandardButtons( QDialogButtonBox::StandardButtons(int(b) )); } void setIcon( QMessageBox::Icon x ) { QIcon pm; switch( x ) { case QMessageBox::NoIcon: break; case QMessageBox::Information: pm = style()->standardIcon( QStyle::SP_MessageBoxInformation ); break; case QMessageBox::Warning: pm = style()->standardIcon( QStyle::SP_MessageBoxWarning ); break; case QMessageBox::Critical: pm = style()->standardIcon( QStyle::SP_MessageBoxCritical ); break; case QMessageBox::Question: pm = style()->standardIcon( QStyle::SP_MessageBoxQuestion ); break; } icon->setPixmap( pm.pixmap( 32, 32 ) ); } QAbstractButton* button( QMessageBox::StandardButton b ) { QAbstractButton* ret = buttons->button( QDialogButtonBox::StandardButton(int(b) )); return ret; } void addButton( QAbstractButton* b, QMessageBox::ButtonRole r ) { buttons->addButton( b, QDialogButtonBox::ButtonRole(int(r))); } void setText( const QString& t ) { label->setText( t ); } void setInformativeText( const QString& t ) { informativeText->setText( t ); } void setCheckBox( bool b ) { checkbox->setVisible( b ); } bool isDontShowAgainChecked() const { return checkbox->isChecked(); } int clickedButton() const { return m_clickedButton; } private slots: void onButtonClicked(class QAbstractButton*); protected: QLabel *icon, *label, *informativeText; QDialogButtonBox* buttons; QCheckBox* checkbox; int m_clickedButton; }; } class UNICORN_DLLEXPORT QMessageBoxBuilder { unicorn::MessageDialog box; public: /** Try not to use 0! */ QMessageBoxBuilder( QWidget* parent ) : box( parent ) {} QMessageBoxBuilder& setTitle( const QString& x ); QMessageBoxBuilder& setText( const QString& x ); /** the default is Information */ QMessageBoxBuilder& setIcon( QMessageBox::Icon x ) { box.setIcon( x ); return *this; } /** the default is a single OK button */ QMessageBoxBuilder& setButtons( QMessageBox::StandardButtons buttons ) { box.setStandardButtons( buttons ); return *this; } QMessageBoxBuilder& setButtonText( QMessageBox::StandardButton aButton, const QString& text ) { box.button( aButton )->setText( text ); return *this; } QMessageBoxBuilder& dontAskAgain(){ box.setCheckBox( true ); return *this; } QMessageBoxBuilder& addButton( QAbstractButton* b, QMessageBox::ButtonRole r ){ box.addButton( b, r ); return *this; } int exec( bool* dontAskAgain = 0 ); QMessageBoxBuilder& sheet() { #ifdef Q_WS_MAC if( box.parentWidget()) box.setWindowFlags( Qt::Sheet | ( box.windowFlags() & ~Qt::Drawer ) ); #endif return *this; } }; #endif // MESSAGE_BOX_BUILDER_H ================================================ FILE: lib/unicorn/ScrobblesModel.cpp ================================================ /* Copyright 2011-2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QColor> #include <QDebug> #include <lastfm/ScrobbleCache.h> #include "ScrobblesModel.h" ScrobblesModel::Scrobble::Scrobble( const lastfm::Track track ) :m_track( track ), m_scrobblingEnabled( true ) { bool ok; int count = m_track.extra( "playCount" ).toInt( &ok ); m_originalPlayCount = ok ? count : 1; } lastfm::Track ScrobblesModel::Scrobble::track() const { return m_track; } QString ScrobblesModel::Scrobble::title() const { return m_track.title(); } QString ScrobblesModel::Scrobble::artist() const { return m_track.artist(); } QString ScrobblesModel::Scrobble::album() const { return m_track.album(); } QDateTime ScrobblesModel::Scrobble::timestamp() const { return m_track.timestamp(); } bool ScrobblesModel::Scrobble::isLoved() const { return m_track.isLoved(); } bool ScrobblesModel::Scrobble::isScrobblingEnabled() const { return m_scrobblingEnabled; } void ScrobblesModel::Scrobble::setEnableScrobbling( bool allow ) { m_scrobblingEnabled = allow; } QVariant ScrobblesModel::Scrobble::attribute( int index ) const { switch( index ) { case ScrobblesModel::Artist: return artist(); case ScrobblesModel::Title: return title(); case ScrobblesModel::Album: return album(); case ScrobblesModel::TimeStamp: return timestamp(); case ScrobblesModel::Plays: return track().extra( "playCount" ).toInt(); case ScrobblesModel::Loved: return isLoved(); default: break; } return QVariant(); } int ScrobblesModel::Scrobble::originalPlayCount() const { return m_originalPlayCount; } bool ScrobblesModel::Scrobble::operator<( const Scrobble& that ) const { return this->m_track < that.m_track; } ScrobblesModel::ScrobblesModel( QObject* parent ) : QAbstractTableModel( parent ), m_readOnly( false ) { m_headerTitles.append( tr( "Artist" ) ); m_headerTitles.append( tr( "Title" ) ); m_headerTitles.append( tr( "Album" ) ); m_headerTitles.append( tr( "Plays" ) ); m_headerTitles.append( tr( "Last Played" ) ); m_headerTitles.append( tr( "Loved" ) ); } void ScrobblesModel::addTracks( const QList<lastfm::Track>& tracks ) { beginInsertRows( QModelIndex(), m_scrobbleList.count(), m_scrobbleList.count() + tracks.count() - 1 ); foreach( lastfm::Track t, tracks ) m_scrobbleList.append( t ); endInsertRows(); } void ScrobblesModel::setReadOnly() { m_readOnly = true; } int ScrobblesModel::rowCount( const QModelIndex& /*parent*/ ) const { return m_scrobbleList.count(); } int ScrobblesModel::columnCount( const QModelIndex& /*parent*/ ) const { return m_headerTitles.count(); } QVariant ScrobblesModel::data( const QModelIndex& index, int role ) const { if ( !index.isValid() ) return QVariant(); if ( index.row() >= m_scrobbleList.size() ) return QVariant(); if ( index.column() >= m_headerTitles.size() ) return QVariant(); if ( role == Qt::DisplayRole ) return m_scrobbleList.at( index.row() ).attribute( index.column() ); else if ( role == Qt::TextColorRole && !m_readOnly ) { if ( !lastfm::ScrobbleCache::isValid( m_scrobbleList.at( index.row() ).track() ) ) return QColor( Qt::red ); } else if ( role == Qt::ToolTipRole && !m_readOnly ) { Scrobble s = m_scrobbleList.at( index.row() ); lastfm::ScrobbleCache::Invalidity invalidity; if ( !lastfm::ScrobbleCache::isValid( s.track(), &invalidity ) ) { switch ( invalidity ) { case lastfm::ScrobbleCache::TooShort: return tr( "This track is under 30 seconds" ); case lastfm::ScrobbleCache::ArtistNameMissing: return tr( "The artist name is missing" ); case lastfm::ScrobbleCache::TrackNameMissing: return tr( "Invalid track title" ); case lastfm::ScrobbleCache::ArtistInvalid: return tr( "Invalid artist" ); case lastfm::ScrobbleCache::NoTimestamp: return tr( "There is no timestamp" ); case lastfm::ScrobbleCache::FromTheFuture: return tr( "This track is too far in the future" ); case lastfm::ScrobbleCache::FromTheDistantPast: return tr( "This track was played over two weeks ago" ); } } } else if ( role == Qt::CheckStateRole && index.column() == ScrobblesModel::Artist ) { if ( m_readOnly ) return QVariant(); else if ( !lastfm::ScrobbleCache::isValid( m_scrobbleList.at( index.row() ).track() ) ) return Qt::Unchecked; else return m_scrobbleList.at( index.row() ).isScrobblingEnabled() ? Qt::Checked : Qt::Unchecked; } return QVariant(); } QVariant ScrobblesModel::headerData( int section, Qt::Orientation orientation, int role ) const { if ( role != Qt::DisplayRole ) return QVariant(); if ( orientation == Qt::Horizontal ) return m_headerTitles.at( section ); else return QVariant(); } Qt::ItemFlags ScrobblesModel::flags( const QModelIndex& index ) const { if ( !index.isValid() ) return Qt::ItemIsEnabled; if ( !m_readOnly ) { if ( index.column() == ScrobblesModel::Artist ) return QAbstractItemModel::flags( index ) | Qt::ItemIsUserCheckable; if ( index.column() == ScrobblesModel::Plays ) return QAbstractItemModel::flags( index ) | Qt::ItemIsEditable; } return QAbstractItemModel::flags( index ); } bool ScrobblesModel::setData( const QModelIndex& index, const QVariant& value, int role ) { if ( index.isValid() ) { Scrobble s = m_scrobbleList.at( index.row() ); if ( index.column() == ScrobblesModel::Artist && role == Qt::CheckStateRole ) { s.setEnableScrobbling( value.toBool() ); m_scrobbleList.replace( index.row(), s ); emit dataChanged( index, index ); return true; } else if ( index.column() == ScrobblesModel::Plays ) { bool ok; int count = value.toInt( &ok ); if ( ok && count != 0 && count <= s.originalPlayCount() ) { lastfm::MutableTrack( s.track() ).setExtra( "playCount", QString::number( count ) ); emit dataChanged( index, index ); } return true; } } return false; } QList<lastfm::Track> ScrobblesModel::tracksToScrobble() const { QList<lastfm::Track> tracks; for ( int i = 0 ; i < m_scrobbleList.count() ; i ++ ) if ( m_scrobbleList.at( i ).isScrobblingEnabled() ) tracks.append( m_scrobbleList.at( i ).track() ); return tracks; } ================================================ FILE: lib/unicorn/ScrobblesModel.h ================================================ /* Copyright 2011-2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SCROBBLES_MODEL_H #define SCROBBLES_MODEL_H #include <QAbstractTableModel> #include <QStringList> #include <lastfm/Track.h> #include "lib/DllExportMacro.h" class UNICORN_DLLEXPORT ScrobblesModel : public QAbstractTableModel { Q_OBJECT public: enum { Artist, Title, Album, Plays, TimeStamp, Loved }; ScrobblesModel( QObject* parent = 0 ); void addTracks( const QList<lastfm::Track>& tracks ); void setReadOnly(); int rowCount( const QModelIndex& parent = QModelIndex() ) const; int columnCount( const QModelIndex& parent = QModelIndex() ) const; QVariant data( const QModelIndex& index, int role ) const; QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; Qt::ItemFlags flags( const QModelIndex& index ) const; bool setData( const QModelIndex& index, const QVariant& value, int role ); QList<lastfm::Track> tracksToScrobble() const; private: class Scrobble { public: Scrobble( const lastfm::Track track ); lastfm::Track track() const; QString title() const; QString artist() const; QString album() const; QDateTime timestamp() const; bool isLoved() const; bool isScrobblingEnabled() const; void setEnableScrobbling( bool allow ); QVariant attribute( int index ) const; int originalPlayCount() const; bool operator<( const Scrobble& that ) const; private: lastfm::Track m_track; bool m_scrobblingEnabled; int m_originalPlayCount; }; private: QList<Scrobble> m_scrobbleList; QStringList m_headerTitles; bool m_readOnly; }; #endif // SCROBBLES_MODEL_H ================================================ FILE: lib/unicorn/SignalBlocker.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SIGNAL_BLOCKER_H_ #define SIGNAL_BLOCKER_H_ #include "lib/DllExportMacro.h" #include <QTimer> #include <QEventLoop> #include "lib/DllExportMacro.h" /** * @brief Waits for a Qt signal to be emitted before * proceeding with code execution. * * @author Jono Cole<jono@last.fm> */ class UNICORN_DLLEXPORT SignalBlocker : public QEventLoop { Q_OBJECT public: SignalBlocker( QObject* o, const char* signal, int timeout) : m_ret( true ) { m_timer.setSingleShot( true ); connect( o, signal, SLOT( onSignaled())); if( timeout > -1 ) { m_timer.setInterval( timeout ); connect( &m_timer, SIGNAL(timeout()), this, SLOT( onTimeout())); } } /** returns true if the signal fired otherwise false if the timeout fired */ bool start() { if( m_ret == false ) return false; m_timer.start(); m_loop.exec(); return m_ret; } private slots: void onSignaled() { m_ret = true; m_timer.stop(); m_loop.quit(); } void onTimeout() { m_ret = false; m_loop.quit(); } private: QEventLoop m_loop; bool m_ret; QTimer m_timer; }; #endif //SIGNAL_BLOCKER_H_ ================================================ FILE: lib/unicorn/TrackImageFetcher.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "TrackImageFetcher.h" #include <lastfm/Track.h> #include <lastfm/ws.h> #include <lastfm/XmlQuery.h> #include <QDebug> #include <QPixmap> #include <QStringList> TrackImageFetcher::TrackImageFetcher( const Track& track, Track::ImageSize size ) :m_track( track ), m_size( size ) { } void TrackImageFetcher::startAlbum() { if (!album().isNull()) { QUrl imageUrl = url( "album" ); if ( imageUrl.isValid() ) connect( lastfm::nam()->get( QNetworkRequest( imageUrl ) ), SIGNAL(finished()), SLOT(onAlbumImageDownloaded()) ); else connect( album().getInfo(), SIGNAL(finished()), SLOT(onAlbumGotInfo()) ); } else startTrack(); } void TrackImageFetcher::startTrack() { QUrl imageUrl = url( "track" ); if ( imageUrl.isValid() ) connect( lastfm::nam()->get( QNetworkRequest( imageUrl ) ), SIGNAL(finished()), SLOT(onTrackImageDownloaded()) ); else trackGetInfo(); } void TrackImageFetcher::startArtist() { QUrl imageUrl = url( "artist" ); if ( imageUrl.isValid() ) connect( lastfm::nam()->get( QNetworkRequest( imageUrl ) ), SIGNAL(finished()), SLOT(onArtistImageDownloaded()) ); else artistGetInfo(); } void TrackImageFetcher::trackGetInfo() { if (!artist().isNull()) m_track.getInfo( this, "onTrackGotInfo" ); else fail(); } void TrackImageFetcher::artistGetInfo() { if (!artist().isNull()) connect( artist().getInfo(), SIGNAL(finished()), SLOT(onArtistGotInfo()) ); else fail(); } void TrackImageFetcher::onAlbumGotInfo() { if (!downloadImage( (QNetworkReply*)sender(), "album" )) startTrack(); } void TrackImageFetcher::onTrackGotInfo( const QByteArray& data ) { XmlQuery lfm; if ( lfm.parse( data ) ) { lastfm::MutableTrack track( m_track ); track.setImageUrl( Track::MegaImage, lfm["track"]["image size=mega"].text() ); track.setImageUrl( Track::ExtraLargeImage, lfm["track"]["image size=extralarge"].text() ); track.setImageUrl( Track::LargeImage, lfm["track"]["image size=large"].text() ); track.setImageUrl( Track::MediumImage, lfm["track"]["image size=medium"].text() ); track.setImageUrl( Track::SmallImage, lfm["track"]["image size=small"].text() ); if (!downloadImage( 0, "track" )) startArtist(); } else { qWarning() << lfm.parseError().message(); } } void TrackImageFetcher::onArtistGotInfo() { if (!downloadImage( (QNetworkReply*)sender(), "artist" )) fail(); } void TrackImageFetcher::onAlbumImageDownloaded() { QPixmap i; if ( i.loadFromData( qobject_cast<QNetworkReply*>(sender())->readAll() ) ) emit finished( i ); else startTrack(); sender()->deleteLater(); //always deleteLater from slots connected to sender() } void TrackImageFetcher::onTrackImageDownloaded() { QPixmap i; if ( i.loadFromData( qobject_cast<QNetworkReply*>(sender())->readAll() ) ) emit finished( i ); else startArtist(); sender()->deleteLater(); //always deleteLater from slots connected to sender() } void TrackImageFetcher::onArtistImageDownloaded() { QPixmap i; if ( i.loadFromData( qobject_cast<QNetworkReply*>(sender())->readAll() ) ) emit finished( i ); else fail(); sender()->deleteLater(); //always deleteLater from slots connected to sender() } bool TrackImageFetcher::downloadImage( QNetworkReply* reply, const QString& root_node ) { XmlQuery lfm; if ( reply && lfm.parse( reply ) ) { // cache all the sizes if ( root_node == "album" ) { m_track.album().setImageUrl( Track::MegaImage, lfm[root_node]["image size=mega"].text() ); m_track.album().setImageUrl( Track::ExtraLargeImage, lfm[root_node]["image size=extralarge"].text() ); m_track.album().setImageUrl( Track::LargeImage, lfm[root_node]["image size=large"].text() ); m_track.album().setImageUrl( Track::MediumImage, lfm[root_node]["image size=medium"].text() ); m_track.album().setImageUrl( Track::SmallImage, lfm[root_node]["image size=small"].text() ); } else if ( root_node == "artist" ) { m_track.artist().setImageUrl( Track::MegaImage, lfm[root_node]["image size=mega"].text() ); m_track.artist().setImageUrl( Track::ExtraLargeImage, lfm[root_node]["image size=extralarge"].text() ); m_track.artist().setImageUrl( Track::LargeImage, lfm[root_node]["image size=large"].text() ); m_track.artist().setImageUrl( Track::MediumImage, lfm[root_node]["image size=medium"].text() ); m_track.artist().setImageUrl( Track::SmallImage, lfm[root_node]["image size=small"].text() ); } } else { qWarning() << lfm.parseError().message(); } QUrl imageUrl = url( root_node ); qWarning() << root_node << imageUrl; if ( imageUrl.isValid() ) { QNetworkReply* get = lastfm::nam()->get( QNetworkRequest( imageUrl ) ); if ( root_node == "album" ) connect( get, SIGNAL(finished()), SLOT(onAlbumImageDownloaded()) ); else if ( root_node == "track" ) connect( get, SIGNAL(finished()), SLOT(onTrackImageDownloaded()) ); else connect( get, SIGNAL(finished()), SLOT(onArtistImageDownloaded()) ); return true; } return false; } QUrl TrackImageFetcher::url( const QString& root_node ) { QList<Track::ImageSize> sizes; switch ( m_size ) { default: case Track::MegaImage: sizes << Track::MegaImage; case Track::ExtraLargeImage: sizes << Track::ExtraLargeImage; case Track::LargeImage: sizes << Track::LargeImage; case Track::MediumImage: sizes << Track::MediumImage; case Track::SmallImage: sizes << Track::SmallImage; } QUrl imageUrl; Track::ImageSize foundSize; foreach ( Track::ImageSize size, sizes ) { QUrl url; if ( root_node == "album" ) url = m_track.album().imageUrl( size, true ); else if ( root_node == "track" ) url = m_track.imageUrl( size, true ); else if ( root_node == "artist" ) url = m_track.artist().imageUrl( size, true ); // we seem to get a load of album.getInfos where the node exists // but the value is "" and "mega" isn't currently used for album images if ( url.isValid() ) { imageUrl = url; foundSize = size; break; } } return imageUrl; } void TrackImageFetcher::fail() { //emit finished( QPixmap() ); } ================================================ FILE: lib/unicorn/TrackImageFetcher.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef TRACK_IMAGE_FETCHER_H #define TRACK_IMAGE_FETCHER_H #include <QObject> #include <lib/DllExportMacro.h> #include <lastfm/Track.h> /** @author <max@last.fm> * Fetches the album art for an album, via album.getInfo */ class UNICORN_DLLEXPORT TrackImageFetcher : public QObject { Q_OBJECT public: TrackImageFetcher( const Track& track, Track::ImageSize size ); // The order of preference is this // * if we know the album then use the album image // * if no album image found, use track image which could be a guess at the album // * if neither album nor track image found, fallback to an artist image. void startAlbum(); void startTrack(); void startArtist(); Track track() const { return m_track; } private: lastfm::Track m_track; QUrl url( const QString& root_node ); void trackGetInfo(); void artistGetInfo(); void fail(); bool downloadImage( QNetworkReply*, const QString& root_node_name ); Album album() const { return m_track.album(); } Artist artist() const { return m_track.artist(); } signals: void finished( const class QPixmap& ); private slots: void onAlbumGotInfo(); void onTrackGotInfo(const QByteArray &data); void onArtistGotInfo(); void onAlbumImageDownloaded(); void onTrackImageDownloaded(); void onArtistImageDownloaded(); private: lastfm::Track::ImageSize m_size; }; #endif ================================================ FILE: lib/unicorn/UnicornApplication.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifdef __APPLE__ // first to prevent compilation errors with Qt 4.5.0-beta1 #include <Carbon/Carbon.h> #include <ApplicationServices/ApplicationServices.h> #include <QMainWindow> #include "UnicornApplicationDelegate.h" extern void qt_mac_set_menubar_icons( bool ); #elif defined WIN32 #include <windows.h> #include <QAbstractEventDispatcher> #endif #include <QProcess> #include <QDebug> #include <QDir> #include <QFile> #include <QTemporaryFile> #include <QFileInfo> #include <QLocale> #include <QRegExp> #include <QStyle> #include <QTimer> #include <QTranslator> #include <QAction> #include <lastfm/misc.h> #include <lastfm/User.h> #include <lastfm/InternetConnectionMonitor.h> #include <lastfm/ws.h> #include <lastfm/XmlQuery.h> #include "PlayBus/Bus.h" #include "dialogs/LoginContinueDialog.h" #include "dialogs/LoginDialog.h" #include "dialogs/UserManagerDialog.h" #include "LoginProcess.h" #include "QMessageBoxBuilder.h" #include "SignalBlocker.h" #include "UnicornCoreApplication.h" #include "UnicornSettings.h" #include "DesktopServices.h" #include "UnicornApplication.h" unicorn::Application::Application(const QString &id, int &argc, char **argv) throw( StubbornUserException ) : QtSingleApplication( id, argc, argv ), m_logoutAtQuit( false ), m_currentSession( new unicorn::Session ), m_wizardRunning( true ), m_icm( 0 ) { m_bus = new unicorn::Bus( this ); qsrand( QDateTime::currentDateTime().toTime_t() + QCoreApplication::applicationPid() ); #ifdef Q_OS_MAC m_delegate = new unicorn::UnicornApplicationDelegate( this ); #endif } void unicorn::Application::init() { #ifdef Q_OS_MAC addLibraryPath( applicationDirPath() + "/../plugins" ); #elif defined Q_OS_WIN addLibraryPath( applicationDirPath() + "/plugins" ); #endif #ifdef Q_WS_MAC qt_mac_set_menubar_icons( false ); #endif CoreApplication::init(); setupHotKeys(); #ifdef __APPLE__ setGetURLEventHandler(); AEEventHandlerUPP urlHandler = NewAEEventHandlerUPP( appleEventHandler ); AEInstallEventHandler( kInternetEventClass, kAEGetURL, urlHandler, 0, false ); setOpenApplicationEventHandler(); AEEventHandlerUPP openHandler = NewAEEventHandlerUPP( appleEventHandler ); AEInstallEventHandler( kCoreEventClass, kAEReopenApplication, openHandler, 0, false ); #endif #ifdef Q_WS_MAC #define CSS_PATH "/../Resources/" #else #define CSS_PATH "/" #endif refreshStyleSheet(); translate(); m_icm = new lastfm::InternetConnectionMonitor( this ); connect( m_icm, SIGNAL(up(QString)), this, SIGNAL(internetConnectionUp())); connect( m_icm, SIGNAL(down(QString)), this, SIGNAL(internetConnectionDown())); connect( m_bus, SIGNAL(wizardRunningQuery(QString)), SLOT(onWizardRunningQuery(QString))); connect( m_bus, SIGNAL(sessionQuery(QString)), SLOT(onBusSessionQuery(QString))); connect( m_bus, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onBusSessionChanged(unicorn::Session))); connect( m_bus, SIGNAL(lovedStateChanged(bool)), SIGNAL(busLovedStateChanged(bool))); m_bus->board(); #ifdef __APPLE__ setQuitOnLastWindowClosed( false ); #endif } void unicorn::Application::loadStyleSheet( QFile& file ) { file.open( QIODevice::ReadOnly ); m_styleSheet += file.readAll(); setStyleSheet( m_styleSheet ); } void unicorn::Application::initiateLogin( bool ) throw( StubbornUserException ) { Session* newSession = 0; if( m_bus->isWizardRunning() ) { SignalBlocker( m_bus, SIGNAL( sessionChanged( const QMap<QString, QString>& ) ), -1 ).start(); } else { QMap<QString, QString> sessionData = m_bus->getSessionData(); //If the bus returns an empty session data, try to get the session from the last user logged in if ( ! ( sessionData.contains( "sessionKey" ) || sessionData.contains( "username" ) ) ) { sessionData = Session::lastSessionData(); } if ( sessionData.contains( "sessionKey" ) && sessionData.contains( "username" ) ) newSession = new Session( sessionData[ "username" ], sessionData[ "sessionKey" ] ); } if ( newSession ) { changeSession( newSession ); } else { SignalBlocker( m_bus, SIGNAL( sessionChanged( const QMap<QString, QString>& ) ), -1 ).start(); QMap<QString, QString> sessionData = m_bus->getSessionData(); if ( sessionData.contains( "sessionKey" ) && sessionData.contains( "username" ) ) { newSession = new Session( sessionData[ "username" ], sessionData[ "sessionKey" ] ); changeSession( newSession ); } else { throw StubbornUserException(); } } } void unicorn::Application::manageUsers() { UserManagerDialog um; connect( &um, SIGNAL( rosterUpdated()), SIGNAL( rosterUpdated())); if( um.exec() ) { QMap<QString, QString> lastSession = Session::lastSessionData(); if ( lastSession.contains( "username" ) && lastSession.contains( "sessionKey" ) ) { changeSession( lastSession[ "username" ], lastSession[ "sessionKey" ] ); } } } void unicorn::Application::translate() { //Try to load the language set by the user and //if there wasn't any, then use the system language QString iso639 = AppSettings().value( "language", "" ).toString(); if ( iso639.isEmpty() ) iso639 = QLocale::system().name(); qDebug() << "Locale: " << iso639; // set the default locale for the app which will be used by web services QLocale::setDefault( QLocale( iso639 ) ); QString qmExt = iso639.left( 2 ) == "zh" ? iso639 : iso639.left( 2 ); qDebug() << "Language ext: " << qmExt; #ifdef Q_WS_MAC QDir const d = lastfm::dir::bundle().filePath( "Contents/Resources/qm" ); #elif defined(Q_OS_WIN) QDir const d = qApp->applicationDirPath() + "/i18n"; #elif defined(Q_OS_UNIX) QDir const d = QString( PREFIX ) + "/share/lastfm-scrobbler/i18n"; #endif //TODO need a unicorn/core/etc. translation, plus policy of no translations elsewhere or something! QTranslator* t1 = new QTranslator( this ); t1->load( d.filePath( "lastfm_" + qmExt ) ); QTranslator* t2 = new QTranslator( this ); t2->load( d.filePath( "qt_" + qmExt ) ); installTranslator( t1 ); installTranslator( t2 ); #ifdef Q_OS_WIN QTranslator* t3 = new QTranslator( this ); t3->load( d.filePath( "qtsparkle/" + qmExt ) ); installTranslator( t3 ); #endif #ifdef Q_OS_MAC macTranslate( qmExt ); #endif } unicorn::Application::~Application() { } void unicorn::Application::setWizardRunning( bool running ) { m_wizardRunning = running; } void unicorn::Application::onWizardRunningQuery( const QString& uuid ) { qDebug() << "Is the Wizard running?"; if ( m_wizardRunning ) { m_bus->sendQueryResponse( uuid, "TRUE" ); } else { m_bus->sendQueryResponse( uuid, "FALSE" ); } } void unicorn::Application::onBusSessionQuery( const QString& uuid ) { QByteArray ba; QDataStream s( &ba, QIODevice::WriteOnly ); QMap<QString, QString> sessionData; sessionData[ "username" ] = currentSession().user().name(); sessionData[ "sessionKey" ] = currentSession().sessionKey(); s << sessionData; m_bus->sendQueryResponse( uuid, ba ); } void unicorn::Application::onBusSessionChanged( const unicorn::Session& session ) { unicorn::Session* newSession = new unicorn::Session( session.user().name(), session.sessionKey() ); changeSession( newSession, false ); } void unicorn::Application::changeSession( const QString& username, const QString& sessionKey, bool announce ) { return changeSession( new unicorn::Session( username, sessionKey ), announce ); } void unicorn::Application::changeSession( Session* newSession, bool announce ) { if( m_currentSession && !m_wizardRunning && Settings().value( "changeSessionConfirmation", true ).toBool() ) { bool dontAskAgain = false; int result = QMessageBoxBuilder( findMainWindow() ).setTitle( tr( "Changing User" ) ) .setText( tr( "%1 will be logged into the Scrobbler and Last.fm Radio. " "All music will now be scrobbled to this account. Do you want to continue?" ) .arg( newSession->user().name() )) .setIcon( QMessageBox::Information ) .setButtons( QMessageBox::Yes | QMessageBox::Cancel ) .dontAskAgain() .exec( &dontAskAgain ); Settings().setValue( "changeSessionConfirmation", !dontAskAgain ); if( result != QMessageBox::Yes ) return; } disconnect( m_currentSession, SIGNAL(userInfoUpdated(lastfm::User)), this, SIGNAL(gotUserInfo(lastfm::User)) ); connect( newSession, SIGNAL(userInfoUpdated(lastfm::User)), SIGNAL(gotUserInfo(lastfm::User)) ); disconnect( m_currentSession, SIGNAL(sessionChanged(unicorn::Session)), this, SIGNAL(sessionChanged(unicorn::Session)) ); connect( newSession, SIGNAL(sessionChanged(unicorn::Session)), SIGNAL(sessionChanged(unicorn::Session)) ); delete m_currentSession; m_currentSession = newSession; lastfm::ws::Username = m_currentSession->user().name(); lastfm::ws::SessionKey = m_currentSession->sessionKey(); if( announce ) m_bus->announceSessionChange( currentSession() ); emit sessionChanged( currentSession() ); } unicorn::Session& unicorn::Application::currentSession() const { return *m_currentSession; } void unicorn::Application::sendBusLovedStateChanged( bool loved ) { QByteArray message = loved ? "LOVED=true" : "LOVED=false"; m_bus->sendMessage(message); } void unicorn::Application::refreshStyleSheet() { m_styleSheet.clear(); if ( m_cssFileName.isNull() ) { // This is the first time we are loading the stylesheet if( !styleSheet().isEmpty() ) { m_cssFileName = QDir::currentPath() + QUrl( styleSheet() ).toLocalFile(); m_cssDir = QFileInfo( m_cssFileName ).path(); } if( styleSheet().isEmpty()) { #if defined(Q_OS_WIN) || defined(Q_OS_MAC) m_cssFileName = applicationDirPath() + CSS_PATH + applicationName() + ".css"; m_cssDir = applicationDirPath() + CSS_PATH; #else m_cssFileName = QString( PREFIX ) + "/share/lastfm-scrobbler/" + applicationName() + ".css"; m_cssDir = QString( PREFIX ) + "/share/lastfm-scrobbler/"; #endif } } if ( !m_cssFileName.isNull() ) { QFile cssFile( m_cssFileName ); cssFile.open( QIODevice::ReadOnly ); m_styleSheet = cssFile.readAll(); setStyleSheet( m_styleSheet ); cssFile.close(); } QRegExp rx( "@import\\s*\"([^\"]*)\";" ); int pos = 0; while( (pos = rx.indexIn( m_styleSheet, pos )) != -1 ) { QFile f( m_cssDir + "/" + rx.cap( 1 )); loadStyleSheet( f ); pos += rx.matchedLength(); } // QStyle* style = style(); // style->set // setStyle( style ); } void* unicorn::Application::installHotKey( Qt::KeyboardModifiers modifiers, quint32 virtualKey, QObject* receiver, const char* slot ) { qDebug() << "Installing HotKey"; quint32 id = m_hotKeyMap.size() + 1; m_hotKeyMap[id] = QPair<QObject*, const char*>( receiver, slot ); #ifdef __APPLE__ EventHotKeyID hotKeyID; hotKeyID.signature='htk1'; hotKeyID.id=id; UInt32 appleMod=0; if( modifiers.testFlag( Qt::ShiftModifier )) appleMod |= shiftKey; if( modifiers.testFlag( Qt::ControlModifier )) appleMod |= controlKey; if( modifiers.testFlag( Qt::AltModifier )) appleMod |= optionKey; if( modifiers.testFlag( Qt::MetaModifier )) appleMod |= cmdKey; EventHotKeyRef hkRef; RegisterEventHotKey( virtualKey, appleMod, hotKeyID, GetApplicationEventTarget(), 0, &hkRef ); return hkRef; #elif defined WIN32 quint32 winMod = 0; if( modifiers.testFlag( Qt::ShiftModifier )) winMod |= MOD_SHIFT; if( modifiers.testFlag( Qt::ControlModifier )) winMod |= MOD_CONTROL; if( modifiers.testFlag( Qt::AltModifier )) winMod |= MOD_ALT; if( modifiers.testFlag( Qt::MetaModifier )) winMod |= MOD_WIN; RegisterHotKey( NULL, id, winMod, virtualKey); return reinterpret_cast<void*>(id); #endif } void unicorn::Application::unInstallHotKey( void* id ) { #ifdef __APPLE__ UnregisterEventHotKey( (EventHotKeyRef)id ); #elif defined WIN32 UnregisterHotKey( NULL, (int)id ); #endif } void unicorn::Application::setupHotKeys() { #ifdef __APPLE__ EventTypeSpec eventType; eventType.eventClass=kEventClassKeyboard; eventType.eventKind=kEventHotKeyPressed; using unicorn::Application; InstallApplicationEventHandler(&Application::hotkeyEventHandler, 1, &eventType, this, NULL); #elif defined WIN32 QAbstractEventDispatcher::instance()->setEventFilter( winEventFilter ); #endif } void unicorn::Application::onHotKeyEvent(quint32 id) { QPair< QObject*, const char*> method = m_hotKeyMap[id]; QObject* receiver = method.first; const char* slot = method.second; QTimer::singleShot( 0, receiver, slot ); } QMainWindow* unicorn::Application::findMainWindow() { QMainWindow* ret = 0; foreach (QWidget* w, qApp->topLevelWidgets()) if ( (ret = qobject_cast<QMainWindow*>(w)) ) break; return ret; } #ifdef __APPLE__ OSStatus /* static */ unicorn::Application::hotkeyEventHandler( EventHandlerCallRef, EventRef event, void* data ) { unicorn::Application* self = (unicorn::Application*)data; EventHotKeyID hkId; GetEventParameter( event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hkId), NULL, &hkId); self->onHotKeyEvent( hkId.id ); return noErr; } void unicorn::Application::appleEventReceived( const QStringList& messages ) { emit messageReceived( messages ); } pascal OSErr /* static */ #ifdef Q_OS_MAC64 unicorn::Application::appleEventHandler( const AppleEvent* e, AppleEvent*, void* ) #else unicorn::Application::appleEventHandler( const AppleEvent* e, AppleEvent*, long ) #endif { OSType id = typeWildCard; AEGetAttributePtr( e, keyEventIDAttr, typeType, 0, &id, sizeof(id), 0 ); if ( id == kAEQuitApplication ) { qApp->quit(); return noErr; } else if ( id == kAEGetURL ) { OSErr err = noErr; Size actualSize = 0; DescType descType = typeChar; if ( (err = AESizeOfParam( e, keyDirectObject, &descType, &actualSize)) == noErr ) { if ( 0 != actualSize ) { // make a buffer (Qt style) QByteArray bUrl; bUrl.resize(actualSize); err = AEGetParamPtr(e, keyDirectObject, typeChar, 0, bUrl.data(), actualSize, &actualSize); qobject_cast<unicorn::Application*>(qApp)->appleEventReceived( QStringList() << bUrl ); } } return noErr; } else { AEAddressDesc descList; OSErr ret; ret = AEGetParamDesc( e, keyAEPropData, typeAEList, &descList ); long count = 0; ret = AECountItems( &descList, &count ); if( ret != noErr ) count = 0; QStringList args; for( int i = 1; i <= count; ++i ) { AEAddressDesc desc; AEGetNthDesc( &descList, i, typeChar, NULL, &desc ); if( ret == noErr ) { unsigned int size = AEGetDescDataSize( &desc ); char data[size + 1]; data[ size ] = 0; ret = AEGetDescData( &desc, data, size ); args << QString::fromUtf8( data ); } } qobject_cast<unicorn::Application*>(qApp)->appleEventReceived( args ); return noErr; } return unimpErr; } #endif #ifdef WIN32 bool /* static */ unicorn::Application::winEventFilter ( void* message ) { MSG* msg = (MSG*)message; if( msg->message == WM_HOTKEY ) { qDebug() << "Filtered WM_HOTKEY"; qobject_cast<unicorn::Application*>(qApp)->onHotKeyEvent( msg->wParam ); return true; } return false; } #endif void unicorn::Application::restart() { #ifdef Q_OS_WIN QStringList args; args << "--new"; // force a new instance of the app, even if one is running qDebug() << applicationFilePath(); QProcess::startDetached( applicationFilePath(), args ); #endif #ifdef Q_OS_MAC // In Mac OS the full path of aplication binary is: // <base-path>/myApp.app/Contents/MacOS/myApp QStringList args; args << "-b"; args << "fm.last.Scrobbler"; args << "-n"; // open a new instance even if one is running args << "--args"; args << "--new"; // force a new instance of the app, even if one is running qDebug() << QProcess::startDetached( "open", args ); #endif // Terminate current instance: exit(0); // Exit gracefully by terminating the myApp instance } bool unicorn::Application::isInternetConnectionUp() const { return m_icm->isUp(); } #ifdef Q_OS_MAC void unicorn::Application::showDockIcon( bool show ) { m_delegate->showDockIcon( show ); } #endif ================================================ FILE: lib/unicorn/UnicornApplication.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UNICORN_APPLICATION_H #define UNICORN_APPLICATION_H #include "qtsingleapplication/qtsingleapplication.h" #include "common/HideStupidWarnings.h" #include "lib/DllExportMacro.h" #include "UnicornSession.h" #include <QApplication> #include <QDebug> #include <QMainWindow> #include <QPointer> #ifdef Q_OS_MAC #include "UnicornApplicationDelegate.h" #endif #ifdef Q_OS_MAC64 #include <Carbon/Carbon.h> #endif namespace lastfm{ class User; class InternetConnectionMonitor; } class LoginContinueDialog; class QNetworkReply; namespace unicorn { class Bus; /** * Unicorn base Application. * * Child classes should make sure to call the protected function initiateLogin * on their constructor otherwise the app would probably crash, as there will be * no valid user session. */ class UNICORN_DLLEXPORT Application : public QtSingleApplication { Q_OBJECT bool m_logoutAtQuit; public: class StubbornUserException {}; /** will put up the log in dialog if necessary, throwing if the user * cancels, ie. they refuse to log in */ Application(const QString &id, int &argc, char **argv) throw( StubbornUserException ); ~Application(); virtual void init(); /** Will return the actual stylesheet that is loaded if one is specified (on the command-line with -stylesheet or with setStyleSheet(). ) Note. the QApplication styleSheet property will return the path to the css file unlike this method. */ const QString& loadedStyleSheet() const { return m_styleSheet; } Session& currentSession() const; static unicorn::Application* instance(){ return (unicorn::Application*)qApp; } void* installHotKey( Qt::KeyboardModifiers, quint32, QObject* receiver, const char* slot ); void unInstallHotKey( void* id ); bool isInternetConnectionUp() const; void translate(); #ifdef Q_OS_MAC void macTranslate( const QString& lang ); void showDockIcon( bool show ); UnicornApplicationDelegate* delegate() const { return m_delegate; } #endif public slots: void manageUsers(); void changeSession( const QString& username, const QString& sessionKey, bool announce = true ); void sendBusLovedStateChanged(bool loved); void refreshStyleSheet(); void restart(); private: void changeSession( unicorn::Session* newSession, bool announce = true ); void setupHotKeys(); void onHotKeyEvent(quint32 id); void loadStyleSheet( QFile& ); QMainWindow* findMainWindow(); QString m_styleSheet; QPointer<Session> m_currentSession; bool m_wizardRunning; QMap< quint32, QPair<QObject*, const char*> > m_hotKeyMap; QString m_cssDir; QString m_cssFileName; #ifdef Q_OS_MAC QPointer<UnicornApplicationDelegate> m_delegate; void setOpenApplicationEventHandler(); void setGetURLEventHandler(); public: void appleEventReceived( const QStringList& messages ); private: static OSStatus hotkeyEventHandler( EventHandlerCallRef, EventRef, void* ); #ifdef Q_OS_MAC64 static OSErr appleEventHandler( const AppleEvent*, AppleEvent*, void* ); #else static short appleEventHandler( const AppleEvent*, AppleEvent*, long ); #endif #endif #ifdef WIN32 static bool winEventFilter ( void* ); #endif protected: /** * Reimplement this function if you want to control the initial login process. */ virtual void initiateLogin( bool forceWizard = false ) throw( StubbornUserException ); void setWizardRunning( bool running ); QPointer<Bus> m_bus; lastfm::InternetConnectionMonitor* m_icm; private slots: void onWizardRunningQuery( const QString& ); void onBusSessionQuery( const QString& ); void onBusSessionChanged( const unicorn::Session& session ); signals: void gotUserInfo( const lastfm::User& user ); void sessionChanged( const unicorn::Session& session ); void rosterUpdated(); void busLovedStateChanged(bool loved); void internetConnectionUp(); void internetConnectionDown(); }; } #endif ================================================ FILE: lib/unicorn/UnicornApplicationDelegate.h ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UNICORNAPPLICATIONDELEGATE_H #define UNICORNAPPLICATIONDELEGATE_H #include <QPixmap> #include "lib/DllExportMacro.h" namespace unicorn { class UnicornApplicationDelegateCommandObserver { public: virtual QString trackTitle() const = 0; virtual QString artist() const = 0; virtual QString album() const = 0; virtual int duration() = 0; virtual QPixmap artwork() = 0; virtual bool loved() = 0; }; class UNICORN_DLLEXPORT UnicornApplicationDelegate : public QObject { Q_OBJECT public: explicit UnicornApplicationDelegate( QObject *parent = 0 ); void showDockIcon( bool show ); void setCommandObserver( UnicornApplicationDelegateCommandObserver* observer ); void forceInitialize(); void forceApplicationDidFinishLaunching( void* aNotification ); signals: void initialize(); void applicationDidFinishLaunching( void* aNotification ); /* NSNotification */ public: UnicornApplicationDelegateCommandObserver* m_observer; }; } #endif // UNICORNAPPLICATIONDELEGATE_H ================================================ FILE: lib/unicorn/UnicornApplicationDelegate.mm ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "UnicornSettings.h" #include "UnicornApplicationDelegate.h" #include <QWidget> #include <QApplication> #include <QDebug> #import <Cocoa/Cocoa.h> #import <AppKit/NSView.h> #import <Carbon/Carbon.h> // Declare this here ourselves so we can compile on OSX 10.6 // and ProcessTransformToUIElementApplication just won't work enum { ProcessTransformToForegroundApplication = 1, ProcessTransformToBackgroundApplication = 2, /* functional in Mac OS X Barolo and later */ ProcessTransformToUIElementApplication = 4 /* functional in Mac OS X Barolo and later */ }; @interface LFMAppDelegate : NSObject <NSApplicationDelegate> { unicorn::UnicornApplicationDelegate* m_observer; BOOL m_show; } - (LFMAppDelegate*) init:(unicorn::UnicornApplicationDelegate*)observer; @end @implementation LFMAppDelegate - (LFMAppDelegate*) init:(unicorn::UnicornApplicationDelegate*)observer { if ( (self = [super init]) ) { self->m_observer = observer; } return self; } -(void)initialize { m_observer->forceInitialize(); } - (void)applicationWillFinishLaunching:(NSNotification *)aNotification { qDebug() << "applicationWillFinishLaunching"; if ( unicorn::Settings().showDock() ) { ProcessSerialNumber psn = { 0, kCurrentProcess }; TransformProcessType(&psn, ProcessTransformToForegroundApplication); SetSystemUIMode(kUIModeNormal, 0); [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:@"com.apple.dock" options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifier:nil]; [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE]; } } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { m_observer->forceApplicationDidFinishLaunching( aNotification ); } - (BOOL)application:(NSApplication*)sender delegateHandlesKey:(NSString*)key { Q_UNUSED(sender); return [[NSSet setWithObjects: @"trackTitle", @"artist", @"album", @"duration", @"artwork", @"loved", nil] containsObject:key]; } - (NSString*)trackTitle { if ( m_observer->m_observer ) { QString string = m_observer->m_observer->trackTitle(); if ( !string.isEmpty() ) return [NSString stringWithCharacters:(const unichar *)string.unicode() length:(NSUInteger)string.length() ]; } return nil; } - (NSString*)artist { if ( m_observer->m_observer ) { QString string = m_observer->m_observer->artist(); if ( !string.isEmpty() ) return [NSString stringWithCharacters:(const unichar *)string.unicode() length:(NSUInteger)string.length() ]; } return nil; } - (NSString*)album { if ( m_observer->m_observer ) { QString string = m_observer->m_observer->album(); if ( !string.isEmpty() ) return [NSString stringWithCharacters:(const unichar *)string.unicode() length:(NSUInteger)string.length() ]; } return nil; } - (NSNumber*)duration { return [NSNumber numberWithInt:m_observer->m_observer ? m_observer->m_observer->duration() : 0]; } - (NSData*)artwork { if ( m_observer->m_observer ) { QPixmap pixmap = m_observer->m_observer->artwork(); if ( !pixmap.isNull() ) { CGImageRef cgImage = pixmap.toMacCGImageRef(); NSImage* nsImage = [[NSImage alloc] initWithCGImage:(CGImageRef)cgImage size:(NSSize)NSZeroSize]; NSData* data = [nsImage TIFFRepresentation]; return data; } else { NSImage* img = [NSImage imageNamed: NSImageNameApplicationIcon]; NSData* data = [img TIFFRepresentation]; return data; } } return nil; } - (BOOL)loved { return m_observer->m_observer && m_observer->m_observer->loved() ? YES : NO; } @end LFMAppDelegate* g_appDelegate; unicorn::UnicornApplicationDelegate::UnicornApplicationDelegate(QObject *parent) : QObject(parent), m_observer( 0 ) { g_appDelegate = [[LFMAppDelegate alloc] init:this]; [[NSApplication sharedApplication] setDelegate: g_appDelegate]; } void unicorn::UnicornApplicationDelegate::showDockIcon( bool show ) { ProcessSerialNumber psn = { 0, kCurrentProcess }; TransformProcessType(&psn, show?ProcessTransformToForegroundApplication:ProcessTransformToUIElementApplication); SetSystemUIMode(kUIModeNormal, 0); [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:@"com.apple.dock" options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifier:nil]; [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE]; } void unicorn::UnicornApplicationDelegate::setCommandObserver( UnicornApplicationDelegateCommandObserver* observer ) { m_observer = observer; } void unicorn::UnicornApplicationDelegate::forceInitialize() { emit initialize(); } void unicorn::UnicornApplicationDelegate::forceApplicationDidFinishLaunching( void* aNotification ) { emit applicationDidFinishLaunching( aNotification ); } ================================================ FILE: lib/unicorn/UnicornApplication_mac.mm ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "UnicornApplication.h" #import <Foundation/NSAppleEventDescriptor.h> #import <Foundation/NSAppleEventManager.h> #import <Foundation/NSObject.h> void unicorn::Application::setOpenApplicationEventHandler() { NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; [appleEventManager setEventHandler:NULL andSelector:NULL forEventClass:kCoreEventClass andEventID:kAEReopenApplication]; } void unicorn::Application::setGetURLEventHandler() { NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; [appleEventManager setEventHandler:NULL andSelector:NULL forEventClass:kInternetEventClass andEventID:kAEGetURL]; } void unicorn::Application::macTranslate( const QString& lang ) { NSString* langString = [NSString stringWithCharacters:(const unichar *)lang.unicode() length:(NSUInteger)lang.length() ]; [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:langString] forKey:@"AppleLanguages"]; } ================================================ FILE: lib/unicorn/UnicornCoreApplication.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QDebug> #include <QLocale> #include <lastfm/ws.h> #include <lastfm/misc.h> #include <lastfm/Fingerprint.h> #include "UnicornCoreApplication.h" #include "common/c++/Logger.h" using namespace lastfm; #ifdef WIN32 extern void qWinMsgHandler( QtMsgType t, const char* msg ); #endif unicorn::CoreApplication::CoreApplication( const QString& id, int& argc, char** argv ) : QtSingleCoreApplication( id, argc, argv ) { init(); } unicorn::CoreApplication::CoreApplication( int& argc, char** argv ) : QtSingleCoreApplication( argc, argv ) { init(); } void //static unicorn::CoreApplication::init() { QCoreApplication::setOrganizationName( "Last.fm" /*unicorn::organizationName() */ ); QCoreApplication::setOrganizationDomain( "last.fm" /*unicorn::organizationDomain()*/ ); // you can override this api key and secret by setting the // environment variables LASTFM_API_KEY and LASTFM_API_SECRET lastfm::ws::ApiKey = QString( API_KEY ).isEmpty() ? "9e89b44de1ff37c5246ad0af18406454" : API_KEY; lastfm::ws::SharedSecret = QString( API_SECRET ).isEmpty() ? "147320ea9b8930fe196a4231da50ada4" : API_SECRET; #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #ifdef Q_OS_MAC QString pluginsDir = applicationDirPath() + "/../plugins"; #else QString pluginsDir = applicationDirPath() + "/plugins"; #endif addLibraryPath( pluginsDir ); #endif dir::runtimeData().mkpath( "." ); #ifndef WIN32 QFile runtimeDataPerms( dir::runtimeData().absolutePath() ); runtimeDataPerms.setPermissions( QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner ); #endif dir::cache().mkpath( "." ); dir::logs().mkpath( "." ); #ifdef WIN32 QString bytes = CoreApplication::log( applicationName() ).absoluteFilePath(); const wchar_t* path = bytes.utf16(); #else QByteArray bytes = CoreApplication::log( applicationName() ).absoluteFilePath().toLocal8Bit(); const char* path = bytes.data(); #endif new Logger( path ); qInstallMsgHandler( qMsgHandler ); qDebug() << "Introducing" << applicationName()+' '+applicationVersion(); qDebug() << "Directed by" << lastfm::platform(); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) qDebug() << "Plugin DIR" << pluginsDir; #endif } void unicorn::CoreApplication::qMsgHandler( QtMsgType type, const char* msg ) { #ifndef NDEBUG #ifdef WIN32 qWinMsgHandler( type, msg ); #else Q_UNUSED( type ); fprintf( stderr, "%s\n", msg ); fflush( stderr ); #endif #endif Logger::the().log( msg ); } QFileInfo unicorn::CoreApplication::log( const QString& productName ) { #ifdef NDEBUG return dir::logs().filePath( productName + ".log" ); #else return dir::logs().filePath( productName + ".debug.log" ); #endif } bool unicorn::CoreApplication::notify(QObject* receiver, QEvent* event ) { try { return QCoreApplication::notify( receiver, event ); } #ifdef LASTFM_FINGERPRINTER catch( const lastfm::Fingerprint::Error& e ) { qDebug() << "Fingerprint error" << e; qApp->quit(); } #endif catch(...) { qDebug() << "Exception caught."; qApp->quit(); } return false; } ================================================ FILE: lib/unicorn/UnicornCoreApplication.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UNICORN_CORE_APPLICATION_H #define UNICORN_CORE_APPLICATION_H #include "lib/DllExportMacro.h" #include "qtsingleapplication/qtsinglecoreapplication.h" #include <QFileInfo> namespace unicorn { class UNICORN_DLLEXPORT CoreApplication : public QtSingleCoreApplication { Q_DISABLE_COPY(CoreApplication); friend class Application; static void init(); static void qMsgHandler( QtMsgType, const char* ); public: CoreApplication( const QString& id, int& argc, char** argv ); CoreApplication( int& argc, char** argv ); static QFileInfo log() { Q_ASSERT( applicationName().size() ); return log( applicationName() ); } static QFileInfo log( const QString& productName ); static const char* platformString(); // We need to catch exceptions from event handlers. // liblastfm-fingerprint is a bit exception happy... virtual bool notify ( QObject * receiver, QEvent * event ); }; } #endif ================================================ FILE: lib/unicorn/UnicornMainWindow.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "UnicornMainWindow.h" #include "UnicornApplication.h" #include "UnicornCoreApplication.h" #include "dialogs/AboutDialog.h" #include "UnicornSettings.h" #include <lastfm/User.h> #include <QDesktopServices> #include <QDesktopWidget> #include <QMenuBar> #include <QShortcut> #include <QMouseEvent> #include "lib/unicorn/DesktopServices.h" #ifdef Q_OS_WIN32 #include <Objbase.h> #endif #define SETTINGS_POSITION_KEY "MainWindowPosition" #define SETTINGS_GEOMETRY_KEY "Geo" unicorn::MainWindow::MainWindow( QMenuBar* menuBar, QWidget* parent ) :QMainWindow( parent ), m_menuBar( menuBar ) { new QShortcut( QKeySequence(Qt::CTRL+Qt::Key_W), this, SLOT(close()) ); new QShortcut( QKeySequence(Qt::ALT+Qt::SHIFT+Qt::Key_L), this, SLOT(openLog()) ); connect( qApp->desktop(), SIGNAL( resized(int)), SLOT( cleverlyPosition())); #ifdef Q_OS_WIN32 taskBarCreatedMessage = RegisterWindowMessage(L"TaskbarButtonCreated"); #endif } unicorn::MainWindow::~MainWindow() { } void unicorn::MainWindow::finishUi() { #ifndef NDEBUG QMenu* debug = appMenuBar()->addMenu( "Debug" ); debug->addAction( tr("Refresh Stylesheet"), qApp, SLOT(refreshStyleSheet()), Qt::CTRL + Qt::Key_R ); #endif cleverlyPosition(); } void unicorn::MainWindow::refreshStyleSheet() { static_cast<unicorn::Application*>(qApp)->refreshStyleSheet(); } #ifdef Q_OS_WIN32 bool unicorn::MainWindow::winEvent(MSG* message, long* result) { if ( message->message == taskBarCreatedMessage) { HRESULT hr = CoCreateInstance(CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS( &taskbar)); if (hr == S_OK) { m_thumbButtonActions.clear(); addWinThumbBarButtons( m_thumbButtonActions ); Q_ASSERT_X( m_thumbButtonActions.count() <= 7, "winEvent", "More than 7 thumb buttons" ); // make sure we update the thumb buttons everytime they change state foreach ( QAction* button, m_thumbButtonActions ) { connect( button, SIGNAL(changed()), SLOT(updateThumbButtons())); connect( button, SIGNAL(toggled(bool)), SLOT(updateThumbButtons())); } if ( m_thumbButtonActions.count() > 0 ) updateThumbButtons(); *result = hr; return true; } } else if ( message->message == WM_COMMAND ) if ( HIWORD(message->wParam) == THBN_CLICKED ) m_thumbButtonActions[LOWORD(message->wParam)]->trigger(); return false; } void unicorn::MainWindow::updateThumbButtons() { THUMBBUTTON buttons[7]; for ( int i(0) ; i < m_thumbButtonActions.count() ; ++i ) { buttons[i].dwMask = THB_ICON|THB_TOOLTIP|THB_FLAGS; buttons[i].iId = i; buttons[i].hIcon = m_thumbButtonActions[i]->isChecked() ? m_thumbButtonActions[i]->icon().pixmap( QSize(16, 16), QIcon::Normal, QIcon::On ).toWinHICON(): m_thumbButtonActions[i]->icon().pixmap( QSize(16, 16), QIcon::Normal, QIcon::Off ).toWinHICON(); wcscpy(buttons[i].szTip, m_thumbButtonActions[i]->text().utf16()); buttons[i].dwFlags = m_thumbButtonActions[i]->isEnabled() ? THBF_ENABLED : THBF_DISABLED; } if (sender()) taskbar->ThumbBarUpdateButtons(winId(), m_thumbButtonActions.count(), buttons); else taskbar->ThumbBarAddButtons(winId(), m_thumbButtonActions.count(), buttons); } #endif // Q_OS_WIN32 void unicorn::MainWindow::openLog() { unicorn::DesktopServices::openUrl( QUrl::fromLocalFile( unicorn::CoreApplication::log().absoluteFilePath() ) ); } bool unicorn::MainWindow::eventFilter( QObject* o, QEvent* event ) { #ifdef SUPER_MEGA_DEBUG qDebug() << o << event; #endif QWidget* obj = qobject_cast<QWidget*>( o ); if (!obj) return false; QMouseEvent* e = static_cast<QMouseEvent*>( event ); switch ((int)e->type()) { case QEvent::MouseButtonPress: m_dragHandleMouseDownPos[ obj ] = e->globalPos() - pos(); return false; case QEvent::MouseButtonRelease: m_dragHandleMouseDownPos[ obj ] = QPoint(); return false; case QEvent::MouseMove: if (m_dragHandleMouseDownPos.contains( obj ) && !m_dragHandleMouseDownPos[ obj ].isNull()) { move( e->globalPos() - m_dragHandleMouseDownPos[ obj ] ); return true; } } return QMainWindow::eventFilter(o, event); } void unicorn::MainWindow::addDragHandleWidget( QWidget* w ) { w->installEventFilter( this ); } void unicorn::MainWindow::hideEvent( QHideEvent* ) { emit hidden( false ); } void unicorn::MainWindow::showEvent( QShowEvent* ) { emit shown( true ); } void unicorn::MainWindow::storeGeometry() const { AppSettings s; s.beginGroup( metaObject()->className() ); s.setValue( SETTINGS_GEOMETRY_KEY, saveGeometry() ); s.endGroup(); } void unicorn::MainWindow::moveEvent( QMoveEvent* ) { storeGeometry(); } void unicorn::MainWindow::resizeEvent( QResizeEvent* ) { storeGeometry(); } void unicorn::MainWindow::cleverlyPosition() { AppSettings s; s.beginGroup( metaObject()->className() ); if ( s.contains( SETTINGS_GEOMETRY_KEY ) ) restoreGeometry( s.value( SETTINGS_GEOMETRY_KEY, "" ).toByteArray() ); s.endGroup(); int screenNum = qApp->desktop()->screenNumber( this ); QRect screenRect = qApp->desktop()->availableGeometry( screenNum ); if( !screenRect.contains( frameGeometry(), true) ) { // the window is not entirly inside the screen // make sure it is able to fit resize( qMin( screenRect.width(), width() ), qMin( screenRect.height(), height() ) ); // make sure the window is on the screen QRect diff = screenRect.intersected( frameGeometry() ); if (diff.left() == screenRect.left() ) move( diff.left(), pos().y() ); if( diff.right() == screenRect.right() ) move( diff.right() - width(), pos().y() ); if( diff.top() == screenRect.top() ) move( pos().x(), diff.top() ); if( diff.bottom() == screenRect.bottom() ) move( pos().x(), diff.bottom() - height() ); } } ================================================ FILE: lib/unicorn/UnicornMainWindow.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UNICORN_MAIN_WINDOW #define UNICORN_MAIN_WINDOW #include "lib/DllExportMacro.h" #include <QMainWindow> #include <QPointer> #include <QMenuBar> #include <QDialog> #include <QMap> #ifdef Q_OS_WIN32 #include <windows.h> #include <shobjidl.h> #endif class AboutDialog; class UpdateDialog; class QNetworkReply; template <typename D> struct OneDialogPointer : public QPointer<D> { OneDialogPointer& operator=( QDialog* d ) { QPointer<D>::operator=( (D*)d ); d->setAttribute( Qt::WA_DeleteOnClose ); d->setWindowFlags( Qt::Dialog | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint ); d->setModal( false ); return *this; } void show() { QDialog* d = (QDialog*)QPointer<D>::data(); d->show(); d->raise(); d->activateWindow(); } }; namespace lastfm { class User; } namespace unicorn { class Session; class UNICORN_DLLEXPORT MainWindow : public ::QMainWindow { Q_OBJECT public: MainWindow( QMenuBar*, QWidget* parent = 0 ); ~MainWindow(); /** call this to add the account menu and about menu action, etc. */ void finishUi(); void addDragHandleWidget( QWidget* ); QMenuBar* appMenuBar() const { return m_menuBar; } public slots: void openLog(); void refreshStyleSheet(); private: void storeGeometry() const; virtual bool eventFilter( QObject*, QEvent* ); virtual void hideEvent( QHideEvent* ); virtual void showEvent( QShowEvent* ); virtual void moveEvent( QMoveEvent* ); virtual void resizeEvent( QResizeEvent* ); virtual void addWinThumbBarButtons( QList<QAction*>& ) {;} QMenuBar* menuBar() const { return 0; } protected: QPointer<QMenuBar> m_menuBar; private: QList<QAction*> m_thumbButtonActions; #ifdef Q_OS_WIN32 bool canUseTaskBar; UINT taskBarCreatedMessage; ITaskbarList3* taskbar; private: bool winEvent(MSG* message, long* result); #endif QMap<QWidget*, QPoint> m_dragHandleMouseDownPos; private slots: void cleverlyPosition(); #ifdef Q_OS_WIN32 void updateThumbButtons(); #endif signals: void hidden( bool ); void shown( bool ); }; } #endif ================================================ FILE: lib/unicorn/UnicornSession.cpp ================================================ /* Copyright 2009-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QApplication> #include <QDebug> #include <QTimer> #include <lastfm/User.h> #include <lastfm/Auth.h> #include <lastfm/XmlQuery.h> #include "UnicornSession.h" #include "UnicornSettings.h" namespace unicorn { QMap<QString, QString> Session::lastSessionData() { Settings s; QMap<QString, QString> sessionData; //use the Username setting or the first username if there have been any logged in previously QString username = s.value( "Username", QString() ).toString(); if( !username.isEmpty() ) { UserSettings us( username ); sessionData[ "username" ] = username; const QString sk = us.value( "SessionKey", "" ).toString(); if( !sk.isEmpty() ) sessionData[ "sessionKey" ] = sk; } return sessionData; } Session::Session() { m_info.valid = false; } Session::Session( const QString& username, QString sessionKey ) { init( username, sessionKey ); connect( qApp, SIGNAL( internetConnectionUp() ), SLOT( fetchInfo() ) ); } Session::Session( QDataStream& dataStream ) { dataStream >> *this; connect( qApp, SIGNAL( internetConnectionUp() ), SLOT( fetchInfo() ) ); } QNetworkReply* Session::getToken() { QMap<QString, QString> params; params["method"] = "auth.getToken"; return lastfm::ws::get( params ); } QNetworkReply* Session::getSession( QString token ) { QMap<QString, QString> params; params["method"] = "auth.getSession"; params["token"] = token; return lastfm::ws::post( params, false ); } QString Session::subscriptionPriceString() const { return m_info.subscriptionPrice; } bool Session::isValid() const { return m_info.valid; } bool Session::youRadio() const { return m_info.youRadio; } bool Session::registeredRadio() const { return m_info.registeredRadio; } bool Session::subscriberRadio() const { return m_info.subscriberRadio; } bool Session::youWebRadio() const { return m_info.youWebRadio; } bool Session::registeredWebRadio() const { return m_info.registeredWebRadio; } bool Session::subscriberWebRadio() const { return m_info.subscriberWebRadio; } QString Session::sessionKey() const { return m_sessionKey; } lastfm::User Session::user() const { return m_user; } void Session::init( const QString& username, const QString& sessionKey ) { Settings s; s.setValue( "Username", username ); m_user.setName( username ); m_sessionKey = sessionKey; UserSettings us( username ); m_user.setName( username ); m_user.setScrobbleCount( us.scrobbleCount() ); m_user.setDateRegistered( us.dateRegistered() ); m_user.setRealName( us.realName() ); m_user.setIsSubscriber( us.subscriber() ); m_user.setType( us.type() ); QList<QUrl> imageUrls; int imageCount = us.beginReadArray( "ImageUrls" ); for ( int i = 0; i < imageCount; i++ ) { us.setArrayIndex( i ); imageUrls.append( us.value( "Url", QUrl() ).toUrl() ); } us.endArray(); m_user.setImages( imageUrls ); if ( sessionKey.isEmpty() ) Q_ASSERT( false ); else us.setValue( "SessionKey", sessionKey ); m_info = us.sessionInfo(); fetchInfo(); } void Session::fetchInfo() { qDebug() << "fetching user info"; lastfm::ws::Username = m_user.name(); lastfm::ws::SessionKey = m_sessionKey; connect( lastfm::User::getInfo(), SIGNAL( finished() ), SLOT( onUserGotInfo() ) ); connect( lastfm::Auth::getSessionInfo(), SIGNAL(finished()), SLOT(onAuthGotSessionInfo()) ); } void Session::onUserGotInfo() { QNetworkReply* reply = ( QNetworkReply* )sender(); XmlQuery lfm; if ( lfm.parse( reply ) ) { lastfm::User user( lfm["user"] ); m_user = user; cacheUserInfo( m_user ); emit userInfoUpdated( m_user ); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } } void Session::onAuthGotSessionInfo() { XmlQuery lfm; if ( lfm.parse( static_cast<QNetworkReply*>( sender() ) ) ) { qDebug() << lfm; m_info.valid = true; m_info.subscriptionPrice = lfm["application"]["radioprice"]["formatted"].text(); XmlQuery you = lfm["application"]["radioPermission"]["user type=you"]; m_info.youRadio = you["radio"].text() == "1"; m_info.youWebRadio = you["webradio"].text() == "1"; XmlQuery registered = lfm["application"]["radioPermission"]["user type=registered"]; m_info.registeredRadio = registered["radio"].text() == "1"; m_info.registeredWebRadio = registered["webradio"].text() == "1"; XmlQuery subscriber = lfm["application"]["radioPermission"]["user type=subscriber"]; m_info.subscriberRadio = subscriber["radio"].text() == "1"; m_info.subscriberWebRadio = subscriber["webradio"].text() == "1"; bool isSubscriber = lfm["application"]["session"]["subscriber"].text() == "1"; m_user.setIsSubscriber( isSubscriber ); cacheUserInfo( m_user ); // make sure the subscriber flag gets cached cacheSessionInfo( *this ); emit sessionChanged( *this ); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } } void Session::cacheUserInfo( const lastfm::User& user ) { UserSettings us( user.name() ); us.setSubscriber( user.isSubscriber() ); us.setValue( "ScrobbleCount", user.scrobbleCount() ); us.setValue( "DateRegistered", user.dateRegistered() ); us.setValue( "RealName", user.realName() ); us.setValue( "Type", user.type() ); QList<User::ImageSize> sizes; sizes << User::SmallImage << User::MediumImage << User::LargeImage; us.beginWriteArray( "ImageUrls", sizes.count() ); for ( int i = 0; i < sizes.count(); i++ ) { us.setArrayIndex( i ); us.setValue( "Url", user.imageUrl( sizes[ i ] ) ); } us.endArray(); } void Session::cacheSessionInfo( const unicorn::Session& session ) { UserSettings userSettings( session.user().name() ); userSettings.setSessionInfo( m_info ); } QDataStream& Session::write( QDataStream& out ) const { QMap<QString, QString> data; data[ "username" ] = user().name(); data[ "sessionkey" ] = m_sessionKey; out << data; return out; } QDataStream& Session::read( QDataStream& in ) { QMap<QString, QString> data; in >> data; init( data[ "username" ], data[ "sessionkey" ] ); return in; } } QDataStream& operator<<( QDataStream& out, const unicorn::Session& s ){ return s.write( out ); } QDataStream& operator>>( QDataStream& in, unicorn::Session& s ){ return s.read( in ); } ================================================ FILE: lib/unicorn/UnicornSession.h ================================================ /* Copyright 2009-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UNICORN_SESSION_H_ #define UNICORN_SESSION_H_ #include <lastfm/XmlQuery.h> #include <lastfm/misc.h> #include <lastfm/User.h> #include <lastfm/ws.h> #include <QObject> #include <QSharedData> #include "lib/DllExportMacro.h" namespace unicorn { class UNICORN_DLLEXPORT Session : public QObject { Q_OBJECT public: struct Info { bool valid; QString subscriptionPrice; bool youRadio; bool registeredRadio; bool subscriberRadio; bool youWebRadio; bool registeredWebRadio; bool subscriberWebRadio; }; public: /** Return session object from stored session */ Session(); Session( QDataStream& dataStream ); Session( const QString& username, QString sessionKey = "" ); bool isValid() const; // client radio permissions bool youRadio() const; bool registeredRadio() const; bool subscriberRadio() const; // web radio permissions bool youWebRadio() const; bool registeredWebRadio() const; bool subscriberWebRadio() const; QString subscriptionPriceString() const; QString sessionKey() const; lastfm::User user() const; static QNetworkReply* getToken(); static QNetworkReply* getSession( QString token ); static QMap<QString, QString> lastSessionData(); QDataStream& write( QDataStream& out ) const; QDataStream& read( QDataStream& in ); signals: void userInfoUpdated( const lastfm::User& user ); void sessionChanged( const unicorn::Session& session ); protected: void init( const QString& username, const QString& sessionKey ); private: void cacheUserInfo( const lastfm::User& user ); void cacheSessionInfo( const unicorn::Session& session ); private slots: void fetchInfo(); void onUserGotInfo(); void onAuthGotSessionInfo(); private: QString m_prevUsername; QString m_sessionKey; lastfm::User m_user; Info m_info; }; } QDataStream& operator<<( QDataStream& out, const unicorn::Session& s ); QDataStream& operator>>( QDataStream& in, unicorn::Session& s ); #endif //UNICORN_SESSION_H_ ================================================ FILE: lib/unicorn/UnicornSettings.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "UnicornSettings.h" #include "UnicornApplication.h" #include <lastfm/User.h> unicorn::Settings::Settings() :QSettings( unicorn::organizationName(), "" ) {} QList<lastfm::User> unicorn::Settings::userRoster() const { const_cast<Settings*>(this)->beginGroup( "Users" ); QList<User> ret; foreach( QString child, childGroups()) { if( child == "com" || !contains( child + "/SessionKey" )) continue; ret << User( child ); } const_cast<Settings*>(this)->endGroup(); return ret; } bool unicorn::Settings::firstRunWizardCompleted() const { return value( "FirstRunWizardCompletedBeta", false ).toBool(); } void unicorn::Settings::setFirstRunWizardCompleted( bool firstRunWizardCompleted ) { setValue( "FirstRunWizardCompletedBeta", firstRunWizardCompleted ); } bool unicorn::Settings::betaUpdates() const { return value( "betaUpdates", false ).toBool(); } void unicorn::Settings::setBetaUpdates( bool betaUpdates ) { setValue( "betaUpdates", betaUpdates ); } void unicorn::Settings::showWhere() { int showWhereIndex = value( "showWhere", -1 ).toInt(); if ( showWhereIndex != -1 ) { // 0 == show both, 1 == show only dock, 2 == show only menu bar setShowAS( showWhereIndex != 1 ); setShowDock( showWhereIndex != 2 ); remove( "showWhere" ); // don't read this setting again } } bool unicorn::Settings::showAS() { showWhere(); return value( "showAS", true ).toBool(); } void unicorn::Settings::setShowAS( bool showAS ) { setValue( "showAS", showAS ); } bool unicorn::Settings::showDock() { showWhere(); return value( "showDock", true ).toBool(); } void unicorn::Settings::setShowDock( bool showDock ) { setValue( "showDock", showDock ); } bool unicorn::Settings::notifications() const { return value( "notifications", true ).toBool(); } void unicorn::Settings::setNotifications( bool notifications ) { setValue( "notifications", notifications ); } bool unicorn::Settings::sendCrashReports() const { return value( "sendCrashReports", true ).toBool(); } void unicorn::Settings::setSendCrashReports( bool sendCrashReports ) { setValue( "sendCrashReports", sendCrashReports ); } bool unicorn::Settings::checkForUpdates() const { return value( "checkForUpdates", true ).toBool(); } void unicorn::Settings::setCheckForUpdates( bool checkForUpdates ) { setValue( "checkForUpdates", checkForUpdates ); } unicorn::AppSettings::AppSettings( QString appname ) : QSettings( unicorn::organizationName(), appname.isEmpty() ? qApp->applicationName() : appname ) {} unicorn::UserSettings::UserSettings( QString username ) { beginGroup( "Users" ); beginGroup( username ); } bool unicorn::AppSettings::alwaysAsk() const { return value( "alwaysAsk", true ).toBool(); } void unicorn::AppSettings::setAlwaysAsk( bool alwaysAsk ) { setValue( "alwaysAsk", alwaysAsk ); } unicorn::OldeAppSettings::OldeAppSettings() : AppSettings( #ifdef Q_OS_MAC "scrobbler" #else "Client" #endif ) {} bool unicorn::OldeAppSettings::deviceScrobblingEnabled() const { return value( "iPodScrobblingEnabled", true ).toBool(); } void unicorn::OldeAppSettings::setDeviceScrobblingEnabled( bool deviceScrobblingEnabled ) { setValue( "iPodScrobblingEnabled", deviceScrobblingEnabled ); } bool unicorn::OldeAppSettings::launchWithMediaPlayers() const { return value( "LaunchWithMediaPlayer", true ).toBool(); } void unicorn::OldeAppSettings::setLaunchWithMediaPlayers( bool LaunchWithMediaPlayers ) { setValue( "LaunchWithMediaPlayer", LaunchWithMediaPlayers ); } unicorn::Session::Info unicorn::UserSettings::sessionInfo() { unicorn::Session::Info info; beginGroup( "Session" ); info.valid = value( "valid", false ).toBool(); info.subscriptionPrice = value( "subscriptionPrice", "" ).toString(); info.youRadio = value( "youRadio", false ).toBool(); info.registeredRadio = value( "registeredRadio", false ).toBool(); info.subscriberRadio = value( "subscriberRadio", false ).toBool(); info.youWebRadio = value( "youWebRadio", false ).toBool(); info.registeredWebRadio = value( "registeredWebRadio", false ).toBool(); info.subscriberWebRadio = value( "subscriberWebRadio", false ).toBool(); endGroup(); return info; } void unicorn::UserSettings::setSessionInfo( const unicorn::Session::Info& sessionInfo ) { beginGroup( "Session" ); setValue( "valid", sessionInfo.valid ); setValue( "subscriptionPrice", sessionInfo.subscriptionPrice ); setValue( "youRadio", sessionInfo.youRadio ); setValue( "registeredRadio", sessionInfo.registeredRadio ); setValue( "subscriberRadio", sessionInfo.subscriberRadio ); setValue( "youWebRadio", sessionInfo.youWebRadio ); setValue( "registeredWebRadio", sessionInfo.registeredWebRadio ); setValue( "subscriberWebRadio", sessionInfo.subscriberWebRadio ); endGroup(); } bool unicorn::UserSettings::subscriber() const { return value( "subscriber", false ).toBool(); } void unicorn::UserSettings::setSubscriber( bool subscriber ) { setValue( "subscriber", subscriber ); } QString unicorn::UserSettings::sessionKey() const { return value( "SessionKey", "" ).toString(); } void unicorn::UserSettings::setSessionKey( const QString& sessionKey ) { setValue( "SessionKey", sessionKey ); } quint32 unicorn::UserSettings::scrobbleCount() const { return value( "ScrobbleCount", 0 ).toUInt(); } void unicorn::UserSettings::setScrobbleCount( quint32 scrobbleCount ) { setValue( "ScrobbleCount", scrobbleCount ); } QDateTime unicorn::UserSettings::dateRegistered() const { return value( "DateRegistered", false ).toDateTime(); } void unicorn::UserSettings::setDateRegistered( const QDateTime& dateRegistered ) { setValue( "DateRegistered", dateRegistered ); } QString unicorn::UserSettings::realName() const { return value( "RealName", "" ).toString(); } void unicorn::UserSettings::setRealName( const QString& realName ) { setValue( "RealName", realName ); } User::Type unicorn::UserSettings::type() const { return static_cast<User::Type>( value( "Type", User::TypeUser ).toInt() ); } void unicorn::UserSettings::setType( User::Type type ) { setValue( "Type", type ); } double unicorn::UserSettings::scrobblePoint() const { return value( "scrobblePoint", 50 ).toDouble(); } void unicorn::UserSettings::setScrobblePoint( double scrobblePoint ) { setValue( "scrobblePoint", scrobblePoint ); } bool unicorn::UserSettings::fingerprinting() const { return value( "fingerprint", true ).toBool(); } void unicorn::UserSettings::setFingerprinting( bool fingerprinting ) { setValue( "fingerprint", fingerprinting ); } bool unicorn::UserSettings::podcasts() const { return value( "podcasts", true ).toBool(); } void unicorn::UserSettings::setPodcasts( bool podcasts ) { setValue( "podcasts", podcasts ); } bool unicorn::UserSettings::scrobblingOn() const { return value( "scrobblingOn", true ).toBool(); } void unicorn::UserSettings::setScrobblingOn( bool scrobblingOn ) { setValue( "scrobblingOn", scrobblingOn ); } QStringList unicorn::UserSettings::exclusionDirs() const { return value( "ExclusionDirs", QStringList() ).toStringList(); } void unicorn::UserSettings::setExclusionDirs( const QStringList& exclusionDirs ) { setValue( "ExclusionDirs", exclusionDirs ); } bool unicorn::UserSettings::enforceScrobbleTimeMax() const { return value( "enforceScrobbleTimeMax", true ).toBool(); } void unicorn::UserSettings::setEnforceScrobbleTimeMax( bool enforceScrobbleTimeMax ) { setValue( "enforceScrobbleTimeMax", enforceScrobbleTimeMax ); } ================================================ FILE: lib/unicorn/UnicornSettings.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UNICORN_SETTINGS_H #define UNICORN_SETTINGS_H #include "lib/unicorn/UnicornSession.h" #include "lib/DllExportMacro.h" #include <lastfm/User.h> #include <QSettings> #include <QString> #include <QCoreApplication> namespace unicorn { inline const char* organizationName() { return "Last.fm"; } inline const char* organizationDomain() { return "last.fm"; } class UNICORN_DLLEXPORT Settings : public QSettings { public: Settings(); QList<lastfm::User> userRoster() const; bool firstRunWizardCompleted() const; void setFirstRunWizardCompleted( bool firstRunWizardCompleted ); bool betaUpdates() const; void setBetaUpdates( bool betaUpdates ); bool showAS(); void setShowAS( bool showAS ); bool showDock(); void setShowDock( bool showDock ); bool notifications() const; void setNotifications( bool notifications ); bool sendCrashReports() const; void setSendCrashReports( bool sendCrashReports ); bool checkForUpdates() const; void setCheckForUpdates( bool checkForUpdates ); private: void showWhere(); }; class UNICORN_DLLEXPORT AppSettings : public QSettings { public: AppSettings( QString appname = QCoreApplication::applicationName() ); bool alwaysAsk() const; void setAlwaysAsk( bool alwaysAsk ); }; class UNICORN_DLLEXPORT OldeAppSettings : public AppSettings { public: OldeAppSettings(); bool deviceScrobblingEnabled() const; void setDeviceScrobblingEnabled( bool deviceScrobblingEnabled ); bool launchWithMediaPlayers() const; void setLaunchWithMediaPlayers( bool launchWithMediaPlayers ); }; /** Clearly no use until a username() has been assigned. But this is * automatic if you use unicorn::Application anyway. */ class UNICORN_DLLEXPORT UserSettings : public Settings { public: struct SessionInfo { }; UserSettings( QString userName = User() ); unicorn::Session::Info sessionInfo(); void setSessionInfo( const unicorn::Session::Info& sessionInfo ); bool subscriber() const; void setSubscriber( bool subscriber ); QString sessionKey() const; void setSessionKey( const QString& sessionKey ); quint32 scrobbleCount() const; void setScrobbleCount( quint32 scrobbleCount ); QDateTime dateRegistered() const; void setDateRegistered( const QDateTime& dateRegistered ); QString realName() const; void setRealName( const QString& realName ); User::Type type() const; void setType( User::Type type ); double scrobblePoint() const; void setScrobblePoint( double scrobblePoint ); bool fingerprinting() const; void setFingerprinting( bool fingerprinting ); bool podcasts() const; void setPodcasts( bool podcasts ); bool scrobblingOn() const; void setScrobblingOn( bool scrobblingOn ); QStringList exclusionDirs() const; void setExclusionDirs( const QStringList& exclusionDirs ); bool enforceScrobbleTimeMax() const; void setEnforceScrobbleTimeMax( bool enforceScrobbleTimeMax ); }; } #endif ================================================ FILE: lib/unicorn/UpdateInfoFetcher.cpp ================================================ /* Copyright 2009-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "UpdateInfoFetcher.h" #include <lastfm/ws.h> #include <lastfm/XmlQuery.h> #include <QDebug> #include <QSettings> #include <QString> #ifdef WIN32 static const char *PLATFORM = "win"; #elif defined Q_WS_X11 static const char *PLATFORM = "linux"; #elif defined Q_WS_MAC static const char *PLATFORM = "mac"; #else static const char *PLATFORM = "unknown"; #endif Plugin::Plugin( const XmlQuery& query ) :m_bootstrapType( NoBootstrap ), m_valid( true ) { m_name = query.attribute( "name" ); m_id = query.attribute( "id" ); m_url = QUrl( query["Url"].text() ); m_installDir = QDir( query[ "InstallDir" ].text() ); m_args = query["Args"].text(); m_minVersion = query["MinVersion"].text(); m_maxVersion = query["MaxVersion"].text(); m_regDisplayName = query["RegDisplayName"].text(); QString bs = query["Bootstrap"].text(); if( bs.compare( "Client", Qt::CaseInsensitive ) == 0) { m_bootstrapType = ClientBootstrap; } else if( bs.compare( "Plugin", Qt::CaseInsensitive ) == 0) { m_bootstrapType = PluginBootstrap; } } bool Plugin::isInstalled() const { #ifdef Q_OS_WIN QSettings s("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\", QSettings::NativeFormat ); foreach( QString group, s.childGroups()) { s.beginGroup( group ); QString name = s.value( "DisplayName" ).toString(); if( name.contains( m_regDisplayName ) || group.contains( m_regDisplayName )) { return true; } s.endGroup(); } return false; #elif defined Q_OS_MAC return true; #endif } bool Plugin::isPluginInstalled() const { #ifdef Q_OS_WIN QSettings s( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Last.fm\\Client\\Plugins", QSettings::NativeFormat ); return s.childGroups().contains( m_id ); #elif defined Q_OS_MAC return true; #endif } bool Plugin::canBootstrap() const { return m_bootstrapType != NoBootstrap; } UpdateInfoFetcher::UpdateInfoFetcher( QNetworkReply* reply, QObject* parent ) :QObject( parent ) { XmlQuery xq; xq.parse( reply ); QList<XmlQuery> plugins = xq.children( "Plugin" ); foreach( const XmlQuery& plugin, plugins ) { m_plugins << Plugin( plugin ); } } QNetworkReply* UpdateInfoFetcher::fetchInfo() //static { QString url = QString( "http://%1/ass/upgrade.xml.php?platform=%2&lang=en" ).arg( lastfm::ws::host(), PLATFORM ); QNetworkRequest req( url ); return lastfm::nam()->get( req ); } ================================================ FILE: lib/unicorn/UpdateInfoFetcher.h ================================================ /* Copyright 2009-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UPDATE_INFO_FETCHER_H_ #define UPDATE_INFO_FETCHER_H_ #include <QObject> #include "lib/DllExportMacro.h" #include <QUrl> #include <QDir> namespace lastfm{ class XmlQuery; } class UNICORN_DLLEXPORT Plugin { public: enum BootstrapType{ NoBootstrap, ClientBootstrap, PluginBootstrap }; Plugin(): m_valid( false ){} Plugin( const lastfm::XmlQuery& ); QString toString() const { return QString( "%1 (%2 - %3)\n\tinstallDir: %4\n\targs:%5\n\tregDisplayName: %6" ) .arg( m_name ) .arg( m_minVersion ) .arg( m_maxVersion ) .arg( m_installDir.path() ) .arg( m_args ) .arg( m_regDisplayName ); } QString name() const { return m_name; } QString regDisplayName() const { return m_regDisplayName; } bool isValid() const { return m_valid; } bool isInstalled() const; bool isPluginInstalled() const; bool canBootstrap() const; private: QString m_name; QString m_id; QUrl m_url; QDir m_installDir; QString m_args; QString m_minVersion; QString m_maxVersion; QString m_regDisplayName; BootstrapType m_bootstrapType; bool m_valid; }; class UNICORN_DLLEXPORT UpdateInfoFetcher : public QObject { Q_OBJECT public: UpdateInfoFetcher( class QNetworkReply* reply, QObject* parent = 0 ); static QNetworkReply* fetchInfo(); const QList<Plugin>& plugins() const{ return m_plugins; } private: QList<Plugin> m_plugins; }; #endif //UPDATE_INFO_FETCHER_H_ ================================================ FILE: lib/unicorn/Updater/Updater.cpp ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "Updater.h" #ifndef Q_OS_MAC #ifdef Q_OS_WIN #include <QCoreApplication> #include <QStringList> #include <qtsparkle/Updater> #endif #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/UnicornApplication.h" unicorn::Updater::Updater( QWidget* parent ) : QObject(parent), m_parentWidget( parent ) { #ifdef Q_OS_WIN if ( qApp->arguments().contains( "--update" ) ) { int urlIndex = qApp->arguments().indexOf( "--update" ) + 1; if ( qApp->arguments().count() > urlIndex && qApp->arguments()[urlIndex].startsWith( "https://" ) ) { m_updater = new qtsparkle::Updater( QUrl( QString( qApp->arguments()[urlIndex].toUtf8() ) ), m_parentWidget ); m_updater->SetNetworkAccessManager( lastfm::nam() ); m_updater->SetIcon( QPixmap( ":/scrobbler_64.png" ) ); } else setBetaUpdates( unicorn::Settings().betaUpdates() ); } else setBetaUpdates( unicorn::Settings().betaUpdates() ); #endif } void unicorn::Updater::setBetaUpdates( bool betaUpdates ) { #ifdef Q_OS_WIN if ( !m_updater || m_betaUpdates != betaUpdates ) { // there is no updater yet or we are changing the update file m_betaUpdates = betaUpdates; delete m_updater; m_updater = new qtsparkle::Updater( QUrl( QString( betaUpdates ? UPDATE_URL_WIN_BETA : UPDATE_URL_WIN ) ), m_parentWidget ); m_updater->SetNetworkAccessManager( lastfm::nam() ); m_updater->SetIcon( QPixmap( ":/scrobbler_64.png" ) ); } #endif } void unicorn::Updater::checkForUpdates() { #ifdef Q_OS_WIN m_updater->CheckNow(); #endif } unicorn::Updater::~Updater() { #ifdef Q_OS_WIN delete m_updater; #endif } #endif // Q_OS_MAC ================================================ FILE: lib/unicorn/Updater/Updater.h ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UPDATER_H #define UPDATER_H #include <QObject> #include <QPointer> #include "lib/DllExportMacro.h" #ifdef Q_OS_WIN namespace qtsparkle { class Updater; } #define UPDATE_URL_WIN "https://cdn.last.fm/client/updates/updates.win.xml" #define UPDATE_URL_WIN_BETA "https://cdn.last.fm/client/updates/updates.win.beta.xml" #elif defined( Q_OS_MAC ) #define UPDATE_URL_MAC @"https://cdn.last.fm/client/updates/updates.mac.xml" #define UPDATE_URL_MAC_BETA @"https://cdn.last.fm/client/updates/updates.mac.beta.xml" #endif namespace unicorn { class UNICORN_DLLEXPORT Updater : public QObject { Q_OBJECT public: explicit Updater( QWidget* parent = 0 ); ~Updater(); void setBetaUpdates( bool betaUpdates ); public slots: void checkForUpdates(); private: #ifdef Q_OS_WIN QPointer<qtsparkle::Updater> m_updater; QWidget* m_parentWidget; bool m_betaUpdates; #endif }; } #endif // UPDATER_H ================================================ FILE: lib/unicorn/Updater/Updater_mac.mm ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QStringList> #include <../UnicornApplication.h> #include <../UnicornSettings.h> #include "Updater.h" #include <AppKit/NSNibDeclarations.h> #include <Foundation/Foundation.h> #include <Foundation/NSURL.h> #include <Sparkle/Sparkle.h> #include <Sparkle/SUUpdater.h> @interface UpdaterDelegate : NSObject { } @end @implementation UpdaterDelegate @end UpdaterDelegate* g_Delegate; unicorn::Updater::Updater(QWidget *parent) : QObject(parent) { SUUpdater* updater = [SUUpdater sharedUpdater]; g_Delegate = [UpdaterDelegate alloc]; [updater setDelegate:g_Delegate]; if ( qApp->arguments().contains( "--update" ) ) { int urlIndex = qApp->arguments().indexOf( "--update" ) + 1; if ( qApp->arguments().count() > urlIndex && qApp->arguments()[urlIndex].startsWith( "https://" ) ) [updater setFeedURL:[NSURL URLWithString: [NSString stringWithCharacters:(const unichar *)qApp->arguments()[urlIndex].unicode() length:(NSUInteger)qApp->arguments()[urlIndex].length() ]]]; else setBetaUpdates( unicorn::Settings().betaUpdates() ); } else setBetaUpdates( unicorn::Settings().betaUpdates() ); } void unicorn::Updater::setBetaUpdates( bool betaUpdates ) { if ( betaUpdates ) [[SUUpdater sharedUpdater] setFeedURL:[NSURL URLWithString:UPDATE_URL_MAC_BETA]]; else [[SUUpdater sharedUpdater] setFeedURL:[NSURL URLWithString:UPDATE_URL_MAC]]; } void unicorn::Updater::checkForUpdates() { [[SUUpdater sharedUpdater] checkForUpdates:0]; } unicorn::Updater::~Updater() { // do nothing - included here for header compatibility with windows version } ================================================ FILE: lib/unicorn/dialogs/AboutDialog.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "AboutDialog.h" #include <QApplication> #include <QIcon> #include <QLabel> #include <QVBoxLayout> #include "ui_AboutDialog.h" AboutDialog::AboutDialog( QWidget* parent ) : QDialog( parent ), ui( new Ui::AboutDialog ) { Q_ASSERT( qApp->applicationVersion().size() ); ui->setupUi( this ); ui->appName->setText( "<b>" + qApp->applicationName() ); ui->qtVersion->setText( tr( "%1 (built on Qt %2)" ).arg( qApp->applicationVersion(), qVersion() ) ); ui->lastfmLink->setText( "<a href='http://www.last.fm'>www.last.fm</a>" ); ui->ircLink->setText( "<a href='https://getsatisfaction.com/lastfm/categories/lastfm_apps'>Last.fm Client Support</a>" ); ui->copyright->setText( QString::fromUtf8("™ & © 2005 - 2019 Last.fm Limited") ); } ================================================ FILE: lib/unicorn/dialogs/AboutDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "lib/DllExportMacro.h" #include <QDialog> namespace Ui { class AboutDialog; } class UNICORN_DLLEXPORT AboutDialog : public QDialog { public: AboutDialog( QWidget* parent ); private: Ui::AboutDialog* ui; }; ================================================ FILE: lib/unicorn/dialogs/AboutDialog.ui ================================================ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>AboutDialog</class> <widget class="QDialog" name="AboutDialog"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>439</width> <height>306</height> </rect> </property> <property name="windowTitle"> <string>About</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <property name="spacing"> <number>0</number> </property> <property name="sizeConstraint"> <enum>QLayout::SetFixedSize</enum> </property> <property name="margin"> <number>0</number> </property> <item> <widget class="QFrame" name="frame"> <property name="frameShape"> <enum>QFrame::NoFrame</enum> </property> <property name="frameShadow"> <enum>QFrame::Raised</enum> </property> <layout class="QVBoxLayout" name="verticalLayout_3"> <property name="margin"> <number>0</number> </property> <item> <layout class="QHBoxLayout" name="horizontalLayout"> <property name="spacing"> <number>20</number> </property> <item> <widget class="QLabel" name="as"> <property name="text"> <string notr="true">TextLabel</string> </property> </widget> </item> <item> <layout class="QVBoxLayout" name="verticalLayout_2"> <property name="spacing"> <number>6</number> </property> <item> <spacer name="verticalSpacer_2"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>0</height> </size> </property> </spacer> </item> <item> <widget class="QLabel" name="appName"> <property name="text"> <string notr="true">TextLabel</string> </property> </widget> </item> <item> <widget class="QLabel" name="qtVersion"> <property name="text"> <string notr="true">TextLabel</string> </property> </widget> </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>0</height> </size> </property> </spacer> </item> </layout> </item> </layout> </item> <item> <widget class="QLabel" name="lastfmLink"> <property name="text"> <string notr="true">TextLabel</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> <property name="openExternalLinks"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QLabel" name="ircLink"> <property name="text"> <string notr="true">TextLabel</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> <property name="openExternalLinks"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QLabel" name="copyright"> <property name="text"> <string notr="true">TextLabel</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> </widget> </item> </layout> </widget> </item> </layout> </widget> <resources/> <connections/> </ui> ================================================ FILE: lib/unicorn/dialogs/CloseAppsDialog.cpp ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QTimer> #include <QPushButton> #include <QDebug> #ifdef Q_OS_WIN #include <windows.h> #include <psapi.h> #endif #ifndef Q_OS_MAC #include "../plugins/ITunesPluginInfo.h" #endif #include "CloseAppsDialog.h" #include "ui_CloseAppsDialog.h" unicorn::CloseAppsDialog::CloseAppsDialog( const QList<IPluginInfo*>& plugins, QWidget *parent ) :QDialog( parent ), ui(new Ui::CloseAppsDialog), m_plugins( plugins ), m_ownsPlugins( false ) { commonSetup(); } unicorn::CloseAppsDialog::CloseAppsDialog(QWidget *parent) : QDialog( parent ), ui(new Ui::CloseAppsDialog) { commonSetup(); } void unicorn::CloseAppsDialog::setTitle( const QString& title ) { setWindowTitle( title ); } void unicorn::CloseAppsDialog::setDescription( const QString& description ) { ui->text->setText( description ); } void unicorn::CloseAppsDialog::showPluginList( bool showPluginList ) { ui->listWidget->setVisible( showPluginList ); } void unicorn::CloseAppsDialog::commonSetup() { ui->setupUi(this); ui->text->setText( tr( "Please close the following apps to continue." ) ); if ( runningApps().count() == 0 ) hide(); checkApps(); QTimer* timer = new QTimer( this ); connect( timer, SIGNAL(timeout()), SLOT(checkApps()) ); timer->setInterval( 1000 ); timer->start(); } void unicorn::CloseAppsDialog::setOwnsPlugins( bool ownsPlugins ) { m_ownsPlugins = ownsPlugins; } bool unicorn::CloseAppsDialog::isITunesRunning() { QStringList apps; #ifndef Q_OS_MAC QList<IPluginInfo*> plugins; ITunesPluginInfo* iTunesPluginInfo = new ITunesPluginInfo; plugins << iTunesPluginInfo; apps = runningApps( plugins ); delete iTunesPluginInfo; #else apps = runningApps(); #endif return apps.count() == 1; } void unicorn::CloseAppsDialog::checkApps() { QStringList apps = runningApps(); ui->listWidget->setUpdatesEnabled( false ); ui->listWidget->clear(); foreach ( const QString& app, apps ) new QListWidgetItem( app, ui->listWidget ); ui->listWidget->setUpdatesEnabled( true ); if ( apps.count() == 0 ) accept(); } void unicorn::CloseAppsDialog::showEvent( QShowEvent* event ) { adjustSize(); } #ifndef Q_OS_MAC QStringList unicorn::CloseAppsDialog::runningApps() { return runningApps( m_plugins ); } QStringList unicorn::CloseAppsDialog::runningApps( const QList<IPluginInfo*>& plugins ) { QStringList apps; DWORD aProcesses[1024]; DWORD cbNeeded; DWORD cProcesses; unsigned int i; if ( EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) ) { // Calculate how many process identifiers were returned. cProcesses = cbNeeded / sizeof(DWORD); // Print the name and process identifier for each process. for ( i = 0; i < cProcesses; ++i ) { if( aProcesses[i] != 0 ) { TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>"); // Get a handle to the process. HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, aProcesses[i] ); if (NULL != hProcess ) { HMODULE hMod; DWORD cbNeeded; if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded) ) { GetModuleBaseName( hProcess, hMod, szProcessName, sizeof(szProcessName)/sizeof(TCHAR) ); foreach( IPluginInfo* plugin, plugins ) if ( plugin->processName().compare( QString::fromStdWString( szProcessName ), Qt::CaseInsensitive ) == 0 ) apps << plugin->name(); } } // Release the handle to the process. CloseHandle( hProcess ); } } } return apps; } #endif unicorn::CloseAppsDialog::~CloseAppsDialog() { delete ui; #ifdef Q_OS_WIN if ( m_ownsPlugins ) foreach ( IPluginInfo* plugin, m_plugins ) delete plugin; #endif } ================================================ FILE: lib/unicorn/dialogs/CloseAppsDialog.h ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef CLOSEAPPSDIALOG_H #define CLOSEAPPSDIALOG_H #include "lib/DllExportMacro.h" #include <QDialog> namespace Ui { class CloseAppsDialog; } namespace unicorn { class IPluginInfo; class UNICORN_DLLEXPORT CloseAppsDialog : public QDialog { Q_OBJECT public: explicit CloseAppsDialog( const QList<IPluginInfo*>& plugins, QWidget *parent ); explicit CloseAppsDialog(QWidget *parent = 0); ~CloseAppsDialog(); void setTitle( const QString& title ); void setDescription( const QString& description ); void showPluginList( bool showPluginList ); void setOwnsPlugins( bool ownsPlugins ); static bool isITunesRunning(); private: #ifndef Q_OS_MAC static QStringList runningApps( const QList<IPluginInfo*>& plugins ); #else static // this method is only statis on mac #endif QStringList runningApps(); void showEvent( QShowEvent* event ); private slots: void checkApps(); private: void commonSetup(); private: Ui::CloseAppsDialog *ui; QList<IPluginInfo*> m_plugins; bool m_ownsPlugins; }; } #endif // CLOSEAPPSDIALOG_H ================================================ FILE: lib/unicorn/dialogs/CloseAppsDialog.ui ================================================ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>CloseAppsDialog</class> <widget class="QDialog" name="CloseAppsDialog"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>313</width> <height>341</height> </rect> </property> <property name="windowTitle"> <string>Close Apps</string> </property> <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0"> <item> <widget class="unicorn::Label" name="text"> <property name="toolTip"> <string notr="true"/> </property> <property name="text"> <string notr="true">TextLabel</string> </property> <property name="textFormat"> <enum>Qt::RichText</enum> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QListWidget" name="listWidget"> <property name="selectionMode"> <enum>QAbstractItemView::NoSelection</enum> </property> </widget> </item> </layout> </widget> <customwidgets> <customwidget> <class>unicorn::Label</class> <extends>QLabel</extends> <header location="global">lib/unicorn/widgets/Label.h</header> </customwidget> </customwidgets> <resources/> <connections/> </ui> ================================================ FILE: lib/unicorn/dialogs/CloseAppsDialog_mac.mm ================================================ #include "CloseAppsDialog.h" QString qt_mac_NSStringToQString(const NSString *nsstr) { NSRange range; range.location = 0; range.length = [nsstr length]; unichar *chars = new unichar[range.length]; [nsstr getCharacters:chars range:range]; QString result = QString::fromUtf16(chars, range.length); delete[] chars; return result; } QStringList unicorn::CloseAppsDialog::runningApps() { // we only ever test for iTunes on Mac QStringList apps; // make sure iTunes isn't running NSArray* runningApps = [[NSWorkspace sharedWorkspace] runningApplications]; for ( unsigned int i = 0 ; i < [runningApps count] ; ++i ) { if ( [[[runningApps objectAtIndex:i] bundleIdentifier] isEqualToString:@"com.apple.iTunes"] ) { NSString* appName = [[runningApps objectAtIndex:i] localizedName]; apps << qt_mac_NSStringToQString( appName ); break; } } return apps; } ================================================ FILE: lib/unicorn/dialogs/LoginContinueDialog.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "LoginContinueDialog.h" #include <QtGui> #include <QDialogButtonBox> LoginContinueDialog::LoginContinueDialog( QWidget* parent ) : QDialog( parent ), m_subscriber( true ) { setWindowModality( Qt::ApplicationModal ); QVBoxLayout* layout = new QVBoxLayout( this ); layout->addWidget( ui.title = new QLabel( tr("Are we done?") ) ); ui.title->setObjectName( "title" ); layout->addWidget( ui.description = new QLabel( tr("Click OK once you have approved this app.") ) ); ui.title->setObjectName( "description" ); ui.title->setWordWrap( true ); layout->addWidget( ui.buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok ) ); connect( ui.buttonBox, SIGNAL(accepted()), SLOT(accept()) ); connect( ui.buttonBox, SIGNAL(rejected()), SLOT(reject()) ); } ================================================ FILE: lib/unicorn/dialogs/LoginContinueDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef LOGIN_CONTINUE_DIALOG_H #define LOGIN_CONTINUE_DIALOG_H #include "lib/DllExportMacro.h" #include <QDialog> #include <QDialogButtonBox> class UNICORN_DLLEXPORT LoginContinueDialog : public QDialog { Q_OBJECT private: struct { class QLabel* title; class QLabel* description; class QDialogButtonBox* buttonBox; } ui; public: LoginContinueDialog( QWidget* parent = 0 ); private: QPushButton* ok() const { return ui.buttonBox->button( QDialogButtonBox::Ok ); } private: bool m_subscriber; }; #endif ================================================ FILE: lib/unicorn/dialogs/LoginDialog.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "LoginDialog.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include <QtGui> LoginDialog::LoginDialog( QWidget* parent ) :QDialog( parent ) { setWindowModality( Qt::ApplicationModal ); QVBoxLayout* layout = new QVBoxLayout( this ); layout->addWidget( ui.title = new QLabel( tr("Last.fm needs your permission first!") ) ); ui.title->setObjectName( "title" ); layout->addWidget( ui.description = new QLabel( tr("This application needs your permission to connect to your Last.fm profile. Click OK to go to the Last.fm website and do this.") ) ); ui.description->setWordWrap( true ); ui.title->setObjectName( "description" ); ui.title->setWordWrap( true ); layout->addWidget( ui.buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel ) ); connect( ui.buttonBox, SIGNAL(accepted()), SLOT(accept()) ); connect( ui.buttonBox, SIGNAL(rejected()), SLOT(reject()) ); } ================================================ FILE: lib/unicorn/dialogs/LoginDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef LOGIN_DIALOG_H #define LOGIN_DIALOG_H #include "lib/DllExportMacro.h" #include "lib/unicorn/UnicornSession.h" #include <QDialog> #include <QDialogButtonBox> class UNICORN_DLLEXPORT LoginDialog : public QDialog { Q_OBJECT private: struct { class QLabel* title; class QLabel* description; class QDialogButtonBox* buttonBox; } ui; public: LoginDialog( QWidget* parent = 0 ); private: QPushButton* ok() const { return ui.buttonBox->button( QDialogButtonBox::Ok ); } }; #endif ================================================ FILE: lib/unicorn/dialogs/ProxyDialog.cpp ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "ProxyDialog.h" #include "ui_ProxyDialog.h" unicorn::ProxyDialog::ProxyDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ProxyDialog) { ui->setupUi(this); connect( this, SIGNAL(accepted()), SLOT(onAccepted())); setFixedSize( width(), height() ); } unicorn::ProxyDialog::~ProxyDialog() { delete ui; } void unicorn::ProxyDialog::onAccepted() { ui->proxySettings->save(); } ================================================ FILE: lib/unicorn/dialogs/ProxyDialog.h ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PROXYDIALOG_H #define PROXYDIALOG_H #include "lib/DllExportMacro.h" #include <QDialog> namespace Ui { class ProxyDialog; } namespace unicorn { class UNICORN_DLLEXPORT ProxyDialog : public QDialog { Q_OBJECT public: explicit ProxyDialog(QWidget *parent = 0); ~ProxyDialog(); private slots: void onAccepted(); private: Ui::ProxyDialog *ui; }; } #endif // PROXYDIALOG_H ================================================ FILE: lib/unicorn/dialogs/ProxyDialog.ui ================================================ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ProxyDialog</class> <widget class="QDialog" name="ProxyDialog"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>310</width> <height>200</height> </rect> </property> <property name="sizePolicy"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="windowTitle"> <string>Proxy Settings</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="unicorn::ProxyWidget" name="proxySettings" native="true"/> </item> <item> <widget class="QDialogButtonBox" name="buttonBox"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="standardButtons"> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> </property> </widget> </item> </layout> </widget> <customwidgets> <customwidget> <class>unicorn::ProxyWidget</class> <extends>QWidget</extends> <header>lib/unicorn/widgets/ProxyWidget.h</header> <container>1</container> </customwidget> </customwidgets> <resources/> <connections> <connection> <sender>buttonBox</sender> <signal>accepted()</signal> <receiver>ProxyDialog</receiver> <slot>accept()</slot> <hints> <hint type="sourcelabel"> <x>248</x> <y>254</y> </hint> <hint type="destinationlabel"> <x>157</x> <y>274</y> </hint> </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ProxyDialog</receiver> <slot>reject()</slot> <hints> <hint type="sourcelabel"> <x>316</x> <y>260</y> </hint> <hint type="destinationlabel"> <x>286</x> <y>274</y> </hint> </hints> </connection> </connections> </ui> ================================================ FILE: lib/unicorn/dialogs/ScrobbleConfirmationDialog.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QCheckBox> #include <QDialogButtonBox> #include <QHeaderView> #include <QLabel> #include <QPushButton> #include <QSortFilterProxyModel> #include <QTableView> #include <QVBoxLayout> #include <lastfm/ScrobbleCache.h> #include "../ScrobblesModel.h" #include "ScrobbleConfirmationDialog.h" #include "ui_ScrobbleConfirmationDialog.h" ScrobbleConfirmationDialog::ScrobbleConfirmationDialog( const QList<lastfm::Track>& tracks, QWidget* parent ) : QDialog( parent ), ui( new Ui::ScrobbleConfirmationDialog ) { ui->setupUi( this ); m_toggled = true; m_scrobblesModel = new ScrobblesModel( this ); QSortFilterProxyModel* proxyModel = new QSortFilterProxyModel( this ); proxyModel->setSourceModel( m_scrobblesModel ); ui->scrobblesView->setModel( proxyModel ); ui->scrobblesView->sortByColumn( ScrobblesModel::TimeStamp, Qt::DescendingOrder ); ui->scrobblesView->hideColumn( ScrobblesModel::Loved ); ui->scrobblesView->hideColumn( ScrobblesModel::Album ); connect( ui->toggleButton, SIGNAL( clicked() ), SLOT( toggleSelection() ) ); addTracks( tracks ); ui->invalidScrobbleWarning->setVisible( tracksToScrobble().count() != m_scrobblesModel->tracksToScrobble().count() ); } ScrobbleConfirmationDialog::~ScrobbleConfirmationDialog() { delete ui; } void ScrobbleConfirmationDialog::setReadOnly() { int count = 0; foreach ( const lastfm::Track& track, m_scrobblesModel->tracksToScrobble() ) count += track.extra( "playCount" ).toInt(); ui->infoText->setText( tr( "%n play(s) ha(s|ve) been scrobbled from a device", "", count ) ); ui->buttons->removeButton( ui->buttons->button( QDialogButtonBox::No ) ); ui->buttons->removeButton( ui->buttons->button( QDialogButtonBox::Yes ) ); ui->buttons->addButton( QDialogButtonBox::Ok ); ui->toggleButton->hide(); ui->autoScrobble->hide(); ui->invalidScrobbleWarning->hide(); m_scrobblesModel->setReadOnly(); } bool ScrobbleConfirmationDialog::autoScrobble() const { return ui->autoScrobble->isChecked(); } void ScrobbleConfirmationDialog::addTracks( const QList<lastfm::Track>& tracks ) { m_scrobblesModel->addTracks( tracks ); // a hack to get the view to sort for the added tracks ui->scrobblesView->setSortingEnabled( false ); ui->scrobblesView->setSortingEnabled( true ); ui->scrobblesView->horizontalHeader()->setResizeMode( QHeaderView::Interactive ); ui->scrobblesView->resizeColumnsToContents(); } void ScrobbleConfirmationDialog::addFiles( const QStringList& files ) { m_files << files; } const QStringList& ScrobbleConfirmationDialog::files() const { return m_files; } QList<lastfm::Track> ScrobbleConfirmationDialog::tracksToScrobble() const { QList<lastfm::Track> validTracks; foreach ( lastfm::Track track, m_scrobblesModel->tracksToScrobble() ) if ( lastfm::ScrobbleCache::isValid( track ) ) validTracks << track; return validTracks; } void ScrobbleConfirmationDialog::toggleSelection() { m_toggled = !m_toggled; for( int i = 0; i < m_scrobblesModel->rowCount(); i++ ) { QModelIndex idx = m_scrobblesModel->index( i, ScrobblesModel::Artist, QModelIndex() ); m_scrobblesModel->setData( idx, m_toggled, Qt::CheckStateRole ); } } ================================================ FILE: lib/unicorn/dialogs/ScrobbleConfirmationDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SCROBBLECONFIRMATIONDIALOG_H #define SCROBBLECONFIRMATIONDIALOG_H #include <QDialog> #include <lastfm/Track.h> #include "lib/DllExportMacro.h" namespace Ui { class ScrobbleConfirmationDialog; } class ScrobblesModel; class UNICORN_DLLEXPORT ScrobbleConfirmationDialog : public QDialog { Q_OBJECT public: ScrobbleConfirmationDialog( const QList<lastfm::Track>& tracks, QWidget* parent = 0 ); ~ScrobbleConfirmationDialog(); QList<lastfm::Track> tracksToScrobble() const; void addTracks( const QList<lastfm::Track>& tracks ); const QStringList& files() const; void addFiles( const QStringList& files ); void setReadOnly(); bool autoScrobble() const; private slots: void toggleSelection(); private: Ui::ScrobbleConfirmationDialog* ui; ScrobblesModel* m_scrobblesModel; bool m_toggled; QStringList m_files; }; #endif // SCROBBLECONFIRMATIONDIALOG_H ================================================ FILE: lib/unicorn/dialogs/ScrobbleConfirmationDialog.ui ================================================ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ScrobbleConfirmationDialog</class> <widget class="QDialog" name="ScrobbleConfirmationDialog"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>500</width> <height>450</height> </rect> </property> <property name="windowTitle"> <string>Device Scrobbles</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QLabel" name="infoText"> <property name="text"> <string>It looks like you've played these tracks. Would you like to scrobble them?</string> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QLabel" name="invalidScrobbleWarning"> <property name="text"> <string>Tracks appearing in red are invalid and will not be scrobbled. Hover your mouse over each track to find out why.</string> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QTableView" name="scrobblesView"> <property name="selectionMode"> <enum>QAbstractItemView::NoSelection</enum> </property> <property name="showGrid"> <bool>false</bool> </property> <property name="sortingEnabled"> <bool>true</bool> </property> <property name="wordWrap"> <bool>false</bool> </property> <property name="cornerButtonEnabled"> <bool>false</bool> </property> <attribute name="horizontalHeaderCascadingSectionResizes"> <bool>true</bool> </attribute> <attribute name="horizontalHeaderStretchLastSection"> <bool>true</bool> </attribute> <attribute name="verticalHeaderVisible"> <bool>false</bool> </attribute> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QCheckBox" name="autoScrobble"> <property name="layoutDirection"> <enum>Qt::LeftToRight</enum> </property> <property name="text"> <string>Scrobble devices automatically</string> </property> <property name="tristate"> <bool>false</bool> </property> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QPushButton" name="toggleButton"> <property name="text"> <string>Toggle selection</string> </property> </widget> </item> <item> <widget class="QDialogButtonBox" name="buttons"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="standardButtons"> <set>QDialogButtonBox::No|QDialogButtonBox::Yes</set> </property> </widget> </item> </layout> </item> </layout> </widget> <resources/> <connections> <connection> <sender>buttons</sender> <signal>accepted()</signal> <receiver>ScrobbleConfirmationDialog</receiver> <slot>accept()</slot> <hints> <hint type="sourcelabel"> <x>248</x> <y>254</y> </hint> <hint type="destinationlabel"> <x>157</x> <y>274</y> </hint> </hints> </connection> <connection> <sender>buttons</sender> <signal>rejected()</signal> <receiver>ScrobbleConfirmationDialog</receiver> <slot>reject()</slot> <hints> <hint type="sourcelabel"> <x>316</x> <y>260</y> </hint> <hint type="destinationlabel"> <x>286</x> <y>274</y> </hint> </hints> </connection> </connections> </ui> ================================================ FILE: lib/unicorn/dialogs/ShareDialog.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QApplication> #include <QPainter> #include <QTimer> #include <QPushButton> #include <QVBoxLayout> #include <QLabel> #include <QPlainTextEdit> #include <QEvent> #include <QRadioButton> #include <QComboBox> #include <QListWidget> #include <QDebug> #include <QCheckBox> #include <QDesktopWidget> #include <lastfm/User.h> #include <lastfm/XmlQuery.h> #include "lib/unicorn/widgets/TrackWidget.h" #include "lib/unicorn/DesktopServices.h" #include "lib/unicorn/TrackImageFetcher.h" #include "ShareDialog.h" #include "ui_ShareDialog.h" #include "../widgets/ItemSelectorWidget.h" const int kMaxMessage(1000); ShareDialog::ShareDialog( const Track& track, QWidget* parent ) : unicorn::Dialog( parent, Qt::Tool ), ui( new Ui::ShareDialog ), m_track( track ) { ui->setupUi( this ); ui->recipients->setType( ItemSelectorWidget::User ); ui->icon->setScaledContents( true ); ui->icon->setHref( m_track.www() ); enableDisableOk(); connect( ui->message, SIGNAL(textChanged()), SLOT(updateCharacterLimit())); connect( ui->message, SIGNAL(textChanged()), SLOT(enableDisableOk())); connect( ui->recipients, SIGNAL(changed()), SLOT(enableDisableOk())); ui->title->setText( unicorn::Label::anchor( m_track.www().toString(), m_track.title() ) ); if ( m_track.album().isNull() ) ui->description->setText( tr( "A track by %1" ).arg( unicorn::Label::anchor( m_track.artist().www().toString(), m_track.artist().name() ) ) ); else ui->description->setText( tr( "A track by %1 from the release %2" ).arg( unicorn::Label::anchor( m_track.artist().www().toString(), m_track.artist().name() ), unicorn::Label::anchor( m_track.album().www().toString(), m_track.album() ) ) ); m_imageFetcher = new TrackImageFetcher( m_track, Track::MediumImage ); connect( m_imageFetcher, SIGNAL(finished(QPixmap)), ui->icon, SLOT(setPixmap(QPixmap)) ); m_imageFetcher->startAlbum(); QPushButton* dummyDefault = ui->buttons->addButton( QDialogButtonBox::Help ); dummyDefault->setDefault( true ); dummyDefault->setAutoDefault( true ); dummyDefault->setVisible( false ); } void ShareDialog::enableDisableOk() { ui->buttons->button( QDialogButtonBox::Ok )->setEnabled( ui->recipients->items().count() > 0 && ui->message->toPlainText().length() <= kMaxMessage ); } void ShareDialog::updateCharacterLimit() { ui->characterLimit->setText( QString::number( ui->message->toPlainText().length() ) + "/" + QString::number(kMaxMessage) ); if ( ui->message->toPlainText().length() > kMaxMessage ) { ui->characterLimit->setProperty( "xerror", true ); } else ui->characterLimit->setProperty( "xerror", false ); style()->polish( ui->characterLimit ); } void ShareDialog::onMessageChanged() { // update the character message updateCharacterLimit(); } void ShareDialog::accept() { QStringList recipients( ui->recipients->items() ); QString const message = ui->message->toPlainText(); bool isPublic = ui->isPublic->isChecked(); // disable the dialog until we get a response from Last.fm setEnabled( false ); connect( m_track.share( recipients, message, isPublic ), SIGNAL(finished()), SLOT(onShared()) ); } void ShareDialog::shareTwitter( const Track& track ) { QUrl twitterShareIntent( "http://twitter.com/intent/tweet" ); twitterShareIntent.addEncodedQueryItem( "text", QUrl::toPercentEncoding( tr("Check out %1").arg( track.toString() ) ) ); twitterShareIntent.addEncodedQueryItem( "url", QUrl::toPercentEncoding( track.www().toEncoded() ) ); twitterShareIntent.addQueryItem( "via", "lastfm" ); twitterShareIntent.addQueryItem( "related", "lastfm,lastfmpresents" ); unicorn::DesktopServices::openUrl( twitterShareIntent ); } void ShareDialog::shareFacebook( const Track& track ) { QUrl facebookShareIntent( "http://www.facebook.com/sharer.php" ); facebookShareIntent.addEncodedQueryItem( "t", QUrl::toPercentEncoding( track.toString() ) ); facebookShareIntent.addEncodedQueryItem( "u", QUrl::toPercentEncoding( track.www().toEncoded() ) ); unicorn::DesktopServices::openUrl( facebookShareIntent ); } void ShareDialog::onShared() { XmlQuery lfm; if ( lfm.parse( qobject_cast<QNetworkReply*>(sender()) ) ) { if ( lfm.attribute( "status" ) == "ok" ) close(); else { // TODO: display some kind of error message setEnabled( true ); } } else { // TODO: display some kind of error message setEnabled( true ); } } ================================================ FILE: lib/unicorn/dialogs/ShareDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SHARE_DIALOG_H #define SHARE_DIALOG_H #include <lastfm/Track.h> #include <QDialogButtonBox> #include "UnicornDialog.h" #include "lib/DllExportMacro.h" namespace Ui { class ShareDialog; } class UNICORN_DLLEXPORT ShareDialog : public unicorn::Dialog { Q_OBJECT public: ShareDialog( const Track& track, QWidget* parent = 0 ); Track track() const { return m_track; } static void shareTwitter( const Track& track ); static void shareFacebook( const Track& track ); private slots: void enableDisableOk(); void accept(); void onMessageChanged(); void onShared(); void updateCharacterLimit(); private: Ui::ShareDialog* ui; Track m_track; class TrackImageFetcher* m_imageFetcher; }; #endif ================================================ FILE: lib/unicorn/dialogs/ShareDialog.ui ================================================ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ShareDialog</class> <widget class="QDialog" name="ShareDialog"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>435</width> <height>418</height> </rect> </property> <property name="windowTitle"> <string>Share with Friends</string> </property> <property name="modal"> <bool>true</bool> </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <property name="spacing"> <number>0</number> </property> <property name="margin"> <number>18</number> </property> <item> <widget class="QFrame" name="what"> <property name="frameShape"> <enum>QFrame::StyledPanel</enum> </property> <property name="frameShadow"> <enum>QFrame::Raised</enum> </property> <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1"> <property name="spacing"> <number>0</number> </property> <property name="margin"> <number>0</number> </property> <item> <widget class="HttpImageWidget" name="icon"> <property name="text"> <string notr="true">icon</string> </property> </widget> </item> <item> <layout class="QVBoxLayout" name="verticalLayout"> <property name="spacing"> <number>0</number> </property> <item> <widget class="unicorn::Label" name="title"> <property name="text"> <string notr="true">TextLabel</string> </property> <property name="textFormat"> <enum>Qt::RichText</enum> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> <item> <widget class="unicorn::Label" name="description"> <property name="text"> <string notr="true">TextLabel</string> </property> <property name="textFormat"> <enum>Qt::RichText</enum> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>0</height> </size> </property> </spacer> </item> </layout> </item> </layout> </widget> </item> <item> <widget class="QLabel" name="with"> <property name="font"> <font> <weight>75</weight> <bold>true</bold> </font> </property> <property name="text"> <string>With:</string> </property> </widget> </item> <item> <widget class="ItemSelectorWidget" name="recipients"> <property name="frameShape"> <enum>QFrame::StyledPanel</enum> </property> <property name="frameShadow"> <enum>QFrame::Raised</enum> </property> </widget> </item> <item> <widget class="QLabel" name="messageTitle"> <property name="font"> <font> <weight>75</weight> <bold>true</bold> </font> </property> <property name="text"> <string>Message (optional):</string> </property> </widget> </item> <item> <widget class="QTextEdit" name="message"> <property name="tabChangesFocus"> <bool>true</bool> </property> <property name="acceptRichText"> <bool>true</bool> </property> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0"> <property name="spacing"> <number>0</number> </property> <item> <widget class="QCheckBox" name="isPublic"> <property name="text"> <string>include in my recent activity</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QLabel" name="characterLimit"> <property name="text"> <string notr="true">0/1000</string> </property> </widget> </item> </layout> </item> <item> <widget class="QDialogButtonBox" name="buttons"> <property name="standardButtons"> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> </property> </widget> </item> </layout> </widget> <customwidgets> <customwidget> <class>unicorn::Label</class> <extends>QLabel</extends> <header>lib/unicorn/widgets/Label.h</header> </customwidget> <customwidget> <class>ItemSelectorWidget</class> <extends>QFrame</extends> <header>lib/unicorn/widgets/ItemSelectorWidget.h</header> <container>1</container> </customwidget> <customwidget> <class>HttpImageWidget</class> <extends>QLabel</extends> <header>lib/unicorn/widgets/HttpImageWidget.h</header> </customwidget> </customwidgets> <resources/> <connections> <connection> <sender>buttons</sender> <signal>accepted()</signal> <receiver>ShareDialog</receiver> <slot>accept()</slot> <hints> <hint type="sourcelabel"> <x>81</x> <y>280</y> </hint> <hint type="destinationlabel"> <x>4</x> <y>234</y> </hint> </hints> </connection> <connection> <sender>buttons</sender> <signal>rejected()</signal> <receiver>ShareDialog</receiver> <slot>reject()</slot> <hints> <hint type="sourcelabel"> <x>113</x> <y>272</y> </hint> <hint type="destinationlabel"> <x>-7</x> <y>271</y> </hint> </hints> </connection> </connections> </ui> ================================================ FILE: lib/unicorn/dialogs/TagDialog.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QtCore> #include <QtGui> #include <QPushButton> #include <lastfm/XmlQuery.h> #include <lastfm/User.h> #include "../widgets/SpinnerLabel.h" #include "../widgets/TrackWidget.h" #include "../widgets/UnicornTabWidget.h" #include "../widgets/ItemSelectorWidget.h" #include "../widgets/DataListWidget.h" #include "../widgets/TagListWidget.h" #include "../TrackImageFetcher.h" #include "ui_TagDialog.h" #include "TagDialog.h" TagDialog::TagDialog( const Track& track, QWidget *parent ) : unicorn::Dialog( parent, Qt::Tool ), ui( new Ui::TagDialog ) { ui->setupUi( this ); m_track = track; ui->icon->setScaledContents( true ); ui->icon->setHref( m_track.www() ); ui->tags->setType( ItemSelectorWidget::Tag ); layout()->setSizeConstraint( QLayout::SetFixedSize ); setSizeGripEnabled( false ); ui->title->setText( unicorn::Label::anchor( m_track.www().toString(), m_track.title() ) ); if ( m_track.album().isNull() ) { ui->description->setText( tr( "A track by %1" ).arg( unicorn::Label::anchor( m_track.artist().www().toString(), m_track.artist().name() ) ) ); ui->album->setEnabled( false ); } else ui->description->setText( tr( "A track by %1 from the release %2" ).arg( unicorn::Label::anchor( m_track.artist().www().toString(), m_track.artist().name() ), unicorn::Label::anchor( m_track.album().www().toString(), m_track.album() ) ) ); m_imageFetcher = new TrackImageFetcher( m_track, Track::MediumImage ); connect( m_imageFetcher, SIGNAL(finished(QPixmap)), ui->icon, SLOT(setPixmap(QPixmap)) ); m_imageFetcher->startAlbum(); ui->buttonBox->button( QDialogButtonBox::Ok )->setText( tr("Tag") ); connect( ui->buttonBox, SIGNAL(accepted()), SLOT(onAccepted()) ); connect( ui->buttonBox, SIGNAL(rejected()), SLOT(reject()) ); connect( ui->tags, SIGNAL(changed()), SLOT(enableDisableOk())); enableDisableOk(); QPushButton* dummyDefault = ui->buttonBox->addButton( QDialogButtonBox::Help ); dummyDefault->setDefault( true ); dummyDefault->setAutoDefault( true ); dummyDefault->setVisible( false ); ui->track->click(); } void TagDialog::enableDisableOk() { ui->buttonBox->button( QDialogButtonBox::Ok )->setEnabled( ui->tags->items().count() > 0 ); } void TagDialog::onAccepted() { // call the ws for tagging QNetworkReply* reply; if ( ui->track->isChecked() ) reply = m_track.addTags( ui->tags->items() ); else if ( ui->album->isChecked() ) reply = m_track.album().addTags( ui->tags->items() ); else reply = m_track.artist().addTags( ui->tags->items() ); connect( reply, SIGNAL(finished()), SLOT(onAddTagFinished())); setEnabled( false ); } void TagDialog::onAddTagFinished() { XmlQuery lfm; if ( lfm.parse( qobject_cast<QNetworkReply*>(sender()) ) ) { if ( lfm.attribute( "status" ) == "ok" ) close(); else { // TODO: display some kind of error message setEnabled( true ); } } else { // TODO: display some kind of error message setEnabled( true ); } } ================================================ FILE: lib/unicorn/dialogs/TagDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef TAG_DIALOG_H #define TAG_DIALOG_H #include <lastfm/Track.h> #include <QModelIndex> #include "UnicornDialog.h" #include "lib/DllExportMacro.h" namespace Ui { class TagDialog; } class UNICORN_DLLEXPORT TagDialog : public unicorn::Dialog { Q_OBJECT public: TagDialog( const Track&, QWidget* parent ); Track track() const { return m_track; } private slots: void onAddTagFinished(); void onAccepted(); void enableDisableOk(); private: Ui::TagDialog* ui; Track m_track; class TrackImageFetcher* m_imageFetcher; }; #endif ================================================ FILE: lib/unicorn/dialogs/TagDialog.ui ================================================ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>TagDialog</class> <widget class="QDialog" name="TagDialog"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>403</width> <height>300</height> </rect> </property> <property name="windowTitle"> <string>Tag</string> </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QLabel" name="choose"> <property name="text"> <string>Choose something to tag:</string> </property> </widget> </item> <item> <widget class="QRadioButton" name="track"> <property name="text"> <string>Track</string> </property> </widget> </item> <item> <widget class="QRadioButton" name="artist"> <property name="text"> <string>Artist</string> </property> </widget> </item> <item> <widget class="QRadioButton" name="album"> <property name="text"> <string>Album</string> </property> </widget> </item> </layout> </item> <item> <widget class="QFrame" name="what"> <property name="frameShape"> <enum>QFrame::StyledPanel</enum> </property> <property name="frameShadow"> <enum>QFrame::Raised</enum> </property> <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1"> <property name="spacing"> <number>0</number> </property> <property name="margin"> <number>0</number> </property> <item> <widget class="HttpImageWidget" name="icon"> <property name="text"> <string>icon</string> </property> </widget> </item> <item> <layout class="QVBoxLayout" name="verticalLayout"> <property name="spacing"> <number>0</number> </property> <item> <widget class="unicorn::Label" name="title"> <property name="text"> <string notr="true">TextLabel</string> </property> <property name="textFormat"> <enum>Qt::RichText</enum> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> <item> <widget class="unicorn::Label" name="description"> <property name="text"> <string notr="true">TextLabel</string> </property> <property name="textFormat"> <enum>Qt::RichText</enum> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>0</height> </size> </property> </spacer> </item> </layout> </item> </layout> </widget> </item> <item> <widget class="QLabel" name="addTags"> <property name="text"> <string>Add tags:</string> </property> </widget> </item> <item> <widget class="ItemSelectorWidget" name="tags"> <property name="frameShape"> <enum>QFrame::StyledPanel</enum> </property> <property name="frameShadow"> <enum>QFrame::Raised</enum> </property> </widget> </item> <item> <widget class="QDialogButtonBox" name="buttonBox"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="standardButtons"> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> </property> </widget> </item> </layout> </widget> <customwidgets> <customwidget> <class>HttpImageWidget</class> <extends>QLabel</extends> <header>lib/unicorn/widgets/HttpImageWidget.h</header> </customwidget> <customwidget> <class>unicorn::Label</class> <extends>QLabel</extends> <header>lib/unicorn/widgets/Label.h</header> </customwidget> <customwidget> <class>ItemSelectorWidget</class> <extends>QFrame</extends> <header>lib/unicorn/widgets/ItemSelectorWidget.h</header> <container>1</container> </customwidget> </customwidgets> <resources/> <connections> <connection> <sender>buttonBox</sender> <signal>accepted()</signal> <receiver>TagDialog</receiver> <slot>accept()</slot> <hints> <hint type="sourcelabel"> <x>248</x> <y>254</y> </hint> <hint type="destinationlabel"> <x>157</x> <y>274</y> </hint> </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>TagDialog</receiver> <slot>reject()</slot> <hints> <hint type="sourcelabel"> <x>316</x> <y>260</y> </hint> <hint type="destinationlabel"> <x>286</x> <y>274</y> </hint> </hints> </connection> </connections> </ui> ================================================ FILE: lib/unicorn/dialogs/UnicornDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UNICORN_DIALOG_H_ #define UNICORN_DIALOG_H_ #include <QDialog> #include <QApplication> #include <QDesktopWidget> #include "../UnicornMainWindow.h" #include "../UnicornSettings.h" #include <math.h> #include "lib/DllExportMacro.h" namespace unicorn { class UNICORN_DLLEXPORT Dialog : public QDialog { Q_OBJECT public: Dialog( QWidget* parent = 0, Qt::WindowFlags f = 0 ) :QDialog( parent, f ), m_firstShow( true ) { connect( qApp->desktop(), SIGNAL( resized(int)), SLOT( cleverlyPosition())); } virtual void setVisible( bool visible ) { if( !visible || !m_firstShow ) return QDialog::setVisible( visible ); m_firstShow = false; cleverlyPosition(); return QDialog::setVisible( visible ); } protected: void saveState( const QObject* object, const QString& key, const QVariant& value ) const { AppSettings s; s.beginGroup( QString( metaObject()->className())); s.beginGroup( QString( object->metaObject()->className())); s.setValue( key, value ); } QVariant restoreState( const QObject* object, const QString& key, const QVariant& defaultValue = QVariant() ) const { AppSettings s; s.beginGroup( QString( metaObject()->className())); s.beginGroup( QString( object->metaObject()->className())); return s.value( key, defaultValue ); } virtual void moveEvent( QMoveEvent* event ) { using unicorn::MainWindow; QWidget* mw = findMainWindow(); if( !mw ) return QDialog::moveEvent( event ); AppSettings s; s.beginGroup( QString( metaObject()->className() )); s.setValue( "position", (pos() - mw->pos())); return QDialog::moveEvent( event ); } private slots: void cleverlyPosition() { //Clever positioning QWidget* mw = findMainWindow(); if( !mw ) return; resize( sizeHint() ); AppSettings s; s.beginGroup( QString( metaObject()->className() )); QPoint offset = s.value( "position", QPoint( 40, 40 )).toPoint(); s.endGroup(); move( mw->pos() + offset); int screenNum = qApp->desktop()->screenNumber( mw ); QRect screenRect = qApp->desktop()->availableGeometry( screenNum ); if( !screenRect.contains( frameGeometry(), true)) { QRect diff; diff = screenRect.intersected( frameGeometry() ); if (diff.left() == screenRect.left() ) move( diff.left(), pos().y()); if( diff.right() == screenRect.right()) move( diff.right() - width(), pos().y()); if( diff.top() == screenRect.top()) move( pos().x(), diff.top()); if( diff.bottom() == screenRect.bottom()) move( pos().x(), diff.bottom() - height()); } } private: bool m_firstShow; QWidget* findMainWindow() const { unicorn::MainWindow* mw = 0; foreach( QWidget* w, qApp->topLevelWidgets() ) { mw = qobject_cast<unicorn::MainWindow*>( w ); if( mw ) break; } return mw; }; }; } //unicorn #endif //UNICORN_DIALOG_H_ ================================================ FILE: lib/unicorn/dialogs/UserManagerDialog.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "UserManagerDialog.h" #include <lastfm/User.h> #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/widgets/UserManagerWidget.h" #include <QApplication> #include <QDebug> #include <QDialogButtonBox> #include <QGroupBox> #include <QHBoxLayout> #include <QLabel> #include <QMouseEvent> #include <QPushButton> #include <QTimer> #include <QVBoxLayout> UserManagerDialog::UserManagerDialog( QWidget* parent ) :QDialog( parent ) { m_users = new UserManagerWidget( this ); QVBoxLayout* layout = new QVBoxLayout(); QHBoxLayout* actionButtons = new QHBoxLayout(); QDialogButtonBox* bb; actionButtons->addWidget( bb = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel )); layout->addWidget( m_users ); layout->addLayout( actionButtons ); setLayout( layout ); connect( m_users, SIGNAL( rosterUpdated() ), this, SIGNAL( rosterUpdated() ) ); connect( bb, SIGNAL( accepted()), SLOT( onAccept())); connect( bb, SIGNAL( rejected()), SLOT( reject())); } UserManagerDialog::~UserManagerDialog() { } void UserManagerDialog::onAccept() { unicorn::Settings s; UserRadioButton* urb = m_users->checkedButton(); if( !urb || urb->user() == User().name()) return QDialog::reject(); s.setValue( "Username", urb->user()); QDialog::accept(); } ================================================ FILE: lib/unicorn/dialogs/UserManagerDialog.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef USER_MANAGER_DIALOG_H_ #define USER_MANAGER_DIALOG_H_ #include <QDialog> #include <QRadioButton> #include "lib/DllExportMacro.h" namespace lastfm{ class User; } class UserManagerWidget; class QLabel; class QFrame; class QPushButton; class UNICORN_DLLEXPORT UserManagerDialog : public QDialog { Q_OBJECT public: UserManagerDialog( QWidget* parent = 0 ); ~UserManagerDialog(); signals: void rosterUpdated(); protected slots: void onAccept(); private: UserManagerWidget* m_users; }; #endif //USER_MANAGER_DIALOG_H_ ================================================ FILE: lib/unicorn/layouts/FlowLayout.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://www.qtsoftware.com/contact. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include <QtGui> #include "FlowLayout.h" //! [1] FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing), m_oneLine( false ) { setContentsMargins(margin, margin, margin, margin); } FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) : m_hSpace(hSpacing), m_vSpace(vSpacing), m_oneLine( false ) { setContentsMargins(margin, margin, margin, margin); } //! [1] //! [2] FlowLayout::~FlowLayout() { QLayoutItem *item; while ((item = takeAt(0))) delete item; } //! [2] //! [3] void FlowLayout::addItem(QLayoutItem *item) { itemList.append(item); } //! [3] void FlowLayout::removeItem(QLayoutItem *item) { itemList.removeAt(itemList.indexOf(item)); invalidate(); } void FlowLayout::insertWidget(int index, QWidget* widget) { // don't allow duplicates! addChildWidget(widget); QWidgetItem* item = new QWidgetItem(widget); itemList.insert(index, item); invalidate(); } //! [4] int FlowLayout::horizontalSpacing() const { if (m_hSpace >= 0) { return m_hSpace; } else { return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); } } int FlowLayout::verticalSpacing() const { if (m_vSpace >= 0) { return m_vSpace; } else { return smartSpacing(QStyle::PM_LayoutVerticalSpacing); } } //! [4] //! [5] int FlowLayout::count() const { return itemList.count(); } QLayoutItem *FlowLayout::itemAt(int index) const { return itemList.value(index); } QLayoutItem *FlowLayout::takeAt(int index) { if (index >= 0 && index < itemList.size()) return itemList.takeAt(index); else return 0; } //! [5] //! [6] Qt::Orientations FlowLayout::expandingDirections() const { return 0; } //! [6] //! [7] bool FlowLayout::hasHeightForWidth() const { return true; } int FlowLayout::heightForWidth(int width) const { int height = doLayout(QRect(0, 0, width, 0), true); return height; } //! [7] //! [8] void FlowLayout::setGeometry(const QRect &rect) { QLayout::setGeometry(rect); doLayout(rect, false); } QSize FlowLayout::sizeHint() const { return minimumSize(); } QSize FlowLayout::minimumSize() const { QSize size; QLayoutItem *item; foreach (item, itemList) size = size.expandedTo(item->minimumSize()); size += QSize(2*margin(), 2*margin()); return size; } //! [8] //! [9] int FlowLayout::doLayout(const QRect &rect, bool testOnly) const { int left, top, right, bottom; getContentsMargins(&left, &top, &right, &bottom); QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); int x = effectiveRect.x(); int y = effectiveRect.y(); int lineHeight = 0; //! [9] //! [10] QLayoutItem *item; foreach (item, itemList) { QWidget *wid = item->widget(); int spaceX = horizontalSpacing(); if (spaceX == -1) spaceX = wid->style()->layoutSpacing( QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); int spaceY = verticalSpacing(); if (spaceY == -1) spaceY = wid->style()->layoutSpacing( QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); //! [10] //! [11] int nextX = x + item->sizeHint().width() + spaceX; if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { if ( m_oneLine ) { item->setGeometry( QRect( 0, 0, 0, 0 ) ); continue; } x = effectiveRect.x(); y = y + lineHeight + spaceY; nextX = x + item->sizeHint().width() + spaceX; lineHeight = 0; } if (!testOnly) item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); x = nextX; lineHeight = qMax(lineHeight, item->sizeHint().height()); } return y + lineHeight - rect.y() + bottom; } //! [11] //! [12] int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const { QObject *parent = this->parent(); if (!parent) { return -1; } else if (parent->isWidgetType()) { QWidget *pw = static_cast<QWidget *>(parent); return pw->style()->pixelMetric(pm, 0, pw); } else { return static_cast<QLayout *>(parent)->spacing(); } } //! [12] ================================================ FILE: lib/unicorn/layouts/FlowLayout.h ================================================ /**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://www.qtsoftware.com/contact. ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef FLOWLAYOUT_H #define FLOWLAYOUT_H #include <QLayout> #include <QRect> #include <QWidgetItem> #include <QStyle> #include <lib/DllExportMacro.h> //! [0] class UNICORN_DLLEXPORT FlowLayout : public QLayout { public: FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); ~FlowLayout(); void setOneLine( bool oneLine ) { m_oneLine = oneLine; } void addItem(QLayoutItem *item); void removeItem(QLayoutItem *item); void insertWidget(int index, QWidget* widget); int horizontalSpacing() const; int verticalSpacing() const; Qt::Orientations expandingDirections() const; bool hasHeightForWidth() const; int heightForWidth(int) const; int count() const; QLayoutItem *itemAt(int index) const; QSize minimumSize() const; void setGeometry(const QRect &rect); QSize sizeHint() const; QLayoutItem *takeAt(int index); private: int doLayout(const QRect &rect, bool testOnly) const; int smartSpacing(QStyle::PixelMetric pm) const; private: QList<QLayoutItem *> itemList; int m_hSpace; int m_vSpace; bool m_oneLine; }; //! [0] #endif ================================================ FILE: lib/unicorn/mac/AppleScript.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "AppleScript.h" #include <QDebug> ComponentInstance AppleScript::s_component = NULL; AppleScript::AppleScript( const QString& code ) : m_compiled_script( kOSANullScript ), m_code( code ) { if (!s_component) s_component = OpenDefaultComponent( kOSAComponentType, typeAppleScript ); } AppleScript::~AppleScript() { OSADispose( s_component, m_compiled_script ); } bool //static AppleScript::isAppleScriptAvailable() { #ifdef Q_OS_MAC64 SInt32 r; #else long r; #endif if (Gestalt( gestaltAppleScriptAttr, &r ) != noErr) r = 0; return (r & (1 << gestaltAppleScriptPresent)) != 0; } static inline QString qStringFromAEDesc( AEDesc d ) { const int N = AEGetDescDataSize( &d ) + 1; QByteArray r( N, '\0' ); AEGetDescData( &d, r.data(), r.size() ); // sometimes iTunes gives us "bonus" nulls for each returned applescript // string component return QString::fromUtf8( r.data(), r.size() ).remove( QChar::Null ); } void AppleScript::logError() { AEDesc d; OSAScriptError( s_component, kOSAErrorMessage, typeUTF8Text, &d ); qWarning() << "ERROR: AppleScript:" << m_compiled_script << '-' << qStringFromAEDesc( d ); } bool AppleScript::compile() { QByteArray const utf8 = m_code.toUtf8(); void const* text = utf8.data(); long const textLength = utf8.length(); OSStatus err; AEDesc d; err = AECreateDesc( typeUTF8Text, text, textLength, &d ); if( err != noErr ) { logError(); return false; } err = OSACompile( s_component, &d, kOSAModeNull, &m_compiled_script ); //qDebug() << "Compiled:" << m_compiled_script << '\n' << m_code.trimmed(); if (err != noErr) { logError(); return false; } return true; } /** NOTE old, unedited text may not all apply:- * * LowRunAppleScript compiles and runs an AppleScript * provided as text in the buffer pointed to by text. textLength * bytes will be compiled from this buffer and run as an AppleScript * using all of the default environment and execution settings. If * resultData is not NULL, then the result returned by the execution * command will be returned as typeChar in this descriptor record * (or typeNull if there is no result information). If the function * returns errOSAScriptError, then resultData will be set to a * descriptive error message describing the error (if one is * available). */ QString AppleScript::exec() { if (m_compiled_script == kOSANullScript) { if( !compile() ) return ""; } OSAID id = kOSANullScript; OSStatus err = OSAExecute( s_component, m_compiled_script, kOSANullScript, kOSAModeAlwaysInteract, &id ); if (err != noErr) { logError(); return ""; } if (id == kOSANullScript) // means script succeeded with no output return ""; AEDesc r; OSADisplay( s_component, id, typeUTF8Text, kOSAModeNull, &r ); QString s = qStringFromAEDesc( r ); OSADispose( s_component, id ); // strip surrounding quotes if any if (s.startsWith( '"' ) && s.endsWith( '"' )) { s = s.mid( 1, s.length() - 2 ); } // iTunes also likes to escape quotes s.replace( "\\\"", "\"" ); //qDebug() << "Output from script:" << m_compiled_script << s; return s; } /** Due to the fact Apple Script can't work with Unicode characters directly * written by Qt into the script, we convert the Unicode data from a QString * into an AppleScript function + the hex values of the QString data (for * example: "<<data utxt00AB00BB>> as unicode text") * NOTE AppleScript 2 supports full unicode without this crap, but this works * with all versions */ QString //static AppleScript::asUnicodeText( const QString& string ) { if (string.isEmpty()) return string; QString const OPEN = QChar(0x00AB) + QString("data utxt"); QString const CLOSE = QChar(0x00BB) + QString(" as unicode text"); QString r; for (int i = 0, j = 0; i < string.length(); i++) { QString hexadecimal = QString::number( string[i].unicode(), 16 ); r += hexadecimal.rightJustified( 4, '0' ); // AppleScript is only able to work with 252 / 4 unicode chars at once // (haha, yeah not 64 - the "utxt" prefix counts also and is 4 chars // long, so 64 - 1 = 63), everything else would cause a compilation // error. Therefore we split the string every 63 bytes and begin a new // "<<data utxt..." function. if (!(++j &= 63)) { r += OPEN + " & " + CLOSE; } } return OPEN + r + CLOSE; } ================================================ FILE: lib/unicorn/mac/AppleScript.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef CORE_APPLE_SCRIPT_H #define CORE_APPLE_SCRIPT_H #ifdef __APPLE__ #include <QString> #include <Carbon/Carbon.h> // is there a less huge header option? /** @author Max Howell <max@last.fm> */ class AppleScript { public: AppleScript( const QString& code = "" ); ~AppleScript(); /** add a whole line, newlines are added after every call to this function! * escapes ' to \" for convenience * NOTE calling exec() and then using this function is unsupported */ AppleScript& operator<<( QString line ) { m_code += line.replace( '\'', '"' ); m_code += '\n'; return *this; } /** execs script set with setScript() * @returns script output */ QString exec(); /** AppleScript hates unicode, encode unicode strings with this */ static QString asUnicodeText( const QString& ); /** if false, you're screwed, we've never yet seen that though */ static bool isAppleScriptAvailable(); bool isEmpty() const { return m_code.isEmpty(); } QString code() const { return m_code; } private: bool compile(); void logError(); private: OSAID m_compiled_script; QString m_code; static ComponentInstance s_component; }; #endif #endif ================================================ FILE: lib/unicorn/notify/Notify.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef NOTIFY_H #define NOTIFY_H #include <QObject> #include <QPointer> #include "lib/unicorn/TrackImageFetcher.h" namespace lastfm { class Track; } namespace unicorn { class Notify : public QObject { Q_OBJECT public: explicit Notify(QObject *parent = 0); ~Notify(); signals: void clicked(); public slots: void newTrack( const lastfm::Track& track ); void paused(); void resumed(); void stopped(); private slots: void onFinished( const QPixmap& pixmap ); public: void growlNotificationWasClicked(); private: QPointer<TrackImageFetcher> m_trackImageFetcher; }; } #endif // NOTIFY_H ================================================ FILE: lib/unicorn/notify/Notify.mm ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifdef LASTFM_USER_NOTIFICATIONS #include <Foundation/NSUserNotification.h> #endif #include <QPixmap> #include <Growl/Growl.h> #include <lastfm/Track.h> #include "Notify.h" #ifdef LASTFM_USER_NOTIFICATIONS @interface MacClickDelegate : NSObject <NSUserNotificationCenterDelegate> { unicorn::Notify* m_observer; } - (MacClickDelegate*) initialise:(unicorn::Notify*)observer; - (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification; @end @implementation MacClickDelegate - (MacClickDelegate*) initialise:(unicorn::Notify*)observer { if ( (self = [super init]) ) { self->m_observer = observer; } return self; } - (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification { Q_UNUSED(center) Q_UNUSED(notification) self->m_observer->growlNotificationWasClicked(); } @end #endif @interface GrowlClickDelegate : NSObject <GrowlApplicationBridgeDelegate> { unicorn::Notify* m_observer; } - (GrowlClickDelegate*) init:(unicorn::Notify*)observer; - (void) growlNotificationWasClicked:(id)clickContext; @end @implementation GrowlClickDelegate - (GrowlClickDelegate*) init:(unicorn::Notify*)observer { if ( (self = [super init]) ) { self->m_observer = observer; } return self; } - (void) growlNotificationWasClicked:(id)clickContext { Q_UNUSED(clickContext) self->m_observer->growlNotificationWasClicked(); } @end unicorn::Notify::Notify(QObject *parent) : QObject(parent) { #ifdef LASTFM_USER_NOTIFICATIONS if ( [NSUserNotificationCenter class] ) { MacClickDelegate* macDelegate = [[MacClickDelegate alloc] initialise: this]; [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:macDelegate]; [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications]; } else #endif { GrowlClickDelegate* growlDelegate = [[GrowlClickDelegate alloc] init: this]; [GrowlApplicationBridge setGrowlDelegate:growlDelegate]; } } unicorn::Notify::~Notify() { #ifdef LASTFM_USER_NOTIFICATIONS if ( [NSUserNotificationCenter class] ) { [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications]; } #endif } void unicorn::Notify::newTrack( const lastfm::Track& track ) { delete m_trackImageFetcher; m_trackImageFetcher = new TrackImageFetcher( track, Track::LargeImage ); connect( m_trackImageFetcher, SIGNAL(finished(QPixmap)), SLOT(onFinished(QPixmap)) ); #ifdef LASTFM_USER_NOTIFICATIONS if ( [NSUserNotificationCenter class] ) { [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications]; onFinished( QPixmap() ); } else #endif { m_trackImageFetcher->startAlbum(); } } void unicorn::Notify::paused() { #ifdef LASTFM_USER_NOTIFICATIONS if ( [NSUserNotificationCenter class] ) [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications]; #endif } void unicorn::Notify::resumed() { #ifdef LASTFM_USER_NOTIFICATIONS if ( [NSUserNotificationCenter class] ) { if ( m_trackImageFetcher ) { [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications]; onFinished( QPixmap() ); } } #endif } void unicorn::Notify::stopped() { #ifdef LASTFM_USER_NOTIFICATIONS if ( [NSUserNotificationCenter class] ) { [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications]; delete m_trackImageFetcher; } #endif } void unicorn::Notify::onFinished( const QPixmap& pixmap ) { Track track = m_trackImageFetcher->track(); QString title = track.title(); QString description = tr("%1\n%2").arg( track.artist(), track.album() ); if ( track.album().isNull() ) description = track.artist(); NSString* nsTitle = [NSString stringWithCharacters:(const unichar *)title.unicode() length:(NSUInteger)title.length() ]; NSString* nsDescription = [NSString stringWithCharacters:(const unichar *)description.unicode() length:(NSUInteger)description.length() ]; #ifdef LASTFM_USER_NOTIFICATIONS if ( [NSUserNotificationCenter class] ) { NSUserNotification* userNotification = [NSUserNotification alloc]; [userNotification setTitle:nsTitle]; [userNotification setSubtitle:nsDescription]; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification]; } else #endif { NSData* data = nil; if ( !pixmap.isNull() ) { CGImageRef cgImage = pixmap.toMacCGImageRef(); NSImage* nsImage = [[NSImage alloc] initWithCGImage:(CGImageRef)cgImage size:(NSSize)NSZeroSize]; data = [nsImage TIFFRepresentation]; } // TODO: Do the growl notification here. It'll be great! [GrowlApplicationBridge notifyWithTitle:(NSString *)nsTitle description:(NSString *)nsDescription notificationName:(NSString *)@"New track" iconData:(NSData *)data priority:(signed int)0 isSticky:(BOOL)NO clickContext:(id)@"context" identifier:(NSString*)@"identifier" ]; } } void unicorn::Notify::growlNotificationWasClicked() { emit clicked(); } ================================================ FILE: lib/unicorn/plugins/FooBar08PluginInfo.cpp ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QStringList> #include "FooBar08PluginInfo.h" unicorn::FooBar08PluginInfo::FooBar08PluginInfo( QObject* parent ) :IPluginInfo( parent ) {} unicorn::Version unicorn::FooBar08PluginInfo::version() const { return Version( 2, 1, 0 ); } QString unicorn::FooBar08PluginInfo::name() const { return "foobar2000"; } QString unicorn::FooBar08PluginInfo::displayName() const { return QString( "foobar2000" ); } QString unicorn::FooBar08PluginInfo::processName() const { return QString( "foobar2000.exe" ); } QString unicorn::FooBar08PluginInfo::id() const { return QString( "foo2" ); } bool unicorn::FooBar08PluginInfo::isAppInstalled() const { return QSettings("HKEY_CURRENT_USER\\Software\\foobar2000", QSettings::NativeFormat).contains("DefaultShellAction"); } unicorn::IPluginInfo::BootstrapType unicorn::FooBar08PluginInfo::bootstrapType() const { return NoBootstrap; } QString unicorn::FooBar08PluginInfo::pluginInstaller() const { return "FooPlugin0.9Setup_2.1.exe"; } ================================================ FILE: lib/unicorn/plugins/FooBar08PluginInfo.h ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef FOOBAR08_PLUGIN_INFO_H_ #define FOOBAR08_PLUGIN_INFO_H_ #include "IPluginInfo.h" #include <lib/DllExportMacro.h> namespace unicorn { class UNICORN_DLLEXPORT FooBar08PluginInfo : public IPluginInfo { Q_OBJECT public: FooBar08PluginInfo( QObject* parent = 0 ); Version version() const; QString name() const; QString displayName() const; QString processName() const; QString id() const; BootstrapType bootstrapType() const; bool isAppInstalled() const; QString pluginInstallPath() const; QString pluginInstaller() const; }; } #endif //FOOBAR08_PLUGIN_INFO_H_ ================================================ FILE: lib/unicorn/plugins/Foobar09PluginInfo.cpp ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QSettings> #include <QStringList> #include "FooBar09PluginInfo.h" unicorn::FooBar09PluginInfo::FooBar09PluginInfo( QObject* parent ) :IPluginInfo( parent ) {} unicorn::Version unicorn::FooBar09PluginInfo::version() const { return Version( 2, 3, 1, 3 ); } QString unicorn::FooBar09PluginInfo::name() const { return "foobar2000"; } QString unicorn::FooBar09PluginInfo::displayName() const { return QString( "foobar2000" ); } QString unicorn::FooBar09PluginInfo::processName() const { return QString( "foobar2000.exe" ); } QString unicorn::FooBar09PluginInfo::id() const { return QString( "foo3" ); } bool unicorn::FooBar09PluginInfo::isAppInstalled() const { return QSettings("HKEY_CURRENT_USER\\Software\\foobar2000", QSettings::NativeFormat).contains("DefaultShellAction"); } unicorn::IPluginInfo::BootstrapType unicorn::FooBar09PluginInfo::bootstrapType() const { return NoBootstrap; } QString unicorn::FooBar09PluginInfo::pluginInstaller() const { return "FooPlugin0.9.4Setup_2.3.1.3.exe"; } ================================================ FILE: lib/unicorn/plugins/Foobar09PluginInfo.h ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef FOOBAR09_PLUGIN_INFO_H_ #define FOOBAR09_PLUGIN_INFO_H_ #include "IPluginInfo.h" #include <lib/DllExportMacro.h> namespace unicorn { class UNICORN_DLLEXPORT FooBar09PluginInfo : public IPluginInfo { Q_OBJECT public: FooBar09PluginInfo( QObject* parent = 0 ); Version version() const; QString name() const; QString displayName() const; QString processName() const; QString id() const; BootstrapType bootstrapType() const; bool isAppInstalled() const; QString pluginInstallPath() const; QString pluginInstaller() const; }; } #endif //FOOBAR09_PLUGIN_INFO_H_ ================================================ FILE: lib/unicorn/plugins/IPluginInfo.cpp ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QSettings> #include <QStringList> #include <QCoreApplication> #include <QProcess> #include <QDebug> #include <windows.h> #include <Shellapi.h> #include <Wtsapi32.h> #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/UnicornSettings.h" #include "../Dialogs/CloseAppsDialog.h" #include "IPluginInfo.h" #include "KillProcess.h" unicorn::IPluginInfo::IPluginInfo( QObject* parent ) :QObject( parent ) , m_install( false ) , m_verbose( false ) {} bool unicorn::IPluginInfo::doInstall() { bool success = false; QList<IPluginInfo*> plugins; plugins << this; CloseAppsDialog* closeApps = new CloseAppsDialog( plugins, 0 ); if ( closeApps->result() != QDialog::Accepted ) closeApps->exec(); else closeApps->deleteLater(); if ( closeApps->result() == QDialog::Accepted ) { QString installer = QString( "\"%1\"" ).arg( QCoreApplication::applicationDirPath() + "/plugins/" + pluginInstaller() ); qDebug() << installer; QProcess* installerProcess = new QProcess( this ); QStringList args; if ( !m_verbose ) args << "/SILENT"; installerProcess->start( installer, args ); success = installerProcess->waitForFinished( -1 ); if ( m_verbose ) { if ( !success ) { // Tell the user that QMessageBoxBuilder( 0 ).setTitle( tr( "Plugin install error" ) ) .setIcon( QMessageBox::Information ) .setText( tr( "<p>There was an error updating your plugin.</p>" "<p>Please try again later.</p>" ) ) .setButtons( QMessageBox::Ok ) .exec(); } else { // The user didn't closed their media players QMessageBoxBuilder( 0 ).setTitle( tr( "Plugin installed!" ) ) .setIcon( QMessageBox::Information ) .setText( tr( "<p>The %1 plugin has been installed.</p>" "<p>You're now ready to scrobble with %1.</p>" ).arg( name() ) ) .setButtons( QMessageBox::Ok ) .exec(); } } } else { // The user didn't close their media players QMessageBoxBuilder( 0 ).setTitle( tr( "The %1 plugin hasn't been installed" ).arg( name() ) ) .setIcon( QMessageBox::Warning ) .setText( tr( "You didn't close %1 so its plugin hasn't been installed." ).arg( name() ) ) .setButtons( QMessageBox::Ok ) .exec(); } return success; } bool unicorn::IPluginInfo::install() const { return m_install; } void unicorn::IPluginInfo::install( bool install ) { m_install = install; } #ifdef Q_OS_WIN BOOL unicorn::IPluginInfo::isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. void* fnIsWow64Process = GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(NULL != fnIsWow64Process) { if (!IsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif bool unicorn::IPluginInfo::isInstalled() const { QSettings settings( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Last.fm\\Client\\Plugins", QSettings::NativeFormat ); return settings.childGroups().contains( id() ); } unicorn::Version unicorn::IPluginInfo::installedVersion() const { QSettings settings( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Last.fm\\Client\\Plugins\\" + id(), QSettings::NativeFormat ); return Version::fromString( settings.value( "Version", "0.0.0.0" ).toString() ); } bool unicorn::IPluginInfo::canBootstrap() const { return bootstrapType() != NoBootstrap; } QString unicorn::IPluginInfo::programFilesX86() const { return QString( getenv( "ProgramFiles(x86)" ) ); } QString unicorn::IPluginInfo::programFiles64() const { return QString( getenv( "ProgramW6432" ) ); } void unicorn::IPluginInfo::setVerbose( bool verbose ) { m_verbose = verbose; } ================================================ FILE: lib/unicorn/plugins/IPluginInfo.h ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PLUGIN_INFO_H_ #define PLUGIN_INFO_H_ #include "lib/unicorn/plugins/Version.h" #include <lib/DllExportMacro.h> #include <QString> #include <QSettings> #ifdef WIN32 #include <windows.h> #include <Shlobj.h> #endif namespace unicorn { class UNICORN_DLLEXPORT IPluginInfo : public QObject { Q_OBJECT public: IPluginInfo( QObject* parent = 0); enum BootstrapType{ NoBootstrap = 0, ClientBootstrap, PluginBootstrap }; bool install() const; virtual QString name() const = 0; virtual Version version() const = 0; Version installedVersion() const; virtual QString id() const = 0; virtual BootstrapType bootstrapType() const = 0; virtual bool isAppInstalled() const = 0; virtual QString processName() const = 0; // DisplayName string value as found in the HKEY_LM/Software/Microsoft/CurrentVersion/Uninstall/{GUID}/ virtual QString displayName() const = 0; virtual QString pluginInstaller() const = 0; #ifdef Q_OS_WIN static BOOL isWow64(); #endif virtual bool isInstalled() const; bool canBootstrap() const; void setVerbose( bool verbose ); public slots: bool doInstall(); void install( bool install ); protected: QString programFilesX86() const; QString programFiles64() const; private: bool m_install; bool m_verbose; }; } #endif //PLUGIN_INFO_H_ ================================================ FILE: lib/unicorn/plugins/ITunesPluginInfo.cpp ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QStringList> #include "ITunesPluginInfo.h" unicorn::ITunesPluginInfo::ITunesPluginInfo( QObject* parent ) :IPluginInfo( parent ) {} unicorn::Version unicorn::ITunesPluginInfo::version() const { return Version( 6, 0, 5, 4 ); } QString unicorn::ITunesPluginInfo::name() const { return "iTunes"; } QString unicorn::ITunesPluginInfo::displayName() const { return QString( "iTunes" ); } QString unicorn::ITunesPluginInfo::processName() const { return QString( "iTunes.exe" ); } QString unicorn::ITunesPluginInfo::id() const { #ifdef Q_OS_WIN32 return "itw"; #elif return "osx"; #endif } bool unicorn::ITunesPluginInfo::isAppInstalled() const { #ifdef Q_OS_WIN32 return QSettings("HKEY_CURRENT_USER\\Software\\Apple Computer, Inc.\\iTunes", QSettings::NativeFormat).contains("SM Shortcut Installed"); #elif return true; #endif } unicorn::IPluginInfo::BootstrapType unicorn::ITunesPluginInfo::bootstrapType() const { return ClientBootstrap; } QString unicorn::ITunesPluginInfo::pluginInstaller() const { return "iTunesPluginWinSetup_6.0.5.4.exe"; } ================================================ FILE: lib/unicorn/plugins/ITunesPluginInfo.h ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef ITUNES_PLUGIN_INFO_H_ #define ITUNES_PLUGIN_INFO_H_ #include "IPluginInfo.h" #include <lib/DllExportMacro.h> namespace unicorn { class UNICORN_DLLEXPORT ITunesPluginInfo : public IPluginInfo { Q_OBJECT public: ITunesPluginInfo( QObject* parent = 0 ); Version version() const; QString name() const; QString displayName() const; QString processName() const; QString id() const; BootstrapType bootstrapType() const; bool isAppInstalled() const; QString pluginInstallPath() const; QString pluginInstaller() const; #ifdef Q_OS_MAC // the iTunes plugin is always installed on mac bool isInstalled() const { return true; } #endif }; } #endif //ITUNES_PLUGIN_INFO_H_ ================================================ FILE: lib/unicorn/plugins/ITunesPluginInstaller.cpp ================================================ /*************************************************************************** * Copyright (C) 2005 - 2008 by * * Last.fm Ltd <client@last.fm> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ITunesPluginInstaller.h" #include "lib/unicorn/dialogs/CloseAppsDialog.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include <CoreFoundation/CoreFoundation.h> #include <QApplication> #include <QDir> #include <QProcess> #include <QDebug> static const char* kBundleName = "AudioScrobbler.bundle"; static const char* kPListFile = "Contents/Info.plist"; QString shippedPluginDir() { return qApp->applicationDirPath() + "/" + kBundleName + "/"; } QString iTunesPluginDir() { return QDir::homePath() + "/Library/iTunes/iTunes Plug-ins/" + kBundleName + "/"; } unicorn::ITunesPluginInstaller::ITunesPluginInstaller( QWidget* parent ) :QObject( parent ), m_needsTwiddlyBootstrap( false ) { qDebug() << " shippedPluginDir: " << shippedPluginDir(); } unicorn::Version unicorn::ITunesPluginInstaller::installedVersion() { return pListVersion( iTunesPluginDir() + kPListFile ); } unicorn::Version unicorn::ITunesPluginInstaller::bundledVersion() { return pListVersion( shippedPluginDir() + kPListFile ); } void unicorn::ITunesPluginInstaller::install() { qDebug() << "Found installed iTunes plugin?" << isPluginInstalled(); Version installVersion; Version shippedVersion; disableLegacyHelperApp(); if ( isPluginInstalled() ) { installVersion = installedVersion(); qDebug() << "Found installed iTunes plugin version:" << installVersion.toString(); } else //TODO don't bootstrap if installation fails m_needsTwiddlyBootstrap = true; shippedVersion = bundledVersion(); if ( shippedVersion == Version() ) { qDebug() << "Could not locate shipped iTunes plugin!" << shippedPluginDir(); } else { qDebug() << "Found shipped iTunes plugin version:" << shippedVersion.toString(); if ( installVersion != shippedVersion ) { bool closeAppsShown = false; qDebug() << "Installing shipped iTunes plugin..."; QWidget* dialogParent = 0; if ( parent()->isWidgetType() ) dialogParent = qobject_cast<QWidget*>( parent() ); unicorn::CloseAppsDialog* closeApps = new unicorn::CloseAppsDialog( dialogParent ); closeApps->setTitle( tr( "Close iTunes for plugin update!" ) ); closeApps->setDescription( tr( "<p>Your iTunes plugin (%2) is different to the one shipped with this version of the app (%1).</p>" "<p>Please close iTunes now to update.</p>" ).arg( shippedVersion.toString(), installVersion == Version() ? tr( "not installed" ) : installVersion.toString() ) ); closeApps->showPluginList( false ); if ( closeApps->result() != QDialog::Accepted ) { closeAppsShown = true; closeApps->exec(); } else closeApps->deleteLater(); if ( closeApps->result() == QDialog::Accepted ) { if ( !removeInstalledPlugin() ) { qDebug() << "Removing installed plugin from" << iTunesPluginDir() << "failed!"; // The user didn't close their media players QMessageBoxBuilder( dialogParent ).setTitle( tr( "Your plugin hasn't been installed" ) ) .setIcon( QMessageBox::Warning ) .setText( tr( "There was an error while removing the old plugin" ) ) .setButtons( QMessageBox::Ok ) .exec(); } else { qDebug() << "Successfully removed installed plugin."; if ( installPlugin() ) { qDebug() << "Successfully installed the plugin."; if ( closeAppsShown ) { // Tell the user that QMessageBoxBuilder( dialogParent ).setTitle( tr( "iTunes Plugin installed!" ) ) .setIcon( QMessageBox::Information ) .setText( tr( "<p>Your iTunes plugin has been installed.</p>" "<p>You're now ready to device scrobble.</p>" ) ) .setButtons( QMessageBox::Ok ) .exec(); } } else { qDebug() << "Installing the plugin failed!"; // The user didn't close their media players QMessageBoxBuilder( dialogParent ).setTitle( tr( "Your plugin hasn't been installed" ) ) .setIcon( QMessageBox::Warning ) .setText( tr( "There was an error while copying the new plugin into place" ) ) .setButtons( QMessageBox::Ok ) .exec(); } } } else { qDebug() << "Plugin not installed. User reject the dialog."; // The user didn't close their media players QMessageBoxBuilder( dialogParent ).setTitle( tr( "Your plugin hasn't been installed" ) ) .setIcon( QMessageBox::Warning ) .setText( tr( "You didn't close iTunes" ) ) .setButtons( QMessageBox::Ok ) .exec(); } } else qDebug() << "Installed iTunes plugin is up-to-date."; } } bool unicorn::ITunesPluginInstaller::isPluginInstalled() { return QDir( iTunesPluginDir() ).exists(); } unicorn::Version unicorn::ITunesPluginInstaller::pListVersion( const QString& file ) { QFile f( file ); if ( !f.open( QIODevice::ReadOnly ) ) return Version(); const QString key( "<key>CFBundleVersion</key>" ); const QString begin( "<string>" ); const QString end( "</string>" ); while ( !f.atEnd() ) { QString line = f.readLine(); if ( line.contains( key ) && !f.atEnd() ) { QString versionLine = QString( ( f.readLine() ) ).trimmed(); QString versionString = versionLine.section( begin, 1 ); versionString = versionString.left( versionString.length() - end.length() ); Version version = Version::fromString( versionString ); f.close(); return version; } } f.close(); return Version(); } static bool deleteDir( QString path ) { if ( !path.endsWith( "/" ) ) path += "/"; QDir d( path ); // Remove all files const QStringList files = d.entryList( QDir::Files ); foreach( QString file, files ) { if ( !d.remove( file ) ) return false; } // Remove all dirs (recursive) const QStringList dirs = d.entryList( QDir::Dirs | QDir::NoDotAndDotDot ); foreach( QString dir, dirs ) { if ( !deleteDir( path + dir ) ) return false; } return d.rmdir( path ); } bool unicorn::ITunesPluginInstaller::removeInstalledPlugin() { if ( !isPluginInstalled() ) return true; return deleteDir( iTunesPluginDir() ); } static bool copyDir( QString path, QString dest ) { if ( !path.endsWith( '/' ) ) path += '/'; if ( !dest.endsWith( '/' ) ) dest += '/'; QDir( dest ).mkpath( "." ); QDir d( path ); const QStringList files = d.entryList( QDir::Files ); foreach( QString file, files ) { QFile f( path + file ); if ( !f.copy( dest + file ) ) return false; } const QStringList dirs = d.entryList( QDir::Dirs | QDir::NoDotAndDotDot ); foreach( QString dir, dirs ) { if ( !copyDir( path + dir, dest + dir ) ) return false; } return true; } bool unicorn::ITunesPluginInstaller::installPlugin() { return copyDir( shippedPluginDir(), iTunesPluginDir() ); } static inline QByteArray CFStringToUtf8( CFStringRef s ) { QByteArray result; if (s != NULL) { CFIndex length; length = CFStringGetLength( s ); length = CFStringGetMaximumSizeForEncoding( length, kCFStringEncodingUTF8 ) + 1; char* buffer = new char[length]; if (CFStringGetCString( s, buffer, length, kCFStringEncodingUTF8 )) result = QByteArray( buffer ); else qWarning() << "CFString conversion failed."; delete[] buffer; } return result; } void unicorn::ITunesPluginInstaller::disableLegacyHelperApp() { qDebug() << "Disabling old LastFmHelper"; // EVEN MORE LEGACY: disable oldest helper auto-launch! QString oldplist = QDir::homePath() + "/Library/LaunchAgents/fm.last.lastfmhelper.plist"; if ( QFile::exists( oldplist ) ) { QProcess::execute( "/bin/launchctl", QStringList() << "unload" << oldplist ); QFile::remove( oldplist ); } // REMOVE LastFmHelper from loginwindow.plist CFArrayRef prefCFArrayRef = (CFArrayRef)CFPreferencesCopyValue( CFSTR( "AutoLaunchedApplicationDictionary" ), CFSTR( "loginwindow" ), kCFPreferencesCurrentUser, kCFPreferencesAnyHost ); if ( prefCFArrayRef == NULL ) return; CFMutableArrayRef tCFMutableArrayRef = CFArrayCreateMutableCopy( NULL, 0, prefCFArrayRef ); if ( tCFMutableArrayRef == NULL ) return; for ( int i = CFArrayGetCount( prefCFArrayRef ) - 1; i >= 0 ; i-- ) { CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex( prefCFArrayRef, i ); QString path = QString::fromUtf8( CFStringToUtf8( (CFStringRef)CFDictionaryGetValue( dict, CFSTR( "Path" ) ) ) ); if ( path.toLower().contains( "lastfmhelper" ) ) { // Better make sure LastFmHelper is really dead QProcess::execute( "/usr/bin/killall", QStringList() << "LastFmHelper" ); qDebug() << "Removing helper from LoginItems at position" << i; CFArrayRemoveValueAtIndex( tCFMutableArrayRef, (CFIndex)i ); } } CFPreferencesSetValue( CFSTR( "AutoLaunchedApplicationDictionary" ), tCFMutableArrayRef, CFSTR( "loginwindow" ), kCFPreferencesCurrentUser, kCFPreferencesAnyHost ); CFPreferencesSynchronize( CFSTR( "loginwindow" ), kCFPreferencesCurrentUser, kCFPreferencesAnyHost ); CFRelease( prefCFArrayRef ); CFRelease( tCFMutableArrayRef ); } void unicorn::ITunesPluginInstaller::uninstall() { QDir d = iTunesPluginDir() + "Contents/Resources"; // always use absolute paths to tools! - muesli QProcess::startDetached( "/bin/sh", QStringList() << d.filePath( "uninstall.sh" ), d.path() ); } ================================================ FILE: lib/unicorn/plugins/ITunesPluginInstaller.h ================================================ /*************************************************************************** * Copyright 2005-2008 Last.fm Ltd <client@last.fm> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #pragma once #include "Version.h" #include <lib/DllExportMacro.h> #include <QObject> #include <QString> namespace unicorn { class UNICORN_DLLEXPORT ITunesPluginInstaller : public QObject { Q_OBJECT public: ITunesPluginInstaller( QWidget* parent ); static Version installedVersion(); static Version bundledVersion(); void uninstall(); // NOTE this is only valid after calling install() bool needsTwiddlyBootstrap() const { return m_needsTwiddlyBootstrap; } public slots: void install(); private: bool isPluginInstalled(); static Version pListVersion( const QString& file ); bool removeInstalledPlugin(); bool installPlugin(); // Legacy code: removes old LastFmHelper for updates void disableLegacyHelperApp(); private: bool m_needsTwiddlyBootstrap; }; } ================================================ FILE: lib/unicorn/plugins/KillProcess.h ================================================ /*************************************************************************** * Copyright (C) 2005 - 2007 by * * Last.fm Ltd <client@last.fm> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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 Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifdef WIN32 #ifndef KILLPROCESS_H #define KILLPROCESS_H // This file contains legacy code that doesn't work with Unicode. #undef UNICODE #undef _UNICODE #include <windows.h> #include <tlhelp32.h> // // Some definitions from NTDDK and other sources // typedef LONG NTSTATUS; typedef LONG KPRIORITY; #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) #define SystemProcessesAndThreadsInformation 5 typedef struct _CLIENT_ID { DWORD UniqueProcess; DWORD UniqueThread; } CLIENT_ID; typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING; typedef struct _VM_COUNTERS { SIZE_T PeakVirtualSize; SIZE_T VirtualSize; ULONG PageFaultCount; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize; SIZE_T QuotaPeakPagedPoolUsage; SIZE_T QuotaPagedPoolUsage; SIZE_T QuotaPeakNonPagedPoolUsage; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PagefileUsage; SIZE_T PeakPagefileUsage; } VM_COUNTERS; typedef struct _SYSTEM_THREADS { LARGE_INTEGER KernelTime; LARGE_INTEGER UserTime; LARGE_INTEGER CreateTime; ULONG WaitTime; PVOID StartAddress; CLIENT_ID ClientId; KPRIORITY Priority; KPRIORITY BasePriority; ULONG ContextSwitchCount; LONG State; LONG WaitReason; } SYSTEM_THREADS, * PSYSTEM_THREADS; // Note that the size of the SYSTEM_PROCESSES structure is // different on NT 4 and Win2K, but we don't care about it, // since we don't access neither IoCounters member nor //Threads array typedef struct _SYSTEM_PROCESSES { ULONG NextEntryDelta; ULONG ThreadCount; ULONG Reserved1[6]; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ProcessName; KPRIORITY BasePriority; ULONG ProcessId; ULONG InheritedFromProcessId; ULONG HandleCount; ULONG Reserved2[2]; VM_COUNTERS VmCounters; #if _WIN32_WINNT >= 0x500 IO_COUNTERS IoCounters; #endif SYSTEM_THREADS Threads[1]; } SYSTEM_PROCESSES, * PSYSTEM_PROCESSES; class CKillProcessHelper { private: //Functions loaded from Kernel32 typedef HANDLE (WINAPI *PFCreateToolhelp32Snapshot)( DWORD dwFlags, DWORD th32ProcessID ); typedef BOOL (WINAPI *PFProcess32First)( HANDLE hSnapshot, LPPROCESSENTRY32 lppe ); typedef BOOL (WINAPI *PFProcess32Next)( HANDLE hSnapshot, LPPROCESSENTRY32 lppe ); // Native NT API Definitions typedef NTSTATUS (WINAPI * PFZwQuerySystemInformation) (UINT, PVOID, ULONG, PULONG); typedef HANDLE (WINAPI* PFGetProcessHeap)(VOID); typedef LPVOID (WINAPI* PFHeapAlloc) (HANDLE,DWORD,SIZE_T); typedef BOOL (WINAPI* PFHeapFree)(HANDLE,DWORD,LPVOID); public: CKillProcessHelper() : FCreateToolhelp32Snapshot(NULL), FProcess32First(NULL), FProcess32Next(NULL), m_hKernelLib(NULL), m_hNTLib(NULL) { m_hKernelLib = ::LoadLibraryA("Kernel32"); if (m_hKernelLib) { // Find ToolHelp functions FCreateToolhelp32Snapshot = (PFCreateToolhelp32Snapshot) ::GetProcAddress(m_hKernelLib, ("CreateToolhelp32Snapshot")); FProcess32First = (PFProcess32First) ::GetProcAddress(m_hKernelLib, ("Process32First")); FProcess32Next = (PFProcess32Next) ::GetProcAddress(m_hKernelLib, ("Process32Next")); } if(!FCreateToolhelp32Snapshot || !FProcess32First || !FProcess32Next) { // i.e. we couldn't find the ToolHelp functions, //so we must be on NT4. Let's load the // undocumented one instead. if(!m_hKernelLib) return; // can't do anything at all without //the kernel. m_hNTLib = ::LoadLibraryA("ntdll.dll"); if(m_hNTLib) { FQuerySysInfo = (PFZwQuerySystemInformation) ::GetProcAddress(m_hNTLib, ("ZwQuerySystemInformation")); // load some support funcs from the kernel FGetProcessHeap = (PFGetProcessHeap) ::GetProcAddress(m_hKernelLib, ("GetProcessHeap")); FHeapAlloc = (PFHeapAlloc) ::GetProcAddress(m_hKernelLib, ("HeapAlloc")); FHeapFree = (PFHeapFree) ::GetProcAddress(m_hKernelLib, ("HeapFree")); } } } ~CKillProcessHelper() { if(m_hKernelLib) FreeLibrary(m_hKernelLib); if(m_hNTLib) FreeLibrary(m_hNTLib); } bool KillProcess(IN const char* pstrProcessName) { DWORD dwId; HANDLE hProcess = FindProcess(pstrProcessName, dwId); BOOL bResult; if(!hProcess) return false; // TerminateAppEnum() posts WM_CLOSE to all windows whose PID // matches your process's. ::EnumWindows((WNDENUMPROC) CKillProcessHelper::TerminateAppEnum, (LPARAM) dwId); // Wait on the handle. If it signals, great. //If it times out, then you kill it. if(WaitForSingleObject(hProcess, 5000) !=WAIT_OBJECT_0) bResult = TerminateProcess(hProcess,0); else bResult = TRUE; CloseHandle(hProcess); return static_cast<bool>(bResult != FALSE); } HANDLE FindProcess(IN const char* pstrProcessName, OUT DWORD& dwId) { if(!m_hKernelLib) return NULL; if(FCreateToolhelp32Snapshot && FProcess32First && FProcess32Next) // use toolhelpapi return THFindProcess(pstrProcessName, dwId); if(FQuerySysInfo && FHeapAlloc && FGetProcessHeap && FHeapFree) // use NT api return NTFindProcess(pstrProcessName, dwId); // neither one got loaded. Strange. return NULL; } private: HANDLE THFindProcess(IN const char* pstrProcessName, OUT DWORD& dwId) { HANDLE hSnapShot=NULL; HANDLE hResult = NULL; PROCESSENTRY32 processInfo; char* pstrExeName; bool bFirst = true; ::ZeroMemory(&processInfo, sizeof(PROCESSENTRY32)); processInfo.dwSize = sizeof(PROCESSENTRY32); hSnapShot = FCreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0); if(hSnapShot == INVALID_HANDLE_VALUE) return NULL; // ok now let's iterate with Process32Next until we // match up the name of our process while((bFirst ? FProcess32First(hSnapShot, &processInfo) : FProcess32Next(hSnapShot, &processInfo))) { bFirst = false; // we need to check for path... and extract // just the exe name pstrExeName = strrchr(processInfo.szExeFile, '\\'); if(!pstrExeName) pstrExeName = processInfo.szExeFile; else pstrExeName++; // skip the backslash // ok now compare against our process name if(_stricmp(pstrExeName, pstrProcessName) == 0) // wee weee we found it { // let's get a HANDLE on it hResult=OpenProcess( SYNCHRONIZE|PROCESS_TERMINATE, TRUE, processInfo.th32ProcessID); dwId = processInfo.th32ProcessID; break; } } // while(Process32Next(hSnapShot, &processInfo){ if(hSnapShot) CloseHandle(hSnapShot); return hResult; } HANDLE NTFindProcess(IN const char* pstrProcessName, OUT DWORD& dwId) { HANDLE hHeap = FGetProcessHeap(); NTSTATUS Status; ULONG cbBuffer = 0x8000; PVOID pBuffer = NULL; HANDLE hResult = NULL; // it is difficult to say a priory which size of // the buffer will be enough to retrieve all // information, so we startwith 32K buffer and // increase its size until we get the // information successfully do { pBuffer = HeapAlloc(hHeap, 0, cbBuffer); if (pBuffer == NULL) return SetLastError( ERROR_NOT_ENOUGH_MEMORY), NULL; Status = FQuerySysInfo( SystemProcessesAndThreadsInformation, pBuffer, cbBuffer, NULL); if (Status == STATUS_INFO_LENGTH_MISMATCH) { HeapFree(hHeap, 0, pBuffer); cbBuffer *= 2; } else if (!NT_SUCCESS(Status)) { HeapFree(hHeap, 0, pBuffer); return SetLastError(Status), NULL; } } while (Status == STATUS_INFO_LENGTH_MISMATCH); PSYSTEM_PROCESSES pProcesses = (PSYSTEM_PROCESSES)pBuffer; for (;;) { PCWSTR pszProcessName = pProcesses->ProcessName.Buffer; if (pszProcessName == NULL) pszProcessName = L"Idle"; CHAR szProcessName[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, pszProcessName, -1,szProcessName, MAX_PATH, NULL, NULL); if(_stricmp(szProcessName, pstrProcessName) == 0) // found it { hResult=OpenProcess( SYNCHRONIZE|PROCESS_TERMINATE, TRUE, pProcesses->ProcessId); dwId = pProcesses->ProcessId; break; } if (pProcesses->NextEntryDelta == 0) break; // find the address of the next process // structure pProcesses = (PSYSTEM_PROCESSES)( ((LPBYTE)pProcesses) + pProcesses->NextEntryDelta); } HeapFree(hHeap, 0, pBuffer); return hResult; } // callback function for window enumeration static BOOL CALLBACK TerminateAppEnum( HWND hwnd, LPARAM lParam ) { DWORD dwID ; GetWindowThreadProcessId(hwnd, &dwID) ; if(dwID == (DWORD)lParam) { PostMessage(hwnd, WM_CLOSE, 0, 0) ; } return TRUE ; } HMODULE m_hNTLib; HMODULE m_hKernelLib; // ToolHelp related functions PFCreateToolhelp32Snapshot FCreateToolhelp32Snapshot; PFProcess32First FProcess32First; PFProcess32Next FProcess32Next; // native NT api functions PFZwQuerySystemInformation FQuerySysInfo; PFGetProcessHeap FGetProcessHeap; PFHeapAlloc FHeapAlloc; PFHeapFree FHeapFree; }; #endif // KILLPROCESS_H #endif // WIN32 ================================================ FILE: lib/unicorn/plugins/PluginList.cpp ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "PluginList.h" #include <QStringList> QList<unicorn::IPluginInfo*> unicorn::PluginList::availablePlugins() const { QList<IPluginInfo*> ret; foreach( IPluginInfo* plugin, m_plugins ) { if( plugin->isInstalled() ) continue; if( plugin->isAppInstalled() ) ret << plugin; } return ret; } QList<unicorn::IPluginInfo*> unicorn::PluginList::installedPlugins() const { QList<IPluginInfo*> ret; foreach( IPluginInfo* plugin, m_plugins ) { if( plugin->isInstalled()) ret << plugin; } return ret; } QList<unicorn::IPluginInfo*> unicorn::PluginList::bootstrappablePlugins() const { QList<IPluginInfo*> ret; foreach( IPluginInfo* plugin, installedPlugins() ) { if( plugin->isInstalled() && plugin->canBootstrap() ) ret << plugin; } return ret; } QList<unicorn::IPluginInfo*> unicorn::PluginList::supportedList() const { QList<IPluginInfo*> ret; foreach( IPluginInfo* i, m_plugins ) ret << i; return ret; } QList<unicorn::IPluginInfo*> unicorn::PluginList::installList() const { QList<IPluginInfo*> ret; foreach( IPluginInfo* i, m_plugins ) { if ( i->install() ) ret << i; } return ret; } QList<unicorn::IPluginInfo*> unicorn::PluginList::updatedList() const { QList<IPluginInfo*> ret; foreach( IPluginInfo* i, m_plugins ) { if ( i->isInstalled() && i->version() > i->installedVersion() ) ret << i; } return ret; } QString unicorn::PluginList::availableDescription() const { QStringList mediaPlayers; foreach( IPluginInfo* i, supportedList() ) mediaPlayers << i->name(); QString ret = mediaPlayers.takeLast(); if( mediaPlayers.count() > 0 ) ret = mediaPlayers.join( ", " ) + " or " + ret; return ret; } unicorn::IPluginInfo* unicorn::PluginList::pluginById( const QString& id ) const { foreach( IPluginInfo* plugin, m_plugins ) { if( !plugin->id().compare( id ) ) return plugin; } return NULL; } ================================================ FILE: lib/unicorn/plugins/PluginList.h ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PLUGIN_LIST_H_ #define PLUGIN_LIST_H_ #include <QObject> #include "WinampPluginInfo.h" #include "WmpPluginInfo.h" #include "ITunesPluginInfo.h" #include "Foobar09PluginInfo.h" #include <lib/DllExportMacro.h> namespace unicorn { class UNICORN_DLLEXPORT PluginList : public QObject { Q_OBJECT public: PluginList( QObject* parent = 0 ) : QObject( parent ) { m_plugins << (new ITunesPluginInfo( this )); m_plugins << (new WmpPluginInfo( this )); m_plugins << (new WinampPluginInfo( this )); m_plugins << (new FooBar09PluginInfo( this )); } QList<IPluginInfo*> availablePlugins() const; QList<IPluginInfo*> installedPlugins() const; QList<IPluginInfo*> bootstrappablePlugins() const; QList<IPluginInfo*> installList() const; QList<IPluginInfo*> updatedList() const; QList<IPluginInfo*> supportedList() const; QString availableDescription() const; IPluginInfo* pluginById( const QString& id ) const; private: QList<IPluginInfo*> m_plugins; }; } #endif //PLUGIN_LIST_H_ ================================================ FILE: lib/unicorn/plugins/Version.cpp ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "Version.h" #include <QStringList> unicorn::Version::Version() :m_major( 0 ), m_minor( 0 ), m_build( 0 ), m_revision( 0 ) { } unicorn::Version::Version( unsigned int major, unsigned int minor, unsigned int build, unsigned int revision ) :m_major( major ), m_minor( minor ), m_build( build ), m_revision( revision ) { } bool unicorn::Version::operator !=( const Version& that ) const { return !(*this == that); } bool unicorn::Version::operator ==( const Version& that ) const { if( m_major != that.m_major ) return false; if( m_minor != that.m_minor ) return false; if( m_build != that.m_build ) return false; if( m_revision != that.m_revision ) return false; return true; } bool unicorn::Version::operator <( const Version& that ) const { return !(*this == that || *this > that ); } bool unicorn::Version::operator >( const Version& that ) const { if( m_major > that.m_major ) return true; if( m_minor > that.m_minor ) return true; if( m_build > that.m_build ) return true; if( m_revision > that.m_revision ) return true; return false; } QString unicorn::Version::toString() const { return QString( "%1.%2.%3.%4" ).arg( QString::number( m_major ), QString::number( m_minor ), QString::number( m_build ), QString::number( m_revision ) ); } unicorn::Version unicorn::Version::fromString( const QString& string ) { QStringList versionList = string.split( "." ); int major = versionList.count() > 0 ? versionList.at( 0 ).toUInt() : 0; int minor = versionList.count() > 1 ? versionList.at( 1 ).toUInt() : 0; int build = versionList.count() > 2 ? versionList.at( 2 ).toUInt() : 0; int revision = versionList.count() > 3 ? versionList.at( 3 ).toUInt() : 0; return Version( major, minor, build, revision ); } ================================================ FILE: lib/unicorn/plugins/Version.h ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #pragma once #include <lib/DllExportMacro.h> #include <QString> namespace unicorn { class UNICORN_DLLEXPORT Version { public: Version(); Version( unsigned int major, unsigned int minor = 0, unsigned int build = 0, unsigned int revision = 0 ); QString toString() const; static Version fromString( const QString& string ); bool operator !=( const Version& that ) const; bool operator ==( const Version& that ) const; bool operator <( const Version& that ) const; bool operator >( const Version& that ) const; private: unsigned int m_major; unsigned int m_minor; unsigned int m_build; unsigned int m_revision; }; } ================================================ FILE: lib/unicorn/plugins/WinampPluginInfo.cpp ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QStringList> #include "WinampPluginInfo.h" unicorn::WinampPluginInfo::WinampPluginInfo( QObject* parent ) :IPluginInfo( parent ) {} unicorn::Version unicorn::WinampPluginInfo::version() const { return Version( 2, 1, 0, 11 ); } QString unicorn::WinampPluginInfo::name() const { return "Winamp"; } QString unicorn::WinampPluginInfo::displayName() const { return QString( "MPlayer2" ); } QString unicorn::WinampPluginInfo::processName() const { return QString( "winamp.exe" ); } QString unicorn::WinampPluginInfo::id() const { return "wa2"; } bool unicorn::WinampPluginInfo::isAppInstalled() const { return QSettings("HKEY_CURRENT_USER\\Software\\Winamp", QSettings::NativeFormat).contains("."); } unicorn::IPluginInfo::BootstrapType unicorn::WinampPluginInfo::bootstrapType() const { return PluginBootstrap; } QString unicorn::WinampPluginInfo::pluginInstaller() const { return "WinampPluginSetup_2.1.0.11.exe"; } ================================================ FILE: lib/unicorn/plugins/WinampPluginInfo.h ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef WINAMP_PLUGIN_INFO_H_ #define WINAMP_PLUGIN_INFO_H_ #include "../Plugins/IPluginInfo.h" #include <lib/DllExportMacro.h> namespace unicorn { class UNICORN_DLLEXPORT WinampPluginInfo : public IPluginInfo { Q_OBJECT public: WinampPluginInfo( QObject* parent = 0 ); Version version() const; QString name() const; QString displayName() const; QString processName() const; QString id() const; BootstrapType bootstrapType() const; bool isAppInstalled() const; QString pluginInstallPath() const; QString pluginInstaller() const; }; } #endif //WINAMP_PLUGIN_INFO_H_ ================================================ FILE: lib/unicorn/plugins/WmpPluginInfo.cpp ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QStringList> #include "WmpPluginInfo.h" unicorn::WmpPluginInfo::WmpPluginInfo( QObject* parent ) :IPluginInfo( parent ) {} unicorn::Version unicorn::WmpPluginInfo::version() const { return Version( 2, 1, 0, 8 ); } QString unicorn::WmpPluginInfo::name() const { return "Windows Media Player"; } QString unicorn::WmpPluginInfo::displayName() const { return QString( "MPlayer2" ); } QString unicorn::WmpPluginInfo::processName() const { return QString( "wmplayer.exe" ); } QString unicorn::WmpPluginInfo::id() const { return "wmp"; } bool unicorn::WmpPluginInfo::isAppInstalled() const { return QSettings("HKEY_CURRENT_USER\\Software\\Microsoft\\MediaPlayer\\Preferences", QSettings::NativeFormat).contains("FirstRun"); } unicorn::IPluginInfo::BootstrapType unicorn::WmpPluginInfo::bootstrapType() const { return PluginBootstrap; } QString unicorn::WmpPluginInfo::pluginInstaller() const { return "WmpPluginSetup_2.1.0.8.exe"; } ================================================ FILE: lib/unicorn/plugins/WmpPluginInfo.h ================================================ /* Copyright 2010-2013 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef WMP_PLUGIN_INFO_H_ #define WMP_PLUGIN_INFO_H_ #include "../Plugins/IPluginInfo.h" #include <lib/DllExportMacro.h> namespace unicorn { class UNICORN_DLLEXPORT WmpPluginInfo : public IPluginInfo { Q_OBJECT public: WmpPluginInfo( QObject* parent = 0 ); Version version() const; QString name() const; QString displayName() const; QString processName() const; QString id() const; BootstrapType bootstrapType() const; bool isAppInstalled() const; QString pluginInstallPath() const; QString pluginInstaller() const; }; } #endif //WMP_PLUGIN_INFO_H_ ================================================ FILE: lib/unicorn/qrc/unicorn.qrc ================================================ <RCC/> ================================================ FILE: lib/unicorn/qtsingleapplication/qtlocalpeer.cpp ================================================ /**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #include "qtlocalpeer.h" #include <QtCore/QCoreApplication> #include <QtCore/QTime> #if defined(Q_OS_WIN) #include <QtCore/QLibrary> #include <QtCore/qt_windows.h> typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); static PProcessIdToSessionId pProcessIdToSessionId = 0; #endif #if defined(Q_OS_UNIX) #include <time.h> #include <unistd.h> #endif namespace QtLP_Private { #include "qtlockedfile.cpp" #if defined(Q_OS_WIN) #include "qtlockedfile_win.cpp" #else #include "qtlockedfile_unix.cpp" #endif } const char* QtLocalPeer::ack = "ack"; QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) : QObject(parent), id(appId) { QString prefix = id; if (id.isEmpty()) { id = QCoreApplication::applicationFilePath(); #if defined(Q_OS_WIN) id = id.toLower(); #endif prefix = id.section(QLatin1Char('/'), -1); } prefix.remove(QRegExp("[^a-zA-Z]")); prefix.truncate(6); QByteArray idc = id.toUtf8(); quint16 idNum = qChecksum(idc.constData(), idc.size()); socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16); #if defined(Q_OS_WIN) if (!pProcessIdToSessionId) { QLibrary lib("kernel32"); pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); } if (pProcessIdToSessionId) { DWORD sessionId = 0; pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); socketName += QLatin1Char('-') + QString::number(sessionId, 16); } #else socketName += QLatin1Char('-') + QString::number(::getuid(), 16); #endif server = new QLocalServer(this); QString lockName = QDir(QDir::tempPath()).absolutePath() + QLatin1Char('/') + socketName + QLatin1String("-lockfile"); lockFile.setFileName(lockName); lockFile.open(QIODevice::ReadWrite); } bool QtLocalPeer::isClient() { if (lockFile.isLocked()) return false; if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) return true; bool res = server->listen(socketName); #if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) // ### Workaround if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); res = server->listen(socketName); } #endif if (!res) qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); return false; } bool QtLocalPeer::sendMessage(const QStringList &message, int timeout) { if (!isClient()) return false; QLocalSocket socket; bool connOk = false; for(int i = 0; i < 2; i++) { // Try twice, in case the other instance is just starting up socket.connectToServer(socketName); connOk = socket.waitForConnected(timeout/2); if (connOk || i) break; int ms = 250; #if defined(Q_OS_WIN) Sleep(DWORD(ms)); #else struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; nanosleep(&ts, NULL); #endif } if (!connOk) return false; QDataStream ds(&socket); ds << message; bool res = socket.waitForBytesWritten(timeout); res &= socket.waitForReadyRead(timeout); // wait for ack res &= (socket.read(qstrlen(ack)) == ack); return res; } void QtLocalPeer::receiveConnection() { QLocalSocket* socket = server->nextPendingConnection(); if (!socket) return; while (socket->bytesAvailable() < (int)sizeof(quint32)) socket->waitForReadyRead(); QDataStream ds(socket); QStringList message; ds >> message; socket->write(ack, qstrlen(ack)); socket->waitForBytesWritten(1000); delete socket; emit messageReceived(message); //### (might take a long time to return) } ================================================ FILE: lib/unicorn/qtsingleapplication/qtlocalpeer.h ================================================ /**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #include <QtNetwork/QLocalServer> #include <QtNetwork/QLocalSocket> #include <QtCore/QDir> namespace QtLP_Private { #include "qtlockedfile.h" } class QtLocalPeer : public QObject { Q_OBJECT public: QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); bool isClient(); bool sendMessage(const QStringList &message, int timeout); QString applicationId() const { return id; } Q_SIGNALS: void messageReceived(const QStringList &message); protected Q_SLOTS: void receiveConnection(); protected: QString id; QString socketName; QLocalServer* server; QtLP_Private::QtLockedFile lockFile; private: static const char* ack; }; ================================================ FILE: lib/unicorn/qtsingleapplication/qtlockedfile.cpp ================================================ /**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #include "qtlockedfile.h" /*! \class QtLockedFile \brief The QtLockedFile class extends QFile with advisory locking functions. A file may be locked in read or write mode. Multiple instances of \e QtLockedFile, created in multiple processes running on the same machine, may have a file locked in read mode. Exactly one instance may have it locked in write mode. A read and a write lock cannot exist simultaneously on the same file. The file locks are advisory. This means that nothing prevents another process from manipulating a locked file using QFile or file system functions offered by the OS. Serialization is only guaranteed if all processes that access the file use QLockedFile. Also, while holding a lock on a file, a process must not open the same file again (through any API), or locks can be unexpectedly lost. The lock provided by an instance of \e QtLockedFile is released whenever the program terminates. This is true even when the program crashes and no destructors are called. */ /*! \enum QtLockedFile::LockMode This enum describes the available lock modes. \value ReadLock A read lock. \value WriteLock A write lock. \value NoLock Neither a read lock nor a write lock. */ /*! Constructs an unlocked \e QtLockedFile object. This constructor behaves in the same way as \e QFile::QFile(). \sa QFile::QFile() */ QtLockedFile::QtLockedFile() : QFile() { #ifdef Q_OS_WIN wmutex = 0; rmutex = 0; #endif m_lock_mode = NoLock; } /*! Constructs an unlocked QtLockedFile object with file \a name. This constructor behaves in the same way as \e QFile::QFile(const QString&). \sa QFile::QFile() */ QtLockedFile::QtLockedFile(const QString &name) : QFile(name) { #ifdef Q_OS_WIN wmutex = 0; rmutex = 0; #endif m_lock_mode = NoLock; } /*! Opens the file in OpenMode \a mode. This is identical to QFile::open(), with the one exception that the Truncate mode flag is disallowed. Truncation would conflict with the advisory file locking, since the file would be modified before the write lock is obtained. If truncation is required, use resize(0) after obtaining the write lock. Returns true if successful; otherwise false. \sa QFile::open(), QFile::resize() */ bool QtLockedFile::open(OpenMode mode) { if (mode & QIODevice::Truncate) { qWarning("QtLockedFile::open(): Truncate mode not allowed."); return false; } return QFile::open(mode); } /*! Returns \e true if this object has a in read or write lock; otherwise returns \e false. \sa lockMode() */ bool QtLockedFile::isLocked() const { return m_lock_mode != NoLock; } /*! Returns the type of lock currently held by this object, or \e QtLockedFile::NoLock. \sa isLocked() */ QtLockedFile::LockMode QtLockedFile::lockMode() const { return m_lock_mode; } /*! \fn bool QtLockedFile::lock(LockMode mode, bool block = true) Obtains a lock of type \a mode. The file must be opened before it can be locked. If \a block is true, this function will block until the lock is aquired. If \a block is false, this function returns \e false immediately if the lock cannot be aquired. If this object already has a lock of type \a mode, this function returns \e true immediately. If this object has a lock of a different type than \a mode, the lock is first released and then a new lock is obtained. This function returns \e true if, after it executes, the file is locked by this object, and \e false otherwise. \sa unlock(), isLocked(), lockMode() */ /*! \fn bool QtLockedFile::unlock() Releases a lock. If the object has no lock, this function returns immediately. This function returns \e true if, after it executes, the file is not locked by this object, and \e false otherwise. \sa lock(), isLocked(), lockMode() */ /*! \fn QtLockedFile::~QtLockedFile() Destroys the \e QtLockedFile object. If any locks were held, they are released. */ ================================================ FILE: lib/unicorn/qtsingleapplication/qtlockedfile.h ================================================ /**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #ifndef QTLOCKEDFILE_H #define QTLOCKEDFILE_H #include <QtCore/QFile> #ifdef Q_OS_WIN #include <QtCore/QVector> #endif #if defined(Q_WS_WIN) # if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) # define QT_QTLOCKEDFILE_EXPORT # elif defined(QT_QTLOCKEDFILE_IMPORT) # if defined(QT_QTLOCKEDFILE_EXPORT) # undef QT_QTLOCKEDFILE_EXPORT # endif # define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) # elif defined(QT_QTLOCKEDFILE_EXPORT) # undef QT_QTLOCKEDFILE_EXPORT # define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) # endif #else # define QT_QTLOCKEDFILE_EXPORT #endif class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile { public: enum LockMode { NoLock = 0, ReadLock, WriteLock }; QtLockedFile(); QtLockedFile(const QString &name); ~QtLockedFile(); bool open(OpenMode mode); bool lock(LockMode mode, bool block = true); bool unlock(); bool isLocked() const; LockMode lockMode() const; private: #ifdef Q_OS_WIN Qt::HANDLE wmutex; Qt::HANDLE rmutex; QVector<Qt::HANDLE> rmutexes; QString mutexname; Qt::HANDLE getMutexHandle(int idx, bool doCreate); bool waitMutex(Qt::HANDLE mutex, bool doBlock); #endif LockMode m_lock_mode; }; #endif ================================================ FILE: lib/unicorn/qtsingleapplication/qtlockedfile_unix.cpp ================================================ /**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #include <string.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include "qtlockedfile.h" bool QtLockedFile::lock(LockMode mode, bool block) { if (!isOpen()) { qWarning("QtLockedFile::lock(): file is not opened"); return false; } if (mode == NoLock) return unlock(); if (mode == m_lock_mode) return true; if (m_lock_mode != NoLock) unlock(); struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; int cmd = block ? F_SETLKW : F_SETLK; int ret = fcntl(handle(), cmd, &fl); if (ret == -1) { if (errno != EINTR && errno != EAGAIN) qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); return false; } m_lock_mode = mode; return true; } bool QtLockedFile::unlock() { if (!isOpen()) { qWarning("QtLockedFile::unlock(): file is not opened"); return false; } if (!isLocked()) return true; struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_type = F_UNLCK; int ret = fcntl(handle(), F_SETLKW, &fl); if (ret == -1) { qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); return false; } m_lock_mode = NoLock; return true; } QtLockedFile::~QtLockedFile() { if (isOpen()) unlock(); } ================================================ FILE: lib/unicorn/qtsingleapplication/qtlockedfile_win.cpp ================================================ /**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #include "qtlockedfile.h" #include <qt_windows.h> #include <QtCore/QFileInfo> #define MUTEX_PREFIX "QtLockedFile mutex " // Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS #define MAX_READERS MAXIMUM_WAIT_OBJECTS Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) { if (mutexname.isEmpty()) { QFileInfo fi(*this); mutexname = QString::fromLatin1(MUTEX_PREFIX) + fi.absoluteFilePath().toLower(); } QString mname(mutexname); if (idx >= 0) mname += QString::number(idx); Qt::HANDLE mutex; if (doCreate) { QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); if (!mutex) { qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); return 0; } } else { QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); if (!mutex) { if (GetLastError() != ERROR_FILE_NOT_FOUND) qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); return 0; } } return mutex; } bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) { Q_ASSERT(mutex); DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); switch (res) { case WAIT_OBJECT_0: case WAIT_ABANDONED: return true; break; case WAIT_TIMEOUT: break; default: qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); } return false; } bool QtLockedFile::lock(LockMode mode, bool block) { if (!isOpen()) { qWarning("QtLockedFile::lock(): file is not opened"); return false; } if (mode == NoLock) return unlock(); if (mode == m_lock_mode) return true; if (m_lock_mode != NoLock) unlock(); if (!wmutex && !(wmutex = getMutexHandle(-1, true))) return false; if (!waitMutex(wmutex, block)) return false; if (mode == ReadLock) { int idx = 0; for (; idx < MAX_READERS; idx++) { rmutex = getMutexHandle(idx, false); if (!rmutex || waitMutex(rmutex, false)) break; CloseHandle(rmutex); } bool ok = true; if (idx >= MAX_READERS) { qWarning("QtLockedFile::lock(): too many readers"); rmutex = 0; ok = false; } else if (!rmutex) { rmutex = getMutexHandle(idx, true); if (!rmutex || !waitMutex(rmutex, false)) ok = false; } if (!ok && rmutex) { CloseHandle(rmutex); rmutex = 0; } ReleaseMutex(wmutex); if (!ok) return false; } else { Q_ASSERT(rmutexes.isEmpty()); for (int i = 0; i < MAX_READERS; i++) { Qt::HANDLE mutex = getMutexHandle(i, false); if (mutex) rmutexes.append(mutex); } if (rmutexes.size()) { DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), TRUE, block ? INFINITE : 0); if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { if (res != WAIT_TIMEOUT) qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky unlock(); return false; } } } m_lock_mode = mode; return true; } bool QtLockedFile::unlock() { if (!isOpen()) { qWarning("QtLockedFile::unlock(): file is not opened"); return false; } if (!isLocked()) return true; if (m_lock_mode == ReadLock) { ReleaseMutex(rmutex); CloseHandle(rmutex); rmutex = 0; } else { foreach(Qt::HANDLE mutex, rmutexes) { ReleaseMutex(mutex); CloseHandle(mutex); } rmutexes.clear(); ReleaseMutex(wmutex); } m_lock_mode = QtLockedFile::NoLock; return true; } QtLockedFile::~QtLockedFile() { if (isOpen()) unlock(); if (wmutex) CloseHandle(wmutex); } ================================================ FILE: lib/unicorn/qtsingleapplication/qtsingleapplication.cpp ================================================ /**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #include "qtsingleapplication.h" #include "qtlocalpeer.h" #include <QtGui/QWidget> /*! \class QtSingleApplication qtsingleapplication.h \brief The QtSingleApplication class provides an API to detect and communicate with running instances of an application. This class allows you to create applications where only one instance should be running at a time. I.e., if the user tries to launch another instance, the already running instance will be activated instead. Another usecase is a client-server system, where the first started instance will assume the role of server, and the later instances will act as clients of that server. By default, the full path of the executable file is used to determine whether two processes are instances of the same application. You can also provide an explicit identifier string that will be compared instead. The application should create the QtSingleApplication object early in the startup phase, and call isRunning() or sendMessage() to find out if another instance of this application is already running. Startup parameters (e.g. the name of the file the user wanted this new instance to open) can be passed to the running instance in the sendMessage() function. If isRunning() or sendMessage() returns false, it means that no other instance is running, and this instance has assumed the role as the running instance. The application should continue with the initialization of the application user interface before entering the event loop with exec(), as normal. The messageReceived() signal will be emitted when the application receives messages from another instance of the same application. If isRunning() or sendMessage() returns true, another instance is already running, and the application should terminate or enter client mode. If a message is received it might be helpful to the user to raise the application so that it becomes visible. To facilitate this, QtSingleApplication provides the setActivationWindow() function and the activateWindow() slot. Here's an example that shows how to convert an existing application to use QtSingleApplication. It is very simple and does not make use of all QtSingleApplication's functionality (see the examples for that). \code // Original int main(int argc, char **argv) { QApplication app(argc, argv); MyMainWidget mmw; mmw.show(); return app.exec(); } // Single instance int main(int argc, char **argv) { QtSingleApplication app(argc, argv); if (app.isRunning()) return 0; MyMainWidget mmw; app.setActivationWindow(&mmw); mmw.show(); return app.exec(); } \endcode Once this QtSingleApplication instance is destroyed(for example, when the user quits), when the user next attempts to run the application this instance will not, of course, be encountered. The next instance to call isRunning() or sendMessage() will assume the role as the new running instance. For console (non-GUI) applications, QtSingleCoreApplication may be used instead of this class, to avoid the dependency on the QtGui library. \sa QtSingleCoreApplication */ void QtSingleApplication::sysInit(const QString &appId) { actWin = 0; peer = new QtLocalPeer(this, appId); connect(peer, SIGNAL(messageReceived(const QStringList&)), SIGNAL(messageReceived(const QStringList&))); } /*! Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). \a argc, \a argv, and \a GUIenabled are passed on to the QAppliation constructor. If you are creating a console application (i.e. setting \a GUIenabled to false), you may consider using QtSingleCoreApplication instead. */ QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) : QApplication(argc, argv, GUIenabled) { sysInit(); } /*! Creates a QtSingleApplication object with the application identifier \a appId. \a argc and \a argv are passed on to the QAppliation constructor. */ QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) : QApplication(argc, argv) { sysInit(appId); } /*! Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). \a argc, \a argv, and \a type are passed on to the QAppliation constructor. */ QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) : QApplication(argc, argv, type) { sysInit(); } #if defined(Q_WS_X11) /*! Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, and \a cmap are passed on to the QApplication constructor. */ QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) : QApplication(dpy, visual, cmap) { sysInit(); } /*! Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a argv, \a visual, and \a cmap are passed on to the QApplication constructor. */ QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) : QApplication(dpy, argc, argv, visual, cmap) { sysInit(); } /*! Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be \a appId. \a dpy, \a argc, \a argv, \a visual, and \a cmap are passed on to the QApplication constructor. */ QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) : QApplication(dpy, argc, argv, visual, cmap) { sysInit(appId); } #endif /*! Returns true if another instance of this application is running; otherwise false. This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session). \sa sendMessage() */ bool QtSingleApplication::isRunning() { return peer->isClient(); } /*! Tries to send the text \a message to the currently running instance. The QtSingleApplication object in the running instance will emit the messageReceived() signal when it receives the message. This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within \a timeout milliseconds, this function return false. \sa isRunning(), messageReceived() */ bool QtSingleApplication::sendMessage(const QStringList &message, int timeout) { return peer->sendMessage(message, timeout); } /*! Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application. */ QString QtSingleApplication::id() const { return peer->applicationId(); } /*! Sets the activation window of this application to \a aw. The activation window is the widget that will be activated by activateWindow(). This is typically the application's main window. If \a activateOnMessage is true (the default), the window will be activated automatically every time a message is received, just prior to the messageReceived() signal being emitted. \sa activateWindow(), messageReceived() */ void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) { actWin = aw; if (activateOnMessage) connect(peer, SIGNAL(messageReceived(const QStringList&)), this, SLOT(activateWindow())); else disconnect(peer, SIGNAL(messageReceived(const QStringList&)), this, SLOT(activateWindow())); } /*! Returns the applications activation window if one has been set by calling setActivationWindow(), otherwise returns 0. \sa setActivationWindow() */ QWidget* QtSingleApplication::activationWindow() const { return actWin; } /*! De-minimizes, raises, and activates this application's activation window. This function does nothing if no activation window has been set. This is a convenience function to show the user that this application instance has been activated when he has tried to start another instance. This function should typically be called in response to the messageReceived() signal. By default, that will happen automatically, if an activation window has been set. \sa setActivationWindow(), messageReceived(), initialize() */ void QtSingleApplication::activateWindow() { if (actWin) { actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); actWin->raise(); actWin->activateWindow(); } } /*! \fn void QtSingleApplication::messageReceived(const QString& message) This signal is emitted when the current instance receives a \a message from another instance of this application. \sa sendMessage(), setActivationWindow(), activateWindow() */ /*! \fn void QtSingleApplication::initialize(bool dummy = true) \obsolete */ ================================================ FILE: lib/unicorn/qtsingleapplication/qtsingleapplication.h ================================================ /**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #include <QtGui/QApplication> #include "lib/DllExportMacro.h" class QtLocalPeer; class UNICORN_DLLEXPORT QtSingleApplication : public QApplication { Q_OBJECT public: QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); QtSingleApplication(const QString &id, int &argc, char **argv); QtSingleApplication(int &argc, char **argv, Type type); #if defined(Q_WS_X11) QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); #endif bool isRunning(); QString id() const; void setActivationWindow(QWidget* aw, bool activateOnMessage = true); QWidget* activationWindow() const; // Obsolete: void initialize(bool dummy = true) { isRunning(); Q_UNUSED(dummy) } public Q_SLOTS: bool sendMessage(const QStringList &message, int timeout = 5000); void activateWindow(); Q_SIGNALS: void messageReceived(const QStringList &message); private: void sysInit(const QString &appId = QString()); QtLocalPeer *peer; QWidget *actWin; }; ================================================ FILE: lib/unicorn/qtsingleapplication/qtsinglecoreapplication.cpp ================================================ /**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #include "qtsinglecoreapplication.h" #include "qtlocalpeer.h" /*! \class QtSingleCoreApplication qtsinglecoreapplication.h \brief A variant of the QtSingleApplication class for non-GUI applications. This class is a variant of QtSingleApplication suited for use in console (non-GUI) applications. It is an extension of QCoreApplication (instead of QApplication). It does not require the QtGui library. The API and usage is identical to QtSingleApplication, except that functions relating to the "activation window" are not present, for obvious reasons. Please refer to the QtSingleApplication documentation for explanation of the usage. A QtSingleCoreApplication instance can communicate to a QtSingleApplication instance if they share the same application id. Hence, this class can be used to create a light-weight command-line tool that sends commands to a GUI application. \sa QtSingleApplication */ /*! Creates a QtSingleCoreApplication object. The application identifier will be QCoreApplication::applicationFilePath(). \a argc and \a argv are passed on to the QCoreAppliation constructor. */ QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) : QCoreApplication(argc, argv) { peer = new QtLocalPeer(this); connect(peer, SIGNAL(messageReceived(const QStringList&)), SIGNAL(messageReceived(const QStringList&))); } /*! Creates a QtSingleCoreApplication object with the application identifier \a appId. \a argc and \a argv are passed on to the QCoreAppliation constructor. */ QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) : QCoreApplication(argc, argv) { peer = new QtLocalPeer(this, appId); connect(peer, SIGNAL(messageReceived(const QStringList&)), SIGNAL(messageReceived(const QStringList&))); } /*! Returns true if another instance of this application is running; otherwise false. This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session). \sa sendMessage() */ bool QtSingleCoreApplication::isRunning() { return peer->isClient(); } /*! Tries to send the text \a message to the currently running instance. The QtSingleCoreApplication object in the running instance will emit the messageReceived() signal when it receives the message. This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within \a timeout milliseconds, this function return false. \sa isRunning(), messageReceived() */ bool QtSingleCoreApplication::sendMessage(const QStringList &message, int timeout) { return peer->sendMessage(message, timeout); } /*! Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application. */ QString QtSingleCoreApplication::id() const { return peer->applicationId(); } /*! \fn void QtSingleCoreApplication::messageReceived(const QString& message) This signal is emitted when the current instance receives a \a message from another instance of this application. \sa sendMessage() */ ================================================ FILE: lib/unicorn/qtsingleapplication/qtsinglecoreapplication.h ================================================ /**************************************************************************** ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of a Qt Solutions component. ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact Nokia at qt-info@nokia.com. ** ****************************************************************************/ #include <QtCore/QCoreApplication> #include "lib/DllExportMacro.h" class QtLocalPeer; class UNICORN_DLLEXPORT QtSingleCoreApplication : public QCoreApplication { Q_OBJECT public: QtSingleCoreApplication(int &argc, char **argv); QtSingleCoreApplication(const QString &id, int &argc, char **argv); bool isRunning(); QString id() const; public Q_SLOTS: bool sendMessage(const QStringList &message, int timeout = 5000); Q_SIGNALS: void messageReceived(const QStringList &message); private: QtLocalPeer* peer; }; ================================================ FILE: lib/unicorn/qtwin.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** ** Use, modification and distribution is allowed without limitation, ** warranty, liability or support of any kind. ** ****************************************************************************/ #include "qtwin.h" #include <QLibrary> #include <QApplication> #include <QWidget> #include <QList> #include <QPointer> #ifdef Q_WS_WIN #include <qt_windows.h> // Blur behind data structures #define DWM_BB_ENABLE 0x00000001 // fEnable has been specified #define DWM_BB_BLURREGION 0x00000002 // hRgnBlur has been specified #define DWM_BB_TRANSITIONONMAXIMIZED 0x00000004 // fTransitionOnMaximized has been specified #define WM_DWMCOMPOSITIONCHANGED 0x031E // Composition changed window message typedef struct _DWM_BLURBEHIND { DWORD dwFlags; BOOL fEnable; HRGN hRgnBlur; BOOL fTransitionOnMaximized; } DWM_BLURBEHIND, *PDWM_BLURBEHIND; typedef struct _MARGINS { int cxLeftWidth; int cxRightWidth; int cyTopHeight; int cyBottomHeight; } MARGINS, *PMARGINS; typedef HRESULT (WINAPI *PtrDwmIsCompositionEnabled)(BOOL* pfEnabled); typedef HRESULT (WINAPI *PtrDwmExtendFrameIntoClientArea)(HWND hWnd, const MARGINS* pMarInset); typedef HRESULT (WINAPI *PtrDwmEnableBlurBehindWindow)(HWND hWnd, const DWM_BLURBEHIND* pBlurBehind); typedef HRESULT (WINAPI *PtrDwmGetColorizationColor)(DWORD *pcrColorization, BOOL *pfOpaqueBlend); static PtrDwmIsCompositionEnabled pDwmIsCompositionEnabled= 0; static PtrDwmEnableBlurBehindWindow pDwmEnableBlurBehindWindow = 0; static PtrDwmExtendFrameIntoClientArea pDwmExtendFrameIntoClientArea = 0; static PtrDwmGetColorizationColor pDwmGetColorizationColor = 0; /* * Internal helper class that notifies windows if the * DWM compositing state changes and updates the widget * flags correspondingly. */ class WindowNotifier : public QWidget { public: WindowNotifier() { winId(); } void addWidget(QWidget *widget) { widgets.append(widget); } void removeWidget(QWidget *widget) { widgets.removeAll(widget); } bool winEvent(MSG *message, long *result); private: QWidgetList widgets; }; static bool resolveLibs() { if (!pDwmIsCompositionEnabled) { QLibrary dwmLib(QString::fromAscii("dwmapi")); pDwmIsCompositionEnabled =(PtrDwmIsCompositionEnabled)dwmLib.resolve("DwmIsCompositionEnabled"); pDwmExtendFrameIntoClientArea = (PtrDwmExtendFrameIntoClientArea)dwmLib.resolve("DwmExtendFrameIntoClientArea"); pDwmEnableBlurBehindWindow = (PtrDwmEnableBlurBehindWindow)dwmLib.resolve("DwmEnableBlurBehindWindow"); pDwmGetColorizationColor = (PtrDwmGetColorizationColor)dwmLib.resolve("DwmGetColorizationColor"); } return pDwmIsCompositionEnabled != 0; } #endif /*! * Chekcs and returns true if Windows DWM composition * is currently enabled on the system. * * To get live notification on the availability of * this feature, you will currently have to * reimplement winEvent() on your widget and listen * for the WM_DWMCOMPOSITIONCHANGED event to occur. * */ bool QtWin::isCompositionEnabled() { #ifdef Q_WS_WIN if (resolveLibs()) { HRESULT hr = S_OK; BOOL isEnabled = false; hr = pDwmIsCompositionEnabled(&isEnabled); if (SUCCEEDED(hr)) return isEnabled; } #endif return false; } /*! * Enables Blur behind on a Widget. * * \a enable tells if the blur should be enabled or not */ bool QtWin::enableBlurBehindWindow(QWidget *widget, bool enable) { Q_ASSERT(widget); bool result = false; #ifdef Q_WS_WIN if (resolveLibs()) { DWM_BLURBEHIND bb = {0}; HRESULT hr = S_OK; bb.fEnable = enable; bb.dwFlags = DWM_BB_ENABLE; bb.hRgnBlur = NULL; widget->setAttribute(Qt::WA_TranslucentBackground, enable); widget->setAttribute(Qt::WA_NoSystemBackground, enable); hr = pDwmEnableBlurBehindWindow(widget->winId(), &bb); if (SUCCEEDED(hr)) { result = true; windowNotifier()->addWidget(widget); } } #else Q_UNUSED(enable) #endif return result; } /*! * ExtendFrameIntoClientArea. * * This controls the rendering of the frame inside the window. * Note that passing margins of -1 (the default value) will completely * remove the frame from the window. * * \note you should not call enableBlurBehindWindow before calling * this functions * * \a enable tells if the blur should be enabled or not */ bool QtWin::extendFrameIntoClientArea(QWidget *widget, int left, int top, int right, int bottom) { Q_ASSERT(widget); Q_UNUSED(left); Q_UNUSED(top); Q_UNUSED(right); Q_UNUSED(bottom); bool result = false; #ifdef Q_WS_WIN if (resolveLibs()) { QLibrary dwmLib(QString::fromAscii("dwmapi")); HRESULT hr = S_OK; MARGINS m = {left, top, right, bottom}; hr = pDwmExtendFrameIntoClientArea(widget->winId(), &m); if (SUCCEEDED(hr)) { result = true; windowNotifier()->addWidget(widget); } widget->setAttribute(Qt::WA_TranslucentBackground, result); } #endif return result; } /*! * Returns the current colorizationColor for the window. * * \a enable tells if the blur should be enabled or not */ QColor QtWin::colorizatinColor() { QColor resultColor = QApplication::palette().window().color(); #ifdef Q_WS_WIN if (resolveLibs()) { DWORD color = 0; BOOL opaque = FALSE; QLibrary dwmLib(QString::fromAscii("dwmapi")); HRESULT hr = S_OK; hr = pDwmGetColorizationColor(&color, &opaque); if (SUCCEEDED(hr)) resultColor = QColor(color); } #endif return resultColor; } #ifdef Q_WS_WIN WindowNotifier *QtWin::windowNotifier() { static WindowNotifier *windowNotifierInstance = 0; if (!windowNotifierInstance) windowNotifierInstance = new WindowNotifier; return windowNotifierInstance; } /* Notify all enabled windows that the DWM state changed */ bool WindowNotifier::winEvent(MSG *message, long *result) { if (message && message->message == WM_DWMCOMPOSITIONCHANGED) { bool compositionEnabled = QtWin::isCompositionEnabled(); foreach(QWidget * widget, widgets) { if (widget) { widget->setAttribute(Qt::WA_NoSystemBackground, compositionEnabled); } widget->update(); } } return QWidget::winEvent(message, result); } #endif ================================================ FILE: lib/unicorn/qtwin.h ================================================ /**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** ** Use, modification and distribution is allowed without limitation, ** warranty, liability or support of any kind. ** ****************************************************************************/ #ifndef QTWIN_H #define QTWIN_H #include <lib/DllExportMacro.h> #include <QColor> #include <QWidget> /** * This is a helper class for using the Desktop Window Manager * functionality on Windows 7 and Windows Vista. On other platforms * these functions will simply not do anything. */ class WindowNotifier; class UNICORN_DLLEXPORT QtWin { public: static bool enableBlurBehindWindow(QWidget *widget, bool enable = true); static bool extendFrameIntoClientArea(QWidget *widget, int left = -1, int top = -1, int right = -1, int bottom = -1); static bool isCompositionEnabled(); static QColor colorizatinColor(); private: static WindowNotifier *windowNotifier(); }; #endif // QTWIN_H ================================================ FILE: lib/unicorn/unicorn.pro ================================================ TARGET = unicorn TEMPLATE = lib CONFIG += dll lastfm sparkle growl logger unix:!mac { CONFIG -= dll CONFIG += staticlib QMAKE_DISTCLEAN += -f ../../_bin/libunicorn.a } QT = core gui xml network include( ../../admin/include.qmake ) DEFINES += _UNICORN_DLLEXPORT LASTFM_COLLAPSE_NAMESPACE DEFINES += API_KEY=\\\"$(LASTFM_API_KEY)\\\" DEFINES += API_SECRET=\\\"$(LASTFM_API_SECRET)\\\" # UniqueApplication win32:LIBS += user32.lib shell32.lib ole32.lib kernel32.lib psapi.lib macx:LIBS += -weak_framework Cocoa SOURCES += \ dialogs/ShareDialog.cpp \ widgets/AvatarWidget.cpp \ layouts/FlowLayout.cpp \ widgets/UserMenu.cpp \ widgets/SlidingStackedWidget.cpp \ widgets/UserToolButton.cpp \ widgets/UserManagerWidget.cpp \ widgets/UnicornTabWidget.cpp \ widgets/TrackWidget.cpp \ widgets/TagListWidget.cpp \ widgets/StatusLight.cpp \ widgets/SearchBox.cpp \ widgets/MessageBar.cpp \ widgets/LfmListViewWidget.cpp \ widgets/Label.cpp \ widgets/ItemSelectorWidget.cpp \ widgets/ImageButton.cpp \ widgets/HttpImageWidget.cpp \ widgets/GhostWidget.cpp \ widgets/FriendsPicker.cpp \ widgets/DataListWidget.cpp \ widgets/BannerWidget.cpp \ widgets/ActionButton.cpp \ UpdateInfoFetcher.cpp \ UnicornSettings.cpp \ UnicornSession.cpp \ UnicornMainWindow.cpp \ UnicornCoreApplication.cpp \ UnicornApplication.cpp \ TrackImageFetcher.cpp \ ScrobblesModel.cpp \ qtwin.cpp \ qtsingleapplication/qtsinglecoreapplication.cpp \ qtsingleapplication/qtsingleapplication.cpp \ qtsingleapplication/qtlockedfile_unix.cpp \ qtsingleapplication/qtlockedfile.cpp \ qtsingleapplication/qtlocalpeer.cpp \ QMessageBoxBuilder.cpp \ LoginProcess.cpp \ PlayBus/PlayBus.cpp \ PlayBus/Bus.cpp \ dialogs/UserManagerDialog.cpp \ dialogs/TagDialog.cpp \ dialogs/LoginDialog.cpp \ dialogs/LoginContinueDialog.cpp \ dialogs/AboutDialog.cpp \ dialogs/ScrobbleConfirmationDialog.cpp \ dialogs/CloseAppsDialog.cpp \ AnimatedStatusBar.cpp \ DesktopServices.cpp \ Updater/Updater.cpp \ widgets/StackedWidget.cpp \ widgets/ProxyWidget.cpp \ dialogs/ProxyDialog.cpp \ plugins/Version.cpp HEADERS += \ widgets/UserToolButton.h \ widgets/UserMenu.h \ widgets/UserManagerWidget.h \ widgets/UserComboSelector.h \ widgets/UnicornTabWidget.h \ widgets/TrackWidget.h \ widgets/TagListWidget.h \ widgets/StatusLight.h \ widgets/SpinnerLabel.h \ widgets/Seed.h \ widgets/SearchBox.h \ widgets/PlayableMimeData.h \ widgets/MessageBar.h \ widgets/LfmListViewWidget.h \ widgets/Label.h \ widgets/ItemSelectorWidget.h \ widgets/ImageButton.h \ widgets/HttpImageWidget.h \ widgets/GhostWidget.h \ widgets/FriendsPicker.h \ widgets/DataListWidget.h \ widgets/DataBox.h \ widgets/BannerWidget.h \ widgets/ActionButton.h \ UpdateInfoFetcher.h \ UnicornSettings.h \ UnicornSession.h \ UnicornMainWindow.h \ UnicornCoreApplication.h \ UnicornApplication.h \ TrackImageFetcher.h \ SignalBlocker.h \ ScrobblesModel.h \ qtwin.h \ qtsingleapplication/qtsinglecoreapplication.h \ qtsingleapplication/qtsingleapplication.h \ qtsingleapplication/qtlockedfile.h \ qtsingleapplication/qtlocalpeer.h \ QMessageBoxBuilder.h \ PlayBus/Bus.h \ PlayBus/PlayBus.h \ LoginProcess.h \ dialogs/UserManagerDialog.h \ dialogs/UnicornDialog.h \ dialogs/TagDialog.h \ dialogs/LoginDialog.h \ dialogs/LoginContinueDialog.h \ dialogs/AboutDialog.h \ dialogs/ScrobbleConfirmationDialog.h \ dialogs/CloseAppsDialog.h \ AnimatedStatusBar.h \ AnimatedPushButton.h \ dialogs/ShareDialog.h \ widgets/AvatarWidget.h \ layouts/FlowLayout.h \ widgets/SlidingStackedWidget.h \ Updater/Updater.h \ DesktopServices.h \ widgets/StackedWidget.h \ widgets/ProxyWidget.h \ dialogs/ProxyDialog.h \ plugins/Version.h win32:HEADERS += plugins/FooBar08PluginInfo.h \ plugins/FooBar09PluginInfo.h \ plugins/ITunesPluginInfo.h \ plugins/WinampPluginInfo.h \ plugins/WmpPluginInfo.h \ plugins/PluginList.h \ plugins/KillProcess.h \ plugins/IPluginInfo.h win32:SOURCES += qtsingleapplication/qtlockedfile_win.cpp\ plugins/PluginList.cpp \ plugins/IPluginInfo.cpp \ plugins/FooBar08PluginInfo.cpp \ plugins/FooBar09PluginInfo.cpp \ plugins/ITunesPluginInfo.cpp \ plugins/WinampPluginInfo.cpp \ plugins/WmpPluginInfo.cpp macx:SOURCES += mac/AppleScript.cpp \ plugins/ITunesPluginInstaller.cpp macx:OBJECTIVE_SOURCES += UnicornApplication_mac.mm \ notify/Notify.mm \ Updater/Updater_mac.mm \ UnicornApplicationDelegate.mm \ dialogs/CloseAppsDialog_mac.mm macx:HEADERS += mac/AppleScript.h \ notify/Notify.h \ UnicornApplicationDelegate.h \ plugins/ITunesPluginInstaller.h \ CONFIG( break ) { HEADERS += CrashReporter/CrashReporter.h SOURCES += CrashReporter/CrashReporter.cpp macx:OBJECTIVE_SOURCES += CrashReporter/CrashReporter_mac.mm } FORMS += \ dialogs/ShareDialog.ui \ dialogs/TagDialog.ui \ dialogs/AboutDialog.ui \ dialogs/ScrobbleConfirmationDialog.ui \ widgets/ProxyWidget.ui \ dialogs/ProxyDialog.ui \ dialogs/CloseAppsDialog.ui unix:!mac { FORMS -= dialogs/CloseAppsDialog.ui SOURCES -= dialogs/CloseAppsDialog.cpp \ plugins/Version.cpp \ Updater/Updater.cpp HEADERS -= dialogs/CloseAppsDialog.cpp \ plugins/Version.h \ Updater/Updater.h } ================================================ FILE: lib/unicorn/widgets/ActionButton.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "ActionButton.h" #include <QAction> void ActionButton::setAction( QAction* action ) { const bool b = action->isCheckable(); setCheckable( b ); // only do one or the other or you trigger it all twice if (b) connect( this, SIGNAL(toggled( bool )), action, SLOT(setChecked( bool )) ); else connect( this, SIGNAL(clicked()), action, SLOT(trigger()) ); connect( action, SIGNAL(changed()), SLOT(onActionChanged()) ); onActionChanged( action ); } void ActionButton::onActionChanged( QAction* action ) { if (!action) action = (QAction*) sender(); setEnabled( action->isEnabled()); setChecked( action->isChecked()); } ================================================ FILE: lib/unicorn/widgets/ActionButton.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef ACTION_BUTTON_H #define ACTION_BUTTON_H #include <QAbstractButton> #include "lib/DllExportMacro.h" class UNICORN_DLLEXPORT ActionButton : public QAbstractButton { Q_OBJECT public: ActionButton( QWidget* parent = 0 ) : QAbstractButton( parent ) {} void setAction( class QAction* ); private slots: void onActionChanged( QAction* = 0 ); }; #endif ================================================ FILE: lib/unicorn/widgets/AvatarWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "AvatarWidget.h" AvatarWidget::AvatarWidget( QWidget *parent ) : HttpImageWidget(parent) { setPixmap( QPixmap( ":/user_default.png" ) ); } void AvatarWidget::setUser( const lastfm::User& user ) { m_user = user; update(); } void AvatarWidget::paintEvent( QPaintEvent* paintEvent ) { HttpImageWidget::paintEvent( paintEvent ); if ( m_user.isSubscriber() ) { QPainter p( this ); QString text = tr( "Subscriber" ); QColor brush( Qt::black ); switch ( m_user.type() ) { case User::TypeModerator: text = tr( "Moderator" ); brush = QColor( 0xFFA500 ); break; case User::TypeStaff: text = tr( "Staff" ); brush = QColor( 0xD51007 ); break; case User::TypeAlumni: text = m_user.gender().female() ? tr( "Alumna" ) : tr( "Alumnus" ); brush = QColor( 0x5336BD ); break; default: break; } QFont font = p.font(); font.setPixelSize( 10 ); font.setWeight( QFont::Bold ); p.setFont( font ); // the 8 and the 20 are from the stylesheet // (horrible, but there's no way to find those values out and this works) int totalHeight = rect().height() - 8; int totalWidth = rect().width() - 20; QFontMetrics fm( font ); int width = fm.width( text ) + 4; int height = fm.height() + 4; QRect rect( (totalWidth / 2) - (width / 2), totalHeight - (height / 2), width, height ) ; p.setBrush( brush ); p.drawRoundedRect( rect, 4, 4 ); p.setPen( QColor( Qt::white ) ); p.drawText( rect, Qt::AlignCenter, text ); } } ================================================ FILE: lib/unicorn/widgets/AvatarWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef AVATARWIDGET_H #define AVATARWIDGET_H #include <lastfm/User.h> #include "lib/unicorn/widgets/HttpImageWidget.h" #include "lib/DllExportMacro.h" class UNICORN_DLLEXPORT AvatarWidget : public HttpImageWidget { Q_OBJECT public: explicit AvatarWidget( QWidget* parent = 0 ); void setUser( const lastfm::User& user ); private: void paintEvent( QPaintEvent *paintEvent ); private: lastfm::User m_user; }; #endif // AVATARWIDGET_H ================================================ FILE: lib/unicorn/widgets/BannerWidget.cpp ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "BannerWidget.h" #include <QStackedLayout> #include <QDesktopServices> #include <QMoveEvent> #include <QDebug> #include <QCoreApplication> #include "lib/unicorn/DesktopServices.h" BannerWidget::BannerWidget( const QString& pText, QWidget* parent ) :QFrame( parent ), m_childWidget( 0 ) { m_layout = new QStackedLayout( this ); setLayout( m_layout ); m_layout->setStackingMode( QStackedLayout::StackAll ); m_layout->addWidget( m_banner = new BannerWidgetPrivate(pText) ); connect( m_banner, SIGNAL( clicked() ), this, SLOT( onClick() ) ); setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); } void BannerWidget::setWidget( QWidget* w ) { //Remove any existing childWidget if( m_layout->count() > 1 ) { m_childWidget->removeEventFilter( this ); m_layout->removeWidget( m_childWidget ); } m_childWidget = w; m_childWidget->installEventFilter( this ); m_layout->insertWidget( 0, m_childWidget ); m_layout->setCurrentWidget( m_banner ); } void BannerWidget::setHref( const QUrl& url ) { #ifdef Q_OS_MAC //On OSX Percent encoding seems to get applied to the url again. m_href = QUrl::fromPercentEncoding( url.toString().toUtf8() ); #else m_href = url.toString(); #endif if( url.isValid()) setCursor( Qt::PointingHandCursor ); else unsetCursor(); } void BannerWidget::onClick() { unicorn::DesktopServices::openUrl( m_href ); } QSize BannerWidget::sizeHint() const { if( m_childWidget ) return m_childWidget->sizeHint(); return QWidget::sizeHint(); } void BannerWidget::mousePressEvent( QMouseEvent* e ) { QCoreApplication::sendEvent( m_layout, e ); } bool BannerWidget::eventFilter( QObject* o, QEvent* e ) { QWidget* w = qobject_cast<QWidget*>(o); if( !w ) return false; if( e->type() == QEvent::Resize ) resize( static_cast<QResizeEvent*>(e)->size() ); return false; } void BannerWidget::setBannerVisible( bool visible ) { m_banner->setVisible( visible ); } bool BannerWidget::bannerVisible() const { return m_banner->isVisible(); } BannerWidgetPrivate::BannerWidgetPrivate( const QString& pText, QWidget* parent ) :QAbstractButton(parent) { setText( QString( " " ) + pText + " " ); } void BannerWidgetPrivate::paintEvent( QPaintEvent* /*e*/ ) { QPainter painter( this ); painter.setRenderHint( QPainter::TextAntialiasing ); painter.setRenderHint( QPainter::Antialiasing ); QRect bgRect = m_textRect.adjusted( -20, 0, 20, 0 ); painter.setWorldMatrix( m_transformMatrix ); painter.fillRect( bgRect, palette().brush( QPalette::Window )); style()->drawItemText( &painter, m_textRect.translated( 0, -1 ), Qt::AlignCenter, palette(), true, text() ); } void BannerWidgetPrivate::resizeEvent( QResizeEvent* event ) { clearMask(); QFont f = font(); m_textRect = QFontMetrics( f ).boundingRect( text() ); m_textRect.adjust( 0, 0, 0, 5 ); m_transformMatrix.reset(); //Tiny optimization and means math.h doesn't need to be included //and saves some runtime ops. I shouldn't imagine sin(45) is likely to change anytime soon! const float sin45 = 0.707106781186548f; m_transformMatrix.translate( event->size().width() - ((sin45 * m_textRect.width()) + 6 ), (sin45 * m_textRect.height()) - 6 ); m_transformMatrix.rotate( 45 ); QRegion mask = m_transformMatrix.map( QRegion( m_textRect.adjusted( -20, 0, 20, 0 ) ) ); setMask( mask ); } void BannerWidgetPrivate::mousePressEvent( QMouseEvent* e ) { if( !mask().contains( e->pos() ) ) { e->ignore(); return; } e->accept(); return QAbstractButton::mousePressEvent( e ); } void BannerWidgetPrivate::mouseReleaseEvent( QMouseEvent* e ) { if( !mask().contains( e->pos() ) ) { e->ignore(); return; } e->accept(); return QAbstractButton::mouseReleaseEvent( e ); } ================================================ FILE: lib/unicorn/widgets/BannerWidget.h ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef BANNER_WIDGET_H #define BANNER_WIDGET_H #include <QFrame> #include <QMatrix> #include <QRect> #include <QUrl> #include <QStyle> #include <QResizeEvent> #include <QPainter> #include <QAbstractButton> #include "lib/DllExportMacro.h" #include <QDebug> class UNICORN_DLLEXPORT BannerWidget : public QFrame { Q_OBJECT public: BannerWidget( const QString& text, QWidget* parent = 0 ); bool bannerVisible() const; void setWidget( QWidget* w ); QSize sizeHint() const; public slots: void setHref( const QUrl& url ); void setBannerVisible( bool visible = true ); protected: void mousePressEvent( QMouseEvent* e ); bool eventFilter( QObject*, QEvent* ); private slots: void onClick(); private: class QStackedLayout* m_layout; class BannerWidgetPrivate* m_banner; bool m_bannerVisible; QString m_href; QWidget* m_childWidget; }; class BannerWidgetPrivate : public QAbstractButton { Q_OBJECT public: BannerWidgetPrivate( const QString& pText, QWidget* parent = 0 ); private: void paintEvent( QPaintEvent* /*e*/ ); void resizeEvent( QResizeEvent* event ); void mousePressEvent( QMouseEvent* e ); void mouseReleaseEvent( QMouseEvent* e ); private: QMatrix m_transformMatrix; QRect m_textRect; }; #endif //BANNER_WIDGET_H ================================================ FILE: lib/unicorn/widgets/DataBox.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QHBoxLayout> #include <QLabel> #include "lib/DllExportMacro.h" class UNICORN_DLLEXPORT DataBox : public QFrame { Q_OBJECT public: DataBox( const QString& title, QWidget* child, QWidget* p = 0 ) :QFrame( p ) { QFrame* w = new QFrame(); w->setObjectName( "header" ); new QHBoxLayout( w ); w->layout()->setContentsMargins( 0, 0, 0, 0 ); w->layout()->setSpacing( 0 ); QLabel* icon; w->layout()->addWidget( icon = new QLabel()); icon->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); icon->setObjectName( "icon" ); w->layout()->addWidget( new QLabel( title )); new QVBoxLayout( this ); layout()->addWidget( w ); QWidget* cw = new QFrame(); cw->setObjectName( "contents" ); QVBoxLayout* v = new QVBoxLayout( cw ); v->setContentsMargins( 0, 0, 0, 0 ); v->setSpacing( 0 ); v->addWidget( child ); v->addStretch( 1 ); layout()->addWidget( cw ); layout()->setContentsMargins( 0, 0, 0, 0 ); layout()->setSpacing( 0 ); } }; ================================================ FILE: lib/unicorn/widgets/DataListWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QDebug> #include <QLayout> #include <QScrollArea> #include <QUrl> #include <QDesktopServices> #include <QMimeData> #include <QUrl> #include <QLabel> #include <QDrag> #include <QMouseEvent> #include <QApplication> #include "DataListWidget.h" #include "lib/unicorn/layouts/FlowLayout.h" #include "lib/unicorn/UnicornApplication.h" class DataItem : public QLabel { public: explicit DataItem( const QString& text, const QUrl& url ) :m_url(url), m_text(text) { setOpenExternalLinks( true ); setTextInteractionFlags( Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse ); QString ss = qobject_cast<unicorn::Application*>(qApp)->loadedStyleSheet(); setText( "<style>" + ss + "</style><a href=\"" + url.toString() + "\">" + text + "</a>" ); } QUrl url() const{ return m_url; } protected: void mouseReleaseEvent( QMouseEvent* event ) { QLabel::mouseReleaseEvent( event ); if ((event->pos() - m_dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) return; } void mousePressEvent(QMouseEvent *event) { QLabel::mousePressEvent( event ); if (event->button() == Qt::LeftButton) m_dragStartPosition = event->pos(); } void mouseMoveEvent(QMouseEvent *event) { QLabel::mouseMoveEvent( event ); if (!(event->buttons() & Qt::LeftButton)) return; if ((event->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance()) return; QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; mimeData->setText( m_text ); QList<QUrl> urls; urls.append( url() ); mimeData->setUrls( urls ); drag->setMimeData(mimeData); drag->exec(Qt::CopyAction); } QUrl m_url; QString m_text; QPoint m_dragStartPosition; }; DataListWidget::DataListWidget(QWidget* parent) :QFrame(parent) { new FlowLayout( this, 0, 0, 0 ); } void DataListWidget::clear() { foreach( QObject* c, findChildren<QWidget*>()) c->deleteLater(); } void DataListWidget::addItem( const QString& text, const QUrl& url ) { layout()->addWidget( new DataItem( text, url )); } ================================================ FILE: lib/unicorn/widgets/DataListWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef DATA_LIST_WIDGET_H_ #define DATA_LIST_WIDGET_H_ #include <QFrame> #include "lib/DllExportMacro.h" class QUrl; class UNICORN_DLLEXPORT DataListWidget : public QFrame { Q_OBJECT public: explicit DataListWidget(QWidget* parent = 0); void clear(); void addItem( const QString&, const QUrl& ); }; #endif //DATA_LIST_WIDGET_H_ ================================================ FILE: lib/unicorn/widgets/FriendsPicker.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "FriendsPicker.h" #include <QLineEdit> #include <lastfm/User.h> #include <QDebug> #include <QDialogButtonBox> #include <QListWidget> #include <QVBoxLayout> FriendsPicker::FriendsPicker( const User& user ) { QVBoxLayout* v = new QVBoxLayout( this ); QLineEdit* lineEdit = new QLineEdit; #if QT_VERSION >= 0x040700 // The placeholder property was introduced in Qt 4.7 lineEdit->setPlaceholderText( tr("Search your friends") ); #endif v->addWidget( lineEdit ); v->addWidget( ui.list = new QListWidget ); v->addWidget( ui.buttons = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel ) ); setWindowTitle( tr("Browse Friends") ); connect( user.getFriends(), SIGNAL(finished()), SLOT(onGetFriendsReturn()) ); connect( ui.buttons, SIGNAL(accepted()), SLOT(accept()) ); connect( ui.buttons, SIGNAL(rejected()), SLOT(reject()) ); } void FriendsPicker::onGetFriendsReturn() { foreach (User u, User::list( (QNetworkReply*)sender() ).users()) ui.list->addItem( u ); } QList<User> FriendsPicker::selection() const { return QList<User>(); } ================================================ FILE: lib/unicorn/widgets/FriendsPicker.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef FRIENDS_PICKER_H #define FRIENDS_PICKER_H #include <QDialog> #include <lastfm/User.h> #include "lib/DllExportMacro.h" class UNICORN_DLLEXPORT FriendsPicker : public QDialog { Q_OBJECT struct { class QDialogButtonBox* buttons; class QListWidget* list; } ui; public: FriendsPicker( const User& = User() ); QList<User> selection() const; private slots: void onGetFriendsReturn(); }; #endif ================================================ FILE: lib/unicorn/widgets/GhostWidget.cpp ================================================ /* Copyright 2010-2011 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "GhostWidget.h" #include <QResizeEvent> #include <QDebug> GhostWidget::GhostWidget( QWidget* parent ) :QWidget( parent ) { } void GhostWidget::setOrigin( QWidget* origin ) { setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); setFixedSize( origin->sizeHint() ); setVisible( origin->isVisible() ); origin->installEventFilter( this ); } bool GhostWidget::eventFilter( QObject* /*obj*/, QEvent* event ) { switch ( event->type() ) { case QEvent::Resize: { QResizeEvent* re = static_cast<QResizeEvent*>( event ); setFixedSize( re->size() ); } break; case QEvent::Show: show(); break; case QEvent::Hide: hide(); break; default: break; } return false; } ================================================ FILE: lib/unicorn/widgets/GhostWidget.h ================================================ /* Copyright 2010-2011 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef GHOST_WIDGET_H #define GHOST_WIDGET_H #include <QWidget> #include "lib/DllExportMacro.h" class UNICORN_DLLEXPORT GhostWidget : public QWidget { public: GhostWidget(QWidget* parent = 0 ); void setOrigin( QWidget* origin ); protected: bool eventFilter( QObject* obj, QEvent* event ); }; #endif //GHOST_WIDGET_H ================================================ FILE: lib/unicorn/widgets/HttpImageWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole and Micahel Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QStyle> #include "HttpImageWidget.h" #include "lib/unicorn/DesktopServices.h" HttpImageWidget::HttpImageWidget( QWidget* parent ) :QLabel( parent ), m_mouseDown( false ) { setAttribute( Qt::WA_LayoutUsesWidgetRect ); setAttribute( Qt::WA_MacNoClickThrough ); } void HttpImageWidget::loadUrl( const QUrl& url, ScaleType scale ) { m_scale = scale; connect( lastfm::nam()->get(QNetworkRequest(url)), SIGNAL(finished()), SLOT(onUrlLoaded())); } void HttpImageWidget::setHref( const QUrl& url ) { #ifdef Q_OS_MAC //On OSX Percent encoding seems to get applied to the url again. m_href = QUrl::fromPercentEncoding( url.toString().toUtf8() ); #else m_href = url; #endif setToolTip( m_href.toString() ); unsetCursor(); disconnect( this, SIGNAL( clicked()), this, SLOT(onClick())); if( m_href.isValid()) { setCursor( Qt::PointingHandCursor ); connect( this, SIGNAL(clicked()), SLOT(onClick())); } } void HttpImageWidget::mousePressEvent( QMouseEvent* /*event*/ ) { m_mouseDown = true; } void HttpImageWidget::mouseReleaseEvent( QMouseEvent* event ) { if( m_mouseDown && contentsRect().contains( event->pos() )) emit clicked(); m_mouseDown = false; } void HttpImageWidget::onClick() { unicorn::DesktopServices::openUrl( m_href ); } void HttpImageWidget::onUrlLoaded() { QNetworkReply* reply = static_cast<QNetworkReply*>(sender()); reply->deleteLater(); if ( reply->error() == QNetworkReply::NoError ) { QPixmap px; if ( px.loadFromData( reply->readAll() ) ) { switch ( m_scale ) { case ScaleAuto: // Decide which way to scale based on the ratio of height to width // of the image and the area that the image is going to be drawn to if ( (px.height() * 1000) / px.width() > (height() * 1000) / width() ) px = px.scaledToWidth( contentsRect().width(), Qt::SmoothTransformation ); else px = px.scaledToHeight( contentsRect().height(), Qt::SmoothTransformation ); break; case ScaleNone: break; case ScaleWidth: px = px.scaledToWidth( contentsRect().width(), Qt::SmoothTransformation ); break; case ScaleHeight: px = px.scaledToHeight( contentsRect().height(), Qt::SmoothTransformation ); break; } setPixmap( px ); } } emit loaded(); } ================================================ FILE: lib/unicorn/widgets/HttpImageWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole and Micahel Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef HTTP_IMAGE_WIDGET_H_ #define HTTP_IMAGE_WIDGET_H_ #include <QLabel> #include <QUrl> #include <QDesktopServices> #include <QPainter> #include <QMouseEvent> #include "lib/DllExportMacro.h" #include <lastfm/ws.h> class UNICORN_DLLEXPORT HttpImageWidget : public QLabel { Q_OBJECT public: enum ScaleType { ScaleNone, ScaleAuto, ScaleWidth, ScaleHeight }; HttpImageWidget( QWidget* parent = 0 ); public slots: void loadUrl( const QUrl& url, ScaleType scale = ScaleAuto ); void setHref( const QUrl& url ); protected: void mousePressEvent( QMouseEvent* event ); void mouseReleaseEvent( QMouseEvent* event ); private slots: void onClick(); void onUrlLoaded(); signals: void clicked(); void loaded(); private: bool m_mouseDown; ScaleType m_scale; QUrl m_href; }; #endif ================================================ FILE: lib/unicorn/widgets/ImageButton.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "ImageButton.h" #include <QPainter> #include <QPaintEvent> #include <QLayout> #include <QAction> #include <QPixmap> #include <QIcon> #include <QString> #include <QDebug> ImageButton::ImageButton( const QPixmap& rest, QWidget* parent ) : ActionButton( parent ) { init( rest ); } ImageButton::ImageButton( const QString& path, QWidget* parent ) : ActionButton( parent ) { init( QPixmap( path ) ); } void ImageButton::init( const QPixmap& p ) { setPixmap( p ); m_sizeHint = p.size(); } void ImageButton::paintEvent( QPaintEvent* event ) { QPainter p( this ); p.setClipRect( event->rect() ); QIcon::Mode mode = isDown() ? QIcon::Active : isEnabled() ? QIcon::Normal : QIcon::Disabled; QIcon::State state = isChecked() ? QIcon::On : QIcon::Off; QRect iconRect = rect(); if( m_iconOffsets.contains( mode ) ) { iconRect.setLeft( iconRect.left() + m_iconOffsets[ mode ].x() ); iconRect.setBottom( iconRect.bottom() + m_iconOffsets[ mode ].y() - 3 ); } icon().paint( &p, iconRect, Qt::AlignCenter, mode, state ); } void ImageButton::setPixmap( const QPixmap& p, const QIcon::State state, const QIcon::Mode mode ) { QIcon i = icon(); i.addPixmap( p, mode, state ); setIcon( i ); } void ImageButton::moveIcon( int x, int y, QIcon::Mode m ) { m_iconOffsets.insert( m, QPoint( x, y ) ); } ================================================ FILE: lib/unicorn/widgets/ImageButton.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef IMAGE_BUTTON_H #define IMAGE_BUTTON_H #include "ActionButton.h" #include <QIcon> #include <QMap> #include "lib/DllExportMacro.h" class UNICORN_DLLEXPORT ImageButton : public ActionButton { void init( const QPixmap& ); public: /** this pixmap becomes the rest state pixmap and defines the size of the eventual widget */ explicit ImageButton( const QPixmap& pixmap, QWidget* parent = 0 ); explicit ImageButton( const QString& pixmap_path, QWidget* parent = 0 ); void setPixmap( const QPixmap&, const QIcon::State = QIcon::Off, QIcon::Mode = QIcon::Normal ); void moveIcon( int x, int y, QIcon::Mode = QIcon::Normal ); virtual QSize sizeHint() const { return m_sizeHint; } protected: virtual void paintEvent( QPaintEvent* event ); private: QSize m_sizeHint; QMap< QIcon::Mode, QPoint > m_iconOffsets; }; #endif //IMAGE_BUTTON_H ================================================ FILE: lib/unicorn/widgets/ItemSelectorWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QDebug> #include <QLabel> #include <QComboBox> #include <QCompleter> #include <QAbstractItemView> #include <QTimer> #include "lib/unicorn/widgets/SearchBox.h" #include "lib/unicorn/layouts/FlowLayout.h" #include "ItemSelectorWidget.h" #include <lastfm/User.h> ItemSelectorWidget::ItemSelectorWidget( QWidget* parent ) :QFrame(parent), m_clearText( false ) { QLayout* layout = new FlowLayout( this, 0, 0, 0 ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 0 ); } void ItemSelectorWidget::setType( Type type ) { if (type == User) layout()->addItem( new QWidgetItem( ui.searchBox = new UserSearch( this ) ) ); else layout()->addItem( new QWidgetItem( ui.searchBox = new TagSearch( this ) ) ); ui.searchBox->setFrame( false ); ui.searchBox->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ); setFocusPolicy( Qt::StrongFocus ); setFocusProxy( ui.searchBox ); connect( ui.searchBox, SIGNAL(editingFinished()), SLOT(onItemSelected()) ); connect( ui.searchBox->completer()->popup(), SIGNAL(clicked(QModelIndex)), SLOT(onItemSelected())); connect( ui.searchBox, SIGNAL(textChanged(QString)), SLOT(onTextChanged(QString))); connect( ui.searchBox, SIGNAL(commaPressed()), SLOT(onItemSelected()) ); connect( ui.searchBox, SIGNAL(deletePressed()), SLOT(onDeletePressed()) ); } void ItemSelectorWidget::onTextChanged( const QString& /*text*/ ) { //QFontMetrics fm(font()); //int completerWidth = ui.userSearch->completer()->popup()->sizeHint().width(); //int textWidth = fm.width( text + " " ); //ui.userSearch->setFixedWidth( completerWidth > textWidth ? completerWidth : textWidth ); } void ItemSelectorWidget::onItemSelected() { addItem( ui.searchBox->text() ); } void ItemSelectorWidget::onItemDeleted( QLabel* item ) { m_items.removeAt( m_items.indexOf( item ) ); layout()->removeWidget( item ); item->deleteLater(); ui.searchBox->setFocus( Qt::OtherFocusReason ); emit changed(); } void ItemSelectorWidget::onDeletePressed() { if ( m_items.count() > 0 ) { QLabel* lastLabel = m_items.takeLast(); int cursorPos = lastLabel->text().length(); ui.searchBox->setText( lastLabel->text() + ui.searchBox->text() ); ui.searchBox->setCursorPosition( cursorPos ); onItemDeleted( lastLabel ); } } void ItemSelectorWidget::onCompleterActivated( const QString& text ) { addItem( text ); } void ItemSelectorWidget::addItem( const QString& text ) { if ( !ui.searchBox->text().isEmpty() // don't add empty recipients && !itemsContain( text ) // don't add duplicates && m_items.count() < 10 ) // limit to 10 { QLabel* item = new QLabel( text, this ); m_items.append( item ); dynamic_cast<FlowLayout*>(layout())->insertWidget( layout()->count() - 1 , item ); // clear the line edit a little bit later because the QCompleter // will set the text to be what was selected after this QTimer::singleShot(1, ui.searchBox, SLOT(clear())); emit changed(); } } bool ItemSelectorWidget::itemsContain( const QString& text ) { foreach ( const QLabel* item, m_items ) { if ( item->text() == text ) return true; } return false; } QStringList ItemSelectorWidget::items() const { QStringList items; foreach (const QLabel* item, m_items) { items << item->text(); } return items; } ================================================ FILE: lib/unicorn/widgets/ItemSelectorWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef ITEM_SELECTOR_WIDGET_H #define ITEM_SELECTOR_WIDGET_H #include <QFrame> #include "lib/DllExportMacro.h" class UNICORN_DLLEXPORT ItemSelectorWidget : public QFrame { Q_OBJECT private: struct { class SearchBox* searchBox; } ui; public: enum Type { Tag, User }; explicit ItemSelectorWidget( QWidget* parent = 0 ); void setType( Type type ); QStringList items() const; signals: void changed(); private slots: void onItemSelected(); void onDeletePressed(); void onItemDeleted( class QLabel* recipient ); void onCompleterActivated( const QString& text ); void onTextChanged( const QString& text ); private: void addItem( const QString& text ); bool itemsContain( const QString& text ); private: QList<class QLabel*> m_items; bool m_clearText; }; #endif // ITEM_SELECTOR_WIDGET_H ================================================ FILE: lib/unicorn/widgets/Label.cpp ================================================ /* Copyright 2011-2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QEvent> #include <QResizeEvent> #include <QPainter> #include <QToolTip> #include <QUrl> #include <QGraphicsDropShadowEffect> #include <QDateTime> #include <QTimer> #include "../DesktopServices.h" #include "Label.h" unicorn::Label::Label( QWidget* parent ) :QLabel( parent ), m_linkColor( QRgb( 0x333333 ) ) { setAttribute( Qt::WA_LayoutUsesWidgetRect ); setAttribute( Qt::WA_MacNoClickThrough ); setOpenExternalLinks( false ); connect( this, SIGNAL(linkHovered(QString)), SLOT(onHovered(QString))); connect( this, SIGNAL(linkActivated(QString)), SLOT(onActivated(QString))); } unicorn::Label::Label( const QString& text, QWidget* parent ) :QLabel( parent ) { setText( text ); setAttribute( Qt::WA_LayoutUsesWidgetRect ); setAttribute( Qt::WA_MacNoClickThrough ); setOpenExternalLinks( false ); connect( this, SIGNAL(linkHovered(QString)), SLOT(onHovered(QString))); connect( this, SIGNAL(linkActivated(QString)), SLOT(onActivated(QString))); } void unicorn::Label::onActivated( const QString& url ) { unicorn::DesktopServices::openUrl( url ); } void unicorn::Label::onHovered( const QString& url ) { QUrl displayUrl( url ); QToolTip::showText( cursor().pos(), displayUrl.toString(), this, QRect() ); } QString unicorn::Label::boldLinkStyle( const QString& text, QColor linkColor ) { return QString( "<html><head><style type=text/css>" "a:link {color:%1; font-weight: bold; text-decoration:none;}" "a:hover {color:%1; font-weight: bold; text-decoration:none;}" "</style></head><body>%2</body></html>" ).arg( linkColor.name(), text ); } QString unicorn::Label::boldLinkStyle( const QString& text ) { return boldLinkStyle( text, m_linkColor ); } QString unicorn::Label::text() const { return m_text; } void unicorn::Label::setText( const QString& text ) { m_text = text; if ( textFormat() == Qt::RichText ) QLabel::setText( boldLinkStyle( m_text ) ); else QLabel::setText( "" ); update(); } void unicorn::Label::setLinkColor( QColor linkColor ) { m_linkColor = linkColor; } QString unicorn::Label::anchor( const QString& url, const QString& text ) { QString actualText = text; actualText.replace( QRegExp( "&" ), "&" ); // This _must_ come first actualText.replace( QRegExp( "<" ), "<" ); actualText.replace( QRegExp( ">" ), ">" ); actualText.replace( QRegExp( "\"" ), """ ); return QString( "<a href=\"%1\">%2</a>" ).arg( url, actualText ); } void unicorn::Label::prettyTime( Label& timestampLabel, const QDateTime& timestamp, QTimer* callback ) { QDateTime now = QDateTime::currentDateTime(); // Full time in the tool tip timestampLabel.setToolTip( timestamp.toString( Qt::DefaultLocaleLongDate ) ); int secondsAgo = timestamp.secsTo( now ); if ( secondsAgo < (60 * 60) ) { // Less than an hour ago int minutesAgo = ( timestamp.secsTo( now ) / 60 ); timestampLabel.setText( tr( "%n minute(s) ago", "", minutesAgo ) ); if ( callback ) callback->start( now.secsTo( timestamp.addSecs(((minutesAgo + 1 ) * 60 ) + 1 ) ) * 1000 ); } else if ( secondsAgo < (60 * 60 * 6) || now.date() == timestamp.date() ) { // Less than 6 hours ago or on the same date int hoursAgo = ( timestamp.secsTo( now ) / (60 * 60) ); timestampLabel.setText( tr( "%n hour(s) ago", "", hoursAgo ) ); if ( callback ) callback->start( now.secsTo( timestamp.addSecs( ( (hoursAgo + 1) * 60 * 60 ) + 1 ) ) * 1000 ); } else if ( secondsAgo < (60 * 60 * 24 * 365) ) { // less than a year ago timestampLabel.setText( timestamp.toString( Qt::DefaultLocaleShortDate ) ); // We don't need to set the timer because this date will never change (well, it might in a year's time) } else { timestampLabel.setText( timestamp.toString( Qt::DefaultLocaleLongDate ) ); // We don't need to set the timer because this date will never change } if ( secondsAgo < 0 ) timestampLabel.setText( tr( "Time is broken" ) ); // in the future! } QString unicorn::Label::price( const QString& price, const QString& currency ) { QString returnPrice; if ( currency.compare( "MXN", Qt::CaseInsensitive ) == 0 || currency.compare( "USD", Qt::CaseInsensitive ) == 0 || currency.compare( "AUD", Qt::CaseInsensitive ) == 0 || currency.compare( "NZD", Qt::CaseInsensitive ) == 0 ) returnPrice = QString::fromUtf8( "$%1" ).arg( price ); else if ( currency.compare( "CAD", Qt::CaseInsensitive ) == 0 ) returnPrice = QString::fromUtf8( "CAD$%1" ).arg( price ); else if ( currency.compare( "DKK", Qt::CaseInsensitive ) == 0 ) returnPrice = QString::fromUtf8( "DKK%1" ).arg( price ); else if ( currency.compare( "CHF", Qt::CaseInsensitive ) == 0 ) returnPrice = QString::fromUtf8( "CHF%1" ).arg( price ); else if ( currency.compare( "NOK", Qt::CaseInsensitive ) == 0 ) returnPrice = QString::fromUtf8( "NOK%1" ).arg( price ); else if ( currency.compare( "SEK", Qt::CaseInsensitive ) == 0 ) returnPrice = QString::fromUtf8( "SEK%1" ).arg( price ); else if ( currency.compare( "GBP", Qt::CaseInsensitive ) == 0 ) returnPrice = QString::fromUtf8( "£%1" ).arg( price ); else if ( currency.compare( "EUR", Qt::CaseInsensitive ) == 0 ) returnPrice = QString::fromUtf8( "€%1" ).arg( price ); else if ( currency.compare( "JPY", Qt::CaseInsensitive ) == 0 ) returnPrice = QString::fromUtf8( "¥%1" ).arg( price ); else returnPrice = QString( "%1 %2" ).arg( price, currency ); return returnPrice; } QSize unicorn::Label::sizeHint() const { QSize sizeHint = QLabel::sizeHint(); if ( textFormat() != Qt::RichText ) sizeHint.setWidth( qMin ( sizeHint.width(), fontMetrics().width( m_text ) + 1 ) ); return sizeHint; } void unicorn::Label::paintEvent( QPaintEvent* event ) { if ( textFormat() == Qt::RichText ) QLabel::paintEvent( event ); else { QFrame::paintEvent(event); QPainter p(this); p.drawText( rect(), fontMetrics().elidedText( m_text, Qt::ElideRight, contentsRect().width() ) ); } } ================================================ FILE: lib/unicorn/widgets/Label.h ================================================ /* Copyright 2011-2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef LABEL_H #define LABEL_H #include <QLabel> #include "lib/DllExportMacro.h" namespace unicorn { class UNICORN_DLLEXPORT Label : public QLabel { Q_OBJECT public: explicit Label( QWidget* parent = 0 ); explicit Label( const QString& text, QWidget* parent = 0 ); QString text() const; void setText( const QString& text ); void setLinkColor( QColor linkColor ); static QString anchor( const QString& url, const QString& text ); static QString boldLinkStyle( const QString& text, QColor linkColor ); // Gives you a pretty time string and will call your slot when it's time to change it again static void prettyTime( Label& timestampLabel, const class QDateTime& timestamp, QTimer* callback = 0 ); static QString price( const QString& price, const QString& currency ); private: void paintEvent( QPaintEvent* event ); QSize sizeHint() const; QString boldLinkStyle( const QString& text ); private slots: void onHovered( const QString& url ); void onActivated( const QString& url ); private: QString m_text; QColor m_linkColor; }; } #endif // LABEL_H ================================================ FILE: lib/unicorn/widgets/LfmListViewWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QCoreApplication> #include <QDomDocument> #include <QFile> #include "LfmListViewWidget.h" #include <lastfm/User.h> #include <lastfm/Artist.h> #include <lastfm/Track.h> #include <iostream> LfmDelegate::LfmDelegate( QAbstractItemView* parent ):QStyledItemDelegate(parent) { m_viewSize = parent->size(); parent->installEventFilter( this ); } void LfmDelegate::paint( QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index ) const { QIcon icon; if( index.data(Qt::DecorationRole).type() == QVariant::Icon ) { icon = index.data(Qt::DecorationRole).value<QIcon>(); if ( icon.isNull() ) //icon = QIcon( m_defaultImage ); icon = QIcon( ":/default_user.png" ); QRect iconRect = opt.rect.translated( 3, 3 ); iconRect.setSize( QSize( 34, 34 )); icon.paint( p, iconRect ); QSize iconSize = icon.actualSize( iconRect.size()); if( iconSize.isEmpty()) iconSize = QSize( 34, 34 ); iconRect.translate( ( iconRect.width() - iconSize.width()) / 2.0f, ( iconRect.height() - iconSize.height()) /2.0f ); iconRect.setSize( iconSize ); p->drawRect( iconRect ); } QFontMetrics fm( p->font() ); QString elidedText = fm.elidedText( index.data().toString(), Qt::ElideRight, opt.rect.width() - 50 ); p->drawText( opt.rect.adjusted( 46, 3, -5, -5 ), elidedText ); } QSize LfmDelegate::sizeHint( const QStyleOptionViewItem& opt, const QModelIndex& /*index*/ ) const { QFontMetrics fm( opt.font ); //int textWidth = fm.width( index.data().toString()); int spacing = qobject_cast<QListView*>(parent())->spacing(); return QSize( (m_viewSize.width() / 2)-(spacing*2), 40 ); } bool LfmDelegate::eventFilter( QObject* obj, QEvent* event ) { if( event->type() == QEvent::Resize ) { QWidget* view = qobject_cast< QWidget* >(obj ); if( !view ) return false; m_viewSize = view->size(); emit sizeHintChanged( QModelIndex() ); } return false; } QPixmap LfmDelegate::defaultImage() const { return m_defaultImage; } void LfmDelegate::setDefaultImage( QPixmap defaultImage ) { m_defaultImage = defaultImage; } void LfmItem::onImageLoaded() { QNetworkReply* reply = static_cast<QNetworkReply*>(sender()); reply->deleteLater(); QPixmap px; px.loadFromData( reply->readAll() ); m_icon = QIcon( px ); emit updated(); } void LfmItem::loadImage( const QUrl& url ) { QString imageUrl = url.toString(); QNetworkReply* reply = lastfm::nam()->get(QNetworkRequest( url )); connect( reply, SIGNAL( finished()), this, SLOT( onImageLoaded())); } void LfmListModel::addUser( const User& a_user ) { User* user = new User; *user = a_user; LfmItem* item = new LfmItem( user ); item->loadImage( user->imageUrl(User::SmallImage, true )); beginInsertRows( QModelIndex(), rowCount(), rowCount()); m_items << item; connect( item, SIGNAL(updated()), SLOT( itemUpdated())); endInsertRows(); } void LfmListModel::addArtist( const Artist& a_artist ) { Artist* artist = new Artist; *artist = a_artist; LfmItem* item = new LfmItem( artist ); item->loadImage( artist->imageUrl( Artist::SmallImage, true )); beginInsertRows( QModelIndex(), rowCount(), rowCount()); m_items << item; connect( item, SIGNAL(updated()), SLOT( itemUpdated())); endInsertRows(); } void LfmListModel::itemUpdated() { LfmItem* item = static_cast<LfmItem*>(sender()); int index = m_items.indexOf( item ); if ( index >= 0 ) emit dataChanged( createIndex( index, 0), createIndex( index, 0)); } QVariant LfmListModel::data( const QModelIndex & index, int role ) const { if( index.row() > m_items.count()) return QVariant(); const LfmItem& item = *(m_items[index.row()]); switch( role ) { case Qt::DisplayRole: return item.m_type->toString(); case Qt::DecorationRole: return item.m_icon; case Qt::ToolTipRole: return item.m_type->toString(); case CursorRole: return Qt::PointingHandCursor; case WwwRole: return item.m_type->www(); } return QVariant(); } LfmListView::LfmListView(QWidget *parent): QListView(parent), m_lastRow(-1) { setMouseTracking(true); setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); } #include <QMouseEvent> void LfmListView::mouseMoveEvent(QMouseEvent *event) { QAbstractItemModel *m(model()); if (m) { QModelIndex index = indexAt(event->pos()); if (index.isValid()) { // When the index is valid, compare it to the last row. // Only do something when the the mouse has moved to a new row. if (index.row() != m_lastRow) { m_lastRow = index.row(); // Request the data for the CursorRole. QVariant data = m->data(index, LfmListModel::CursorRole ); Qt::CursorShape shape = Qt::ArrowCursor; if (!data.isNull()) shape = static_cast<Qt::CursorShape>(data.toInt()); setCursor(shape); } } else { if (m_lastRow != -1) setCursor(Qt::ArrowCursor); m_lastRow = -1; } } QListView::mouseMoveEvent(event); } ================================================ FILE: lib/unicorn/widgets/LfmListViewWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef LFM_LIST_VIEW_WIDGET_H #define LFM_LIST_VIEW_WIDGET_H #include <lastfm/AbstractType.h> #include <QStyledItemDelegate> #include <QPainter> #include <QUrl> #include <QAbstractItemView> #include <QEvent> #include "lib/DllExportMacro.h" #include <QListView> #include <QFontMetrics> #include <QDebug> namespace lastfm { class AbstractType; }; using lastfm::AbstractType; class UNICORN_DLLEXPORT LfmDelegate :public QStyledItemDelegate { Q_OBJECT Q_PROPERTY(QPixmap defaultImage READ defaultImage WRITE setDefaultImage); public: LfmDelegate( QAbstractItemView* parent ); virtual void paint( QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index ) const; virtual QSize sizeHint( const QStyleOptionViewItem& opt, const QModelIndex& index ) const; bool eventFilter( QObject* obj, QEvent* event ); QPixmap defaultImage() const; void setDefaultImage( QPixmap defaultImage ); private: QSize m_viewSize; QPixmap m_defaultImage; }; class LfmItem : public QObject { Q_OBJECT public: LfmItem(AbstractType* type, QObject* parent = 0) :QObject( parent ), m_type( type ) {;} ~LfmItem() { delete m_type; } bool operator==( const LfmItem& that ) const { return this->m_type->toString() == that.m_type->toString(); } void loadImage( const QUrl& url ); QIcon m_icon; AbstractType* m_type; signals: void updated(); protected slots: void onImageLoaded(); }; namespace lastfm { class User; class Artist; class Track; } using lastfm::User; using lastfm::Artist; using lastfm::Track; class UNICORN_DLLEXPORT LfmListModel : public QAbstractListModel { Q_OBJECT public: enum DataRole { WwwRole = Qt::UserRole, CursorRole}; LfmListModel( QObject* parent=0 ):QAbstractListModel( parent ){} void addUser( const User& ); void addArtist( const Artist& ); void addCachedTrack( const Track& ); void addScrobbledTrack( const Track& ); void read(QString path); void write(QString path) const; int rowCount ( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED(parent); return m_items.length(); } QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const; void clear() { if( m_items.isEmpty()) return; beginRemoveRows( QModelIndex(), 0, rowCount() -1 ); foreach( LfmItem* item, m_items ) { item->deleteLater(); m_items.removeAll( item ); } endRemoveRows(); } protected slots: void itemUpdated(); protected: QList<LfmItem*> m_items; }; #include <QListView> class UNICORN_DLLEXPORT LfmListView : public QListView { Q_OBJECT public: LfmListView(QWidget *parent = 0); protected: virtual void mouseMoveEvent(QMouseEvent *event); private: int m_lastRow; }; #endif //LFM_LIST_VIEW_WIDGET_H ================================================ FILE: lib/unicorn/widgets/MessageBar.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QtGui> #include <QLabel> #include "lib/unicorn/dialogs/ScrobbleConfirmationDialog.h" #include "lib/unicorn/widgets/Label.h" #include "lib/unicorn/DesktopServices.h" #include "MessageBar.h" MessageBar::MessageBar( QWidget* parent ) :QFrame( parent ) { QHBoxLayout* layout = new QHBoxLayout( this ); layout->addWidget( ui.icon = new QLabel() ); ui.icon->setObjectName( "icon" ); layout->addWidget( ui.message = new QLabel() ); ui.message->setObjectName( "message" ); layout->addStretch(); layout->addWidget( ui.close = new QPushButton() ); ui.close->setObjectName( "close" ); connect( ui.message, SIGNAL(linkActivated(QString)), SLOT(onLinkActivated(QString))); connect( ui.close, SIGNAL(clicked()), SLOT(onCloseClicked())); connect( qApp, SIGNAL(showMessage(QString,QString)), SLOT(show(QString,QString))); connect( qApp, SIGNAL(error(QString)), SLOT(show(QString))); hide(); } void MessageBar::addTracks( const QList<lastfm::Track>& tracks ) { m_tracks << tracks; } const QList<lastfm::Track>& MessageBar::tracks() const { return m_tracks; } void MessageBar::show( const QString& message, const QString& id, int timeout ) { if ( !m_timeout ) { m_timeout = new QTimer( this ); connect( m_timeout, SIGNAL(timeout()), SLOT(onCloseClicked()) ); } if ( timeout != -1 ) m_timeout->start( timeout * 1000 ); else m_timeout->stop(); setObjectName( id ); ui.message->setText( unicorn::Label::boldLinkStyle( message, Qt::black ) ); style()->polish( this ); style()->polish( ui.icon ); QWidget::show(); } void MessageBar::onCloseClicked() { m_tracks.clear(); hide(); } void MessageBar::onLinkActivated( const QString& link ) { if ( link == "tracks" ) { // always sort the tracks before displaying them qSort ( m_tracks.begin(), m_tracks.end() ); // Show a dialog with the tracks ScrobbleConfirmationDialog confirmDialog( m_tracks ); confirmDialog.setReadOnly(); confirmDialog.exec(); } else if ( link == "plugin" ) { #ifdef Q_OS_MAC if ( !m_installer ) m_installer = new unicorn::ITunesPluginInstaller( this ); m_installer->install(); #elif defined( Q_OS_WIN ) if ( !m_itw ) m_itw = new unicorn::ITunesPluginInfo( this ); m_itw->setVerbose( true ); m_itw->doInstall(); #endif } else { // this should be a url so open it in the browser unicorn::DesktopServices::openUrl( link ); } } ================================================ FILE: lib/unicorn/widgets/MessageBar.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef MESSAGE_BAR_H #define MESSAGE_BAR_H #include "lib/DllExportMacro.h" #include <lastfm/Track.h> #ifdef Q_OS_MAC #include "lib/unicorn/plugins/ITunesPluginInstaller.h" #elif defined( Q_OS_WIN ) #include "lib/unicorn/plugins/ITunesPluginInfo.h" #endif #include <QFrame> class UNICORN_DLLEXPORT MessageBar : public QFrame { Q_OBJECT private: struct { class QLabel* icon; class QLabel* message; class QPushButton* close; } ui; public: explicit MessageBar( QWidget* parent ); void addTracks( const QList<lastfm::Track>& tracks ); const QList<lastfm::Track>& tracks() const; public slots: void show( const QString& message, const QString& id = QString(), int timeout = -1 ); private slots: void onLinkActivated( const QString& link ); void onCloseClicked(); private: QList<lastfm::Track> m_tracks; QPointer<QTimer> m_timeout; #ifdef Q_OS_MAC QPointer<unicorn::ITunesPluginInstaller> m_installer; #elif defined( Q_OS_WIN ) QPointer<unicorn::ITunesPluginInfo> m_itw; #endif }; #endif ================================================ FILE: lib/unicorn/widgets/PlayableMimeData.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PLAYABLE_MIME_DATA #define PLAYABLE_MIME_DATA #include <QMimeData> #include <lastfm/Artist.h> #include <lastfm/Tag.h> #include <lastfm/User.h> #include "Seed.h" class PlayableMimeData : public QMimeData { Q_OBJECT public: static PlayableMimeData* createFromArtist( const Artist& a ) { PlayableMimeData* data = new PlayableMimeData(); data->setText( a ); data->setUrls( QList<QUrl>() << a.www() ); data->setData( "text/x-lfm-entity-type", "Artist" ); data->m_type = Seed::ArtistType; return data; } static PlayableMimeData* createFromTag( const Tag& t ) { PlayableMimeData* data = new PlayableMimeData(); data->setText( t ); data->setUrls( QList<QUrl>() << t.www() ); data->setData( "text/x-lfm-entity-type", "Tag" ); data->m_type = Seed::TagType; return data; } static PlayableMimeData* createFromUser( const User& u ) { PlayableMimeData* data = new PlayableMimeData(); data->setText( u ); data->setUrls( QList<QUrl>() << u.www() ); data->setData( "text/x-lfm-entity-type", "User" ); data->m_type = Seed::UserType; return data; } static PlayableMimeData* createFromPredefined( const QString& s ) { PlayableMimeData* data = new PlayableMimeData(); data->setText( s ); data->setData( "text/x-lfm-entity-type", "Predefined" ); data->m_type = Seed::PreDefinedType; return data; } Seed::Type type() const { return m_type; } void setType( Seed::Type t ) { m_type = t; } void setRQL( const QString& rql ){ m_rql = rql; } QString rql() const{ return m_rql; } private: Seed::Type m_type; QString m_rql; }; #endif //PLAYABLE_MIME_DATA ================================================ FILE: lib/unicorn/widgets/ProxyWidget.cpp ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QNetworkProxy> #include <lastfm/NetworkAccessManager.h> #include "lib/unicorn/UnicornSettings.h" #include "ProxyWidget.h" #include "ui_ProxyWidget.h" unicorn::ProxyWidget::ProxyWidget(QWidget *parent) : QWidget(parent), ui( new Ui::ProxyWidget) { ui->setupUi( this ); unicorn::AppSettings appSettings; QStringList proxyTypes; proxyTypes << tr( "Auto-detect" ) << tr( "No-proxy" ) << tr("HTTP") << tr("SOCKS5"); ui->proxyType->addItems( proxyTypes ); ui->proxyType->setCurrentIndex( appSettings.value( "proxyType", 0 ).toInt() ); ui->proxyHost->setText( appSettings.value( "proxyHost", "" ).toString() ); ui->proxyPort->setText( appSettings.value( "proxyPort", "" ).toString() ); ui->proxyUsername->setText( appSettings.value( "proxyUsername", "" ).toString() ); ui->proxyPassword->setText( appSettings.value( "proxyPassword", "" ).toString() ); connect( ui->proxyType, SIGNAL(currentIndexChanged(int)), SIGNAL(changed())); connect( ui->proxyHost, SIGNAL(textChanged(QString)), SIGNAL(changed())); connect( ui->proxyPort, SIGNAL(textChanged(QString)), SIGNAL(changed())); connect( ui->proxyUsername, SIGNAL(textChanged(QString)), SIGNAL(changed())); connect( ui->proxyPassword, SIGNAL(textChanged(QString)), SIGNAL(changed())); connect( this, SIGNAL(changed()), SLOT(onChanged()) ); onChanged(); } unicorn::ProxyWidget::~ProxyWidget() { delete ui; } void unicorn::ProxyWidget::onChanged() { bool enabled = ui->proxyType->currentIndex() > 1; ui->proxyHost->setEnabled( enabled ); ui->proxyPort->setEnabled( enabled ); ui->proxyUsername->setEnabled( enabled ); ui->proxyPassword->setEnabled( enabled ); } void unicorn::ProxyWidget::save() { unicorn::AppSettings appSettings; int proxyType = appSettings.value( "proxyType", 0 ).toInt(); QString proxyHost = appSettings.value( "proxyHost", "" ).toString(); QString proxyPort = appSettings.value( "proxyPort", "" ).toString(); QString proxyUsername = appSettings.value( "proxyUsername", "" ).toString(); QString proxyPassword = appSettings.value( "proxyPassword", "" ).toString(); if ( proxyType != ui->proxyType->currentIndex() || proxyHost != ui->proxyHost->text() || proxyPort != ui->proxyPort->text() || proxyUsername != ui->proxyUsername->text() || proxyPassword != ui->proxyPassword->text() ) { // one of the proxy settings has changed // save them appSettings.setValue( "proxyType", ui->proxyType->currentIndex() ); appSettings.setValue( "proxyHost", ui->proxyHost->text() ); appSettings.setValue( "proxyPort", ui->proxyPort->text() ); appSettings.setValue( "proxyUsername", ui->proxyUsername->text() ); appSettings.setValue( "proxyPassword", ui->proxyPassword->text() ); // set this new proxy QNetworkProxy::ProxyType type = QNetworkProxy::DefaultProxy; if ( ui->proxyType->currentIndex() == 1 ) type = QNetworkProxy::NoProxy; else if ( ui->proxyType->currentIndex() == 2 ) type = QNetworkProxy::HttpProxy; else if ( ui->proxyType->currentIndex() == 3 ) type = QNetworkProxy::Socks5Proxy; QNetworkProxy proxy( type, ui->proxyHost->text(), ui->proxyPort->text().toInt(), ui->proxyUsername->text(), ui->proxyPassword->text() ); lastfm::NetworkAccessManager* nam = qobject_cast<lastfm::NetworkAccessManager*>( lastfm::nam() ); if ( nam ) nam->setUserProxy( proxy ); } } ================================================ FILE: lib/unicorn/widgets/ProxyWidget.h ================================================ /* Copyright 2013 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef PROXYWIDGET_H #define PROXYWIDGET_H #include "lib/DllExportMacro.h" #include <QWidget> namespace Ui { class ProxyWidget; } namespace unicorn { class UNICORN_DLLEXPORT ProxyWidget : public QWidget { Q_OBJECT public: explicit ProxyWidget(QWidget *parent = 0); ~ProxyWidget(); void save(); signals: void changed(); private slots: void onChanged(); private: Ui::ProxyWidget* ui; }; } #endif // PROXYWIDGET_H ================================================ FILE: lib/unicorn/widgets/ProxyWidget.ui ================================================ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ProxyWidget</class> <widget class="QWidget" name="ProxyWidget"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>312</width> <height>106</height> </rect> </property> <property name="windowTitle"> <string notr="true">Form</string> </property> <layout class="QGridLayout" name="gridLayout_2"> <item row="1" column="1"> <widget class="QLineEdit" name="proxyHost"/> </item> <item row="1" column="0"> <widget class="QLabel" name="label"> <property name="text"> <string>Host:</string> </property> </widget> </item> <item row="6" column="1"> <widget class="QLineEdit" name="proxyUsername"/> </item> <item row="6" column="0"> <widget class="QLabel" name="label_2"> <property name="text"> <string>Username:</string> </property> </widget> </item> <item row="1" column="2"> <widget class="QLabel" name="label_4"> <property name="text"> <string>Port:</string> </property> </widget> </item> <item row="1" column="3"> <widget class="QLineEdit" name="proxyPort"/> </item> <item row="6" column="2"> <widget class="QLabel" name="label_3"> <property name="text"> <string>Password:</string> </property> </widget> </item> <item row="6" column="3"> <widget class="QLineEdit" name="proxyPassword"> <property name="echoMode"> <enum>QLineEdit::Password</enum> </property> </widget> </item> <item row="0" column="0" colspan="2"> <widget class="QComboBox" name="proxyType"/> </item> </layout> </widget> <resources/> <connections/> </ui> ================================================ FILE: lib/unicorn/widgets/SearchBox.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QKeyEvent> #include "SearchBox.h" #include <QNetworkReply> #include <QPushButton> #include <QListView> #include <QCompleter> #include <QStringListModel> #include <lastfm/XmlQuery.h> #include <lastfm/Artist.h> #include <lastfm/Tag.h> #include <lastfm/User.h> SearchBox::SearchBox(QWidget* parent) : QLineEdit( parent ) , m_searching(false) { setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ); setAttribute( Qt::WA_MacShowFocusRect, false ); m_completer = new QCompleter(this); m_completer->setCaseSensitivity(Qt::CaseInsensitive); setCompleter(m_completer); connect(this, SIGNAL(textEdited(QString)), SLOT(onTextEdited(QString))); } void SearchBox::onTextEdited(const QString& text) { QString trimmedText = text.trimmed(); if (!m_searching && trimmedText.length()) { QNetworkReply* reply = startSearch(trimmedText); if (reply) { m_searching = true; connect(reply, SIGNAL(finished()), SLOT(onSearchFinished())); } } } void SearchBox::keyPressEvent( QKeyEvent* event ) { if ( event->key() == Qt::Key_Backspace ) { if ( cursorPosition() == 0 ) { emit deletePressed(); return; } } else if ( event->text() == "," ) { emit commaPressed(); return; } QLineEdit::keyPressEvent( event ); } void SearchBox::onSearchFinished() { sender()->deleteLater(); QString searchTerm; XmlQuery lfm; if ( lfm.parse( qobject_cast<QNetworkReply*>(sender()) ) ) { searchTerm = ((QDomElement)lfm["results"]).attribute("for"); m_completer->setModel( new QStringListModel( handleSearchResponse(lfm))); m_completer->complete(); } m_searching = false; // possibly a search pending: if (text().trimmed() != searchTerm) { onTextEdited(text()); } } /////////////////////////////////////////// ArtistSearch::ArtistSearch(QWidget* parent) : SearchBox(parent) { } QNetworkReply* ArtistSearch::startSearch(const QString& term) { return Artist(term).search(); } QStringList ArtistSearch::handleSearchResponse(XmlQuery& lfm) { QStringList list; foreach(XmlQuery i, lfm["results"]["artistmatches"].children("artist")) { list << i["name"].text(); } return list; } /////////////////////////////////////////// TagSearch::TagSearch(QWidget* parent) : SearchBox(parent) { } QNetworkReply* TagSearch::startSearch(const QString& term) { return Tag(term).search(); } QStringList TagSearch::handleSearchResponse(XmlQuery& lfm) { QStringList list; foreach(XmlQuery i, lfm["results"]["tagmatches"].children("tag")) { list << i["name"].text().toLower(); } return list; } /////////////////////////////////////////// UserSearch::UserSearch(QWidget* parent) : SearchBox(parent) { connect(User().getFriends(), SIGNAL(finished()), SLOT(onGetFriendsFinished())); } int CaseInsensitiveLessThan(const QString& s1, const QString &s2) { return s1.toLower() < s2.toLower(); } void UserSearch::onGetFriendsFinished() { lastfm::UserList friendPage = User::list( (QNetworkReply*)sender() ); m_friends += friendPage.users(); if ( friendPage.currentPage() == friendPage.totalPages() ) { QStringList friends; foreach (User u, m_friends) friends << u.name(); qSort(friends.begin(), friends.end(), CaseInsensitiveLessThan); m_completer->setCaseSensitivity( Qt::CaseInsensitive ); m_completer->setModel(new QStringListModel( friends )); } else { // get the next page of friends connect(User().getFriends( false, friendPage.usersPerPage(), friendPage.currentPage() + 1 ), SIGNAL(finished()), SLOT(onGetFriendsFinished())); } } QNetworkReply* UserSearch::startSearch(const QString& term) { // alas, there is no user.search yet Q_UNUSED(term); m_completer->complete(); return 0; } QStringList UserSearch::handleSearchResponse(XmlQuery& lfm) { Q_UNUSED(lfm); return QStringList(); } ================================================ FILE: lib/unicorn/widgets/SearchBox.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SEARCH_BOX_H #define SEARCH_BOX_H #include <QLineEdit> #include <lastfm/global.h> #include "lib/DllExportMacro.h" #include <lastfm/User.h> class QNetworkReply; class QCompleter; namespace lastfm { class XmlQuery; } class UNICORN_DLLEXPORT SearchBox : public QLineEdit { Q_OBJECT; public: SearchBox(QWidget *parent = 0); signals: void commaPressed(); void deletePressed(); private slots: void onTextEdited(const QString& text); void onSearchFinished(); protected: virtual QNetworkReply* startSearch(const QString& term) = 0; virtual QStringList handleSearchResponse(XmlQuery& lfm) = 0; void keyPressEvent( QKeyEvent* event ); QCompleter* m_completer; bool m_searching; }; class UNICORN_DLLEXPORT ArtistSearch : public SearchBox { public: ArtistSearch(QWidget *parent = 0); virtual QNetworkReply* startSearch(const QString& term); virtual QStringList handleSearchResponse(XmlQuery& lfm); }; class UNICORN_DLLEXPORT TagSearch : public SearchBox { public: TagSearch(QWidget *parent = 0); virtual QNetworkReply* startSearch(const QString& term); virtual QStringList handleSearchResponse(XmlQuery& lfm); }; class UNICORN_DLLEXPORT UserSearch : public SearchBox { Q_OBJECT public: UserSearch(QWidget *parent = 0); virtual QNetworkReply* startSearch(const QString& term); virtual QStringList handleSearchResponse(XmlQuery& lfm); public slots: void finishEdit(){ emit editingFinished(); } private slots: void onGetFriendsFinished(); private: QList<lastfm::User> m_friends; }; #endif ================================================ FILE: lib/unicorn/widgets/Seed.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SEED_H #define SEED_H #include <lastfm/global.h> #include <QListWidgetItem> class Seed : public QObject { Q_OBJECT public: #if 0 Seed( class SeedListView* parent = 0 ); Seed( const QString& name, SeedListView* parent = 0 ); #endif enum Type { Undefined = -1, ArtistType = 0, TagType, UserType, PreDefinedType, CustomType }; #if 0 void setupItem() {} static Seed* createFromMimeData( const class PlayableMimeData* data, SeedListView* parent = 0 ); void setPlayableType( const Seed::Type t ){ m_type = t; } Seed::Type playableType() const{ return m_type; } Qt::ItemFlags flags() const{ return Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; } QString rql() const{ return m_rql; } void fetchImage(); void setPixmap( const QPixmap ); void setRQL( const QString& rql ){ m_rql = rql; } void setIcon( const QIcon& i ){ m_icon = i; emit updated(); } const QIcon& icon() const{ return m_icon; } void setName( const QString& name ){ m_name = name; emit updated(); } const QString& name() const{ return m_name; } signals: void updated(); public slots: void iconDataDownloaded(); private slots: void onArtistSearchFinished( QNetworkReply* r ); private: QPixmap cropToSize( const QPixmap, const QSize& ) const; QPixmap overlayPixmap( const QPixmap source, const QPixmap overlay, const QPoint offset = QPoint( 0, 0)) const; QString m_rql; Type m_type; QString m_name; QIcon m_icon; #endif }; Q_DECLARE_METATYPE( Seed::Type ) #endif //SEED_H ================================================ FILE: lib/unicorn/widgets/SlidingStackedWidget.cpp ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QDebug> #include <QPropertyAnimation> #include <QParallelAnimationGroup> #include "SlidingStackedWidget.h" unicorn::SlidingStackedWidget::SlidingStackedWidget( QWidget* parent ) : QStackedWidget(parent), m_speed( 200 ), m_animationtype( QEasingCurve::OutQuart ), m_vertical( false ), m_now( 0 ), m_next( 0 ), m_pnow( QPoint(0,0) ), m_active( false ), m_index( 0 ) { } void unicorn::SlidingStackedWidget::setAnimation(enum QEasingCurve::Type animationtype) { m_animationtype = animationtype; } void unicorn::SlidingStackedWidget::slide( int index ) { m_index = index; if ( !m_active ) { if ( index > count() - 1 ) index = (index) % count(); else index = ( index + count() ) % count(); slideWidget( widget( index ) ); } } void unicorn::SlidingStackedWidget::slideWidget( QWidget* newwidget ) { m_active=true; enum t_direction directionhint; int now=currentIndex(); //currentIndex() is a function inherited from QStackedWidget int next=indexOf(newwidget); if ( now == next ) { m_active=false; emit currentChanged( currentIndex() ); return; } else if ( now < next ) { directionhint = m_vertical ? TOP2BOTTOM : RIGHT2LEFT; } else { directionhint = m_vertical ? BOTTOM2TOP : LEFT2RIGHT; } //NOW.... //calculate the shifts int offsetx=frameRect().width(); //inherited from mother int offsety=frameRect().height();//inherited from mother //the following is important, to ensure that the new widget //has correct geometry information when sliding in first time widget(next)->setGeometry ( 0, 0, offsetx, offsety ); if (directionhint==BOTTOM2TOP) { offsetx=0; offsety=-offsety; } else if (directionhint==TOP2BOTTOM) { offsetx=0; //offsety=offsety; } else if (directionhint==RIGHT2LEFT) { offsetx=-offsetx; offsety=0; } else if (directionhint==LEFT2RIGHT) { //offsetx=offsetx; offsety=0; } //re-position the next widget outside/aside of the display area QPoint pnext=widget(next)->pos(); QPoint pnow=widget(now)->pos(); m_pnow=pnow; widget(next)->move(pnext.x()-offsetx,pnext.y()-offsety); //make it visible/show widget(next)->show(); widget(next)->raise(); //animate both, the now and next widget to the side, using animation framework QPropertyAnimation *animnow = new QPropertyAnimation(widget(now), "pos"); animnow->setDuration(m_speed); animnow->setEasingCurve(m_animationtype); animnow->setStartValue(QPoint(pnow.x(), pnow.y())); animnow->setEndValue(QPoint(offsetx+pnow.x(), offsety+pnow.y())); QPropertyAnimation *animnext = new QPropertyAnimation(widget(next), "pos"); animnext->setDuration(m_speed); animnext->setEasingCurve(m_animationtype); animnext->setStartValue(QPoint(-offsetx+pnext.x(), offsety+pnext.y())); animnext->setEndValue(QPoint(pnext.x(), pnext.y())); QParallelAnimationGroup *animgroup = new QParallelAnimationGroup; animgroup->addAnimation(animnow); animgroup->addAnimation(animnext); QObject::connect(animgroup, SIGNAL(finished()),this,SLOT(animationDoneSlot())); m_next=next; m_now=now; m_active=true; animgroup->start(); } void unicorn::SlidingStackedWidget::animationDoneSlot(void) { setCurrentIndex( m_next ); widget(m_now)->hide(); widget(m_now)->move(m_pnow); m_active = false; // animate again if they changed index while we were animating if ( m_index != currentIndex() ) slide( m_index ); else emit animationFinished(); } ================================================ FILE: lib/unicorn/widgets/SlidingStackedWidget.h ================================================ /* Copyright 2011 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SLIDINGSTACKEDWIDGET_H #define SLIDINGSTACKEDWIDGET_H #include <QStackedWidget> #include <QEasingCurve> #include "lib/DllExportMacro.h" namespace unicorn { class UNICORN_DLLEXPORT SlidingStackedWidget : public QStackedWidget { Q_OBJECT public: //! This enumeration is used to define the animation direction enum t_direction { LEFT2RIGHT, RIGHT2LEFT, TOP2BOTTOM, BOTTOM2TOP, AUTOMATIC }; //! The Constructor and Destructor explicit SlidingStackedWidget(QWidget *parent); Q_PROPERTY( QEasingCurve::Type easingCurve READ easingCurve WRITE setEasingCurve ) Q_PROPERTY( int speed READ speed WRITE setSpeed ) QEasingCurve::Type easingCurve() const { return m_animationtype; } void setEasingCurve( QEasingCurve::Type animationtype ) { m_animationtype = animationtype; } int speed() const { return m_speed; } void setSpeed( int speed ) { m_speed = speed; } public slots: //! Some basic settings API void setAnimation(enum QEasingCurve::Type animationtype); //check out the QEasingCurve documentation for different styles //! The Animation / Page Change API void slide( int index ); signals: //! this is used for internal purposes in the class engine void animationFinished(void); protected slots: //! this is used for internal purposes in the class engine void animationDoneSlot(void); protected: //! this is used for internal purposes in the class engine void slideWidget( QWidget * widget ); QWidget* m_mainwindow; int m_speed; enum QEasingCurve::Type m_animationtype; bool m_vertical; int m_now; int m_next; QPoint m_pnow; bool m_active; int m_index; }; } #endif // SLIDINGSTACKEDWIDGET_H ================================================ FILE: lib/unicorn/widgets/SpinnerLabel.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SPINNER_LABEL_H #define SPINNER_LABEL_H #include <QEvent> #include <QLabel> //TODO implementation #include <QMovie> //TODO implementation #include <QDebug> class SpinnerLabel : public QLabel { virtual bool event( QEvent* e ) { switch ((int)e->type()) { case QEvent::Hide: m_movie->stop(); break; case QEvent::Show: m_movie->start(); break; } return QLabel::event( e ); } QMovie* m_movie; public: SpinnerLabel( QWidget* parent = 0 ) : QLabel( parent ) { setMovie( m_movie = new QMovie( ":/lastfm/spinner.mng" ) ); m_movie->setParent( this ); //qt fucking sucks setFixedSize( 25, 18 ); } }; #endif //SPINNER_LABEL_H ================================================ FILE: lib/unicorn/widgets/StackedWidget.cpp ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "StackedWidget.h" unicorn::StackedWidget::StackedWidget(QWidget *parent) : QStackedWidget(parent) { connect( this, SIGNAL(currentChanged(int)), SLOT(onCurrentChanged(int))); } QSize unicorn::StackedWidget::sizeHint() const { return currentWidget()->sizeHint(); } void unicorn::StackedWidget::onCurrentChanged( int index ) { for ( int i = 0 ; i < count() ; ++i ) { if ( i == index ) { widget( i )->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ); widget( i )->adjustSize(); } else widget( i )->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored ); } adjustSize(); } ================================================ FILE: lib/unicorn/widgets/StackedWidget.h ================================================ /* Copyright 2012 Last.fm Ltd. - Primarily authored by Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef STACKEDWIDGET_H #define STACKEDWIDGET_H #include <QStackedWidget> #include "lib/DllExportMacro.h" namespace unicorn { class UNICORN_DLLEXPORT StackedWidget : public QStackedWidget { Q_OBJECT public: explicit StackedWidget(QWidget *parent = 0); private slots: void onCurrentChanged( int index ); private: QSize sizeHint() const; }; } #endif // STACKEDWIDGET_H ================================================ FILE: lib/unicorn/widgets/StatusLight.cpp ================================================ /*************************************************************************** * Copyright 2008 by P. Sereno * * http://www.sereno-online.com * * http://www.qt4lab.org * * http://www.qphoton.org * * Copyright 2009 Last.fm Ltd. <max@last.fm> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "StatusLight.h" #include <QDebug> #include <QFontMetrics> #include <QPainter> #include <QRadialGradient> StatusLight::StatusLight( QWidget* parent ) : QWidget( parent ) { m_color = Qt::green; int const M = fontMetrics().height(); setFixedSize( M, M ); } void StatusLight::paintEvent( QPaintEvent* ) { QPainter p( this ); p.setRenderHint( QPainter::Antialiasing, true ); p.setWindow( -50,-50, 100, 100 ); p.setPen( Qt::white ); p.drawArc( -25, -25, 50, 50, 0, 5670 ); p.drawArc( -32, -33, 66, 66, 0, 5670 ); p.setPen( QColor(Qt::darkGray).lighter( 125 ) ); p.drawArc( -34, -33, 66, 66, 0, 180*16 ); QRadialGradient radialGrad(QPointF(0, -14), 20); radialGrad.setColorAt( 0, QColor( 0xff, 0xff, 0xff, 0.8 ) ); radialGrad.setColorAt( 1, m_color ); QBrush brush(radialGrad); p.setBrush(brush); p.setPen( m_color.darker() ); p.drawEllipse(-25,-25,50,50); } ================================================ FILE: lib/unicorn/widgets/StatusLight.h ================================================ /*************************************************************************** * Copyright 2008 by P. Sereno * * http://www.sereno-online.com * * http://www.qt4lab.org * * http://www.qphoton.org * * Copyright 2009 Last.fm Ltd. <max@last.fm> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef STATUS_LIGHT_H #define STATUS_LIGHT_H #include "lib/DllExportMacro.h" #include <QColor> #include <QWidget> class UNICORN_DLLEXPORT StatusLight : public QWidget { QColor m_color; public: StatusLight( QWidget* parent = 0 ); void setColor( const QColor& c ) { m_color = c; } protected: void paintEvent( QPaintEvent* ); }; #endif ================================================ FILE: lib/unicorn/widgets/TagListWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "TagListWidget.h" #include "PlayableMimeData.h" #include <lastfm/Tag.h> #include <lastfm/ws.h> #include <QDesktopServices> #include <QHeaderView> #include <QItemDelegate> #include <QMenu> #include <QUrl> #include "lib/unicorn/DesktopServices.h" TagListWidget::TagListWidget( QWidget* parent ) : QTreeWidget( parent ) , m_currentReply( 0 ) { setColumnCount( 2 ); setRootIsDecorated( false ); setContextMenuPolicy( Qt::CustomContextMenu ); setFrameStyle( NoFrame ); setAlternatingRowColors( true ); setDragEnabled( true ); class TallerRowDelegate : public QItemDelegate { virtual QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const { return QItemDelegate::sizeHint( option, index ) + QSize( 0, 4 ); } }; setItemDelegate( new TallerRowDelegate ); QTreeWidget::hideColumn( 1 ); QTreeWidget::header()->hide(); m_menu = new QMenu( this ); QActionGroup* group = new QActionGroup( this ); QAction* a = m_menu->addAction( tr( "Sort by Popularity" ) ); connect( a, SIGNAL(triggered()), SLOT(sortByPopularity()) ); group->addAction( a ); a->setCheckable( true ); a->setChecked( true ); a = m_menu->addAction( tr( "Sort Alphabetically" ) ); connect( a, SIGNAL(triggered()), SLOT(sortAlphabetically()) ); group->addAction( a ); a->setCheckable( true ); m_menu->addSeparator(); a = m_menu->addAction( tr("Open Last.fm Page for this Tag") ); connect( a, SIGNAL(triggered()), SLOT(openTagPageForCurrentItem()) ); connect( this, SIGNAL(customContextMenuRequested( QPoint )), SLOT(showMenu( QPoint )) ); connect( this, SIGNAL(doubleClicked( const QModelIndex& )), SLOT(onDoubleClicked ( const QModelIndex& )) ); } QTreeWidgetItem* TagListWidget::createNewItem( QString tag ) { tag = tag.toLower(); QTreeWidgetItem* item = new QTreeWidgetItem( QStringList() << tag ); QIcon icon; icon.addPixmap( QPixmap( ":/buckets/tag.png" ) ); item->setIcon( 0, icon ); addTopLevelItem( item ); return item; } bool TagListWidget::add( QString tag ) { //FIXME avoid duplicates createNewItem( tag ); m_newTags += tag; return true; } void TagListWidget::showMenu( const QPoint& point ) { m_menu->exec( mapToGlobal( point ) ); } void TagListWidget::sortAlphabetically() { sortItems( 0, Qt::AscendingOrder ); } void TagListWidget::sortByPopularity() { //I got here and wasn't sure if sortItems() should be used instead either --mxcl sortByColumn( 1, Qt::DescendingOrder ); } void TagListWidget::openTagPageForCurrentItem() { unicorn::DesktopServices::openUrl( Tag( currentItem()->text( 0 ) ).www() ); } void TagListWidget::setTagsRequest( QNetworkReply* r ) { clear(); delete m_currentReply; m_currentReply = r; connect( r, SIGNAL(finished()), SLOT(onTagsRequestFinished()) ); } void TagListWidget::onTagsRequestFinished() { QNetworkReply* r = (QNetworkReply*)sender(); QMap<int, QString> tags = Tag::list( r ); QMapIterator<int, QString> i( tags ); while (i.hasNext()) { QTreeWidgetItem *entry = createNewItem( i.next().value() ); // I couldn't make it sort properly otherwise, even the QVariant methods wouldn't work! entry->setText( 1, QString::number( 10 * 1000 + i.key() ) ); } m_currentReply = 0; } QMimeData* TagListWidget::mimeData( const QList<QTreeWidgetItem *> items ) const { if( items.count() < 1 ) return 0; Tag tag( items.first()->text( 0 ) ); PlayableMimeData* pData = PlayableMimeData::createFromTag( tag ); return pData; } #include <QPainter> TagIconView::TagIconView() { setAlternatingRowColors( false ); disconnect( this, SIGNAL(customContextMenuRequested( QPoint )), 0, 0 ); } void TagIconView::paintEvent( QPaintEvent* e ) { TagListWidget::paintEvent( e ); if (topLevelItemCount()) return; QPainter p( viewport() ); p.setPen( Qt::lightGray ); #ifndef WIN32 QFont f = p.font(); f.setPixelSize( 15 ); p.setFont( f ); #endif p.drawText( viewport()->rect(), Qt::AlignCenter, tr("Type a tag above,\nor choose from below") ); } ================================================ FILE: lib/unicorn/widgets/TagListWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef TAGLISTWIDGET_H #define TAGLISTWIDGET_H #include <QTreeWidget> #include "PlayableMimeData.h" class TagListWidget : public QTreeWidget { Q_OBJECT public: TagListWidget( QWidget* parent = 0 ); using QTreeWidget::indexFromItem; /** we won't add the tag if we already have it, and in that case we * return false */ bool add( QString ); QStringList newTags() const { return m_newTags; } public slots: void setTagsRequest( QNetworkReply* ); protected: virtual QMimeData* mimeData( const QList<QTreeWidgetItem *> items ) const; private slots: void onTagsRequestFinished(); private: class QMenu* m_menu; QStringList m_newTags; QNetworkReply *m_currentReply; QTreeWidgetItem* createNewItem( QString tag ); private slots: void showMenu( const QPoint& ); void sortByPopularity(); void sortAlphabetically(); void openTagPageForCurrentItem(); }; class TagIconView : public TagListWidget { virtual void paintEvent( QPaintEvent* ); public: TagIconView(); }; #endif // TAGLISTWIDGET_H ================================================ FILE: lib/unicorn/widgets/TrackWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "TrackWidget.h" #include "lib/unicorn/TrackImageFetcher.h" #include <QHBoxLayout> #include <QVBoxLayout> #include <QLabel> #include <QStackedWidget> #include <QRadioButton> #include <QPixmap> TrackWidget::TrackWidget( const lastfm::Track& track ) :m_track( track ) { // the radio buttons layout QVBoxLayout* radioButtons = new QVBoxLayout; radioButtons->addWidget( ui.trackShare = new QRadioButton( tr("Track"), this ) ); radioButtons->addWidget( ui.albumShare = new QRadioButton( tr("Album"), this ) ); radioButtons->addWidget( ui.artistShare = new QRadioButton( tr("Artist"), this ) ); connect( ui.artistShare, SIGNAL(clicked(bool)), SLOT(onRadioButtonsClicked(bool)) ); connect( ui.albumShare, SIGNAL(clicked(bool)), SLOT(onRadioButtonsClicked(bool)) ); connect( ui.trackShare, SIGNAL(clicked(bool)), SLOT(onRadioButtonsClicked(bool)) ); QHBoxLayout* h = new QHBoxLayout( this ); h->addLayout( radioButtons ); h->addWidget( ui.image = new QLabel, Qt::AlignLeft ); h->addWidget( ui.description = new QLabel, Qt::AlignLeft ); ui.image->setScaledContents( true ); // start fetching the image m_fetcherAlbum = new TrackImageFetcher( track, Track::MediumImage ); connect( m_fetcherAlbum, SIGNAL(finished( QPixmap )), SLOT(onCoverDownloaded( QPixmap )) ); m_fetcherAlbum->startAlbum(); // start fetching the image m_fetcherArtist = new TrackImageFetcher( track, Track::MediumImage ); connect( m_fetcherArtist, SIGNAL(finished( QPixmap )), SLOT(onArtistDownloaded( QPixmap )) ); m_fetcherArtist->startArtist(); // default the track being selected ui.trackShare->setChecked( true ); onRadioButtonsClicked( true ); // we sometimes don't know the album name so disable the ablum option in that case if ( m_track.album().isNull() ) ui.albumShare->setEnabled( false ); ui.image->setFixedSize( radioButtons->sizeHint().height(), radioButtons->sizeHint().height() ); setFixedWidth( 400 ); } void TrackWidget::onRadioButtonsClicked( bool ) { QFontMetrics fm( font() ); // change the share desription to what we are now sharing if ( ui.artistShare->isChecked() ) { QString artistName = fm.elidedText( m_track.artist().name(), Qt::ElideRight, ui.description->width() ); ui.description->setText( artistName + "\n" ); ui.description->setToolTip( m_track.artist().name() ); ui.image->setPixmap( ui.artistImage ); } else if ( ui.albumShare->isChecked() ) { QString albumTitle = fm.elidedText( m_track.album().title(), Qt::ElideRight, ui.description->width() ); QString artistName = fm.elidedText( m_track.artist().name(), Qt::ElideRight, ui.description->width() ); ui.description->setText( albumTitle + "\n" + artistName ); ui.description->setToolTip( m_track.album().title() + "\n" + m_track.artist().name() ); ui.image->setPixmap( ui.albumImage ); } else if ( ui.trackShare->isChecked() ) { QString durationString = " (" + m_track.durationString() + ")\n"; int durationWidth = fm.width( durationString ); QString title = fm.elidedText( m_track.title(), Qt::ElideRight, ui.description->width() - durationWidth ); QString artistName = fm.elidedText( m_track.artist().name(), Qt::ElideRight, ui.description->width() ); ui.description->setText( title + durationString + artistName ); ui.description->setToolTip( m_track.title() + durationString + m_track.artist().name() ); ui.image->setPixmap( ui.artistImage ); } } TrackWidget::Type TrackWidget::type() const { if ( ui.artistShare->isChecked() ) return Artist; else if ( ui.albumShare->isChecked() ) return Album; return Track; } void TrackWidget::onCoverDownloaded( const QPixmap& pixmap ) { ui.albumImage = pixmap; onRadioButtonsClicked( true ); sender()->deleteLater(); } void TrackWidget::onArtistDownloaded( const QPixmap& pixmap ) { ui.artistImage = pixmap; onRadioButtonsClicked( true ); sender()->deleteLater(); } ================================================ FILE: lib/unicorn/widgets/TrackWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef TRACK_WIDGET_H #define TRACK_WIDGET_H #include <lastfm/global.h> #include <lastfm/Track.h> #include <QWidget> #include "lib/DllExportMacro.h" #include <QStackedWidget> class UNICORN_DLLEXPORT TrackWidget : public QWidget { Q_OBJECT public: enum Type { Artist, Album, Track } m_type; private: struct { class QRadioButton* trackShare; class QRadioButton* albumShare; class QRadioButton* artistShare; class QLabel* image; class QLabel* description; class QPixmap artistImage; class QPixmap albumImage; } ui; public: TrackWidget( const lastfm::Track& track ); Type type() const; private slots: void onCoverDownloaded( const class QPixmap& ); void onArtistDownloaded( const class QPixmap& ); void onRadioButtonsClicked( bool ); private: const lastfm::Track& m_track; class TrackImageFetcher* m_fetcherAlbum; class TrackImageFetcher* m_fetcherArtist; }; #endif ================================================ FILE: lib/unicorn/widgets/UnicornTabWidget.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QMouseEvent> #include <QPainter> #include <QTabBar> #include <QHBoxLayout> #include <QApplication> #include "UnicornTabWidget.h" const int unicorn::TabBar::k_startTearDistance = 30; unicorn::TabBar::TabBar() :m_spacing( 0 ), m_leftMargin( 5 ), m_active( ":/DockWindow/tab/active.png" ) { #ifndef WIN32 QFont f = font(); f.setPixelSize( 10 ); f.setBold( true ); setFont( f ); #endif QPalette p = palette(); QLinearGradient window( 0, 0, 0, sizeHint().height()); window.setColorAt( 0, 0x3c3939 ); window.setColorAt( 1, 0x282727 ); p.setBrush( QPalette::Window, window ); QPixmap pm = m_active.copy( (m_active.width() / 2)-1, 0, 2, m_active.height()); p.setBrush( QPalette::Button, pm ); QLinearGradient buttonHighlight( 0, 0, 0, sizeHint().height() - 14 ); buttonHighlight.setColorAt( 0, Qt::black ); buttonHighlight.setColorAt( 1, 0x474243 ); p.setBrush( QPalette::Midlight, buttonHighlight ); p.setColor( QPalette::Active, QPalette::Text, 0x848383 ); p.setColor( QPalette::Inactive, QPalette::Text, 0x848383 ); setPalette( p ); setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed ); setFixedHeight( 33 ); setMinimumHeight( 33 ); new QHBoxLayout( this ); layout()->setContentsMargins( 0, 0, 5, 0 ); ((QHBoxLayout*)layout())->addStretch( 1 ); setAutoFillBackground( true ); } QSize unicorn::TabBar::sizeHint() const { return QSize( minimumWidth(), 33 ); } void unicorn::TabBar::mousePressEvent( QMouseEvent* e ) { if (e->button() != Qt::LeftButton) { e->ignore(); return; } m_mouseDownPos = e->pos(); int w = (minimumWidth() - layout()->minimumSize().width() - m_leftMargin) / count(); int index = ( (e->pos().x() - m_leftMargin ) / (w + m_spacing )); if( index < count() ) setCurrentIndex( index ); } void unicorn::TabBar::mouseReleaseEvent( QMouseEvent* ) { m_mouseDownPos = QPoint(); } void unicorn::TabBar::mouseMoveEvent( QMouseEvent* e ) { if( !m_tearable ) return; if( !(e->buttons() & Qt::LeftButton) || m_mouseDownPos.isNull() ) return; if( (e->pos() - m_mouseDownPos ).manhattanLength() < k_startTearDistance) return; TabWidget* tabWidget = qobject_cast<TabWidget*>( parentWidget() ); if( !tabWidget ) return; int index = currentIndex(); removeTab( index ); QWidget* curWidget = tabWidget->widget( index ); if( !curWidget ) return; QPoint offset = curWidget->mapToGlobal(curWidget->pos()) - QCursor::pos(); curWidget->setParent( window(), Qt::Tool ); curWidget->move( QCursor::pos() + QPoint(offset.x(), 0)); curWidget->resize( tabWidget->size()); curWidget->show(); m_tearable = false; while( QApplication::mouseButtons() & Qt::LeftButton ) { if( curWidget->pos() != QCursor::pos()) curWidget->move( QCursor::pos() + QPoint(offset.x(), 0)); QApplication::processEvents( QEventLoop::WaitForMoreEvents ); } curWidget->installEventFilter( this ); m_tearable = true; m_mouseDownPos = QPoint(); } void unicorn::TabBar::tabInserted( int ) { int w = 0; for (int i = 0; i < count(); ++i) w = qMax( fontMetrics().width( tabText( i ) ), w ); setMinimumWidth( (m_leftMargin + (count() * ( w + 20 ))) + 10 + layout()->minimumSize().width()); } void unicorn::TabBar::tabRemoved( int i ) { tabInserted( i ); } void unicorn::TabBar::addWidget( QWidget* wi ) { layout()->addWidget( wi ); int w = 0; for (int i = 0; i < count(); ++i) w = qMax( fontMetrics().width( tabText( i ) ), w ); setMinimumWidth( (m_leftMargin + (count() * ( w + 20 ))) + 10 + layout()->minimumSize().width()); } void unicorn::TabBar::paintEvent( QPaintEvent* e ) { QPainter p( this ); p.setClipRect( e->rect()); if( count() <= 0 ) return; int w = (minimumWidth() - layout()->minimumSize().width() - m_leftMargin) / count(); for (int i = 0; i < count(); ++i) { int const x = m_leftMargin + (i * ( w + m_spacing )); if (i == count() - 1) w += (minimumWidth() - layout()->minimumSize().width() - m_leftMargin + 10) % w; if (currentIndex() == i) { p.setBrush( palette().brush( QPalette::Button ) ); p.drawPixmap( x, 7, 8, m_active.height(), m_active, 0, 0, 8, m_active.height() ); p.drawPixmap( x + 8, 7, w -16, m_active.height(), palette().brush( QPalette::Button ).texture()); p.drawPixmap( x + w - 8, 7, 9, m_active.height(), m_active, m_active.width() / 2, 0, 9, m_active.height() ); p.setPen( palette().color( QPalette::Active, QPalette::Text ) ); } else { p.setPen( palette().color( QPalette::Inactive, QPalette::Text ) ); } p.drawText( x, -1, w, height(), Qt::AlignCenter, tabText( i ) ); } const int h = height() - 1; p.setPen( QPen( Qt::black, 0 ) ); p.setRenderHint( QPainter::Antialiasing, false ); p.drawLine( 0, h, width(), h ); } void unicorn::TabBar::setSpacing( int spacing ) { m_spacing = spacing; } bool unicorn::TabBar::eventFilter( QObject* o, QEvent* e ) { if( e->type() != QEvent::Close ) return false; QWidget* w; if( !( w = qobject_cast< QWidget* >( o ))) return false; if( w->windowTitle().isEmpty()) return false; ((TabWidget*)parentWidget())->addTab( w ); w->removeEventFilter( this ); return true; } unicorn::TabWidget::TabWidget() { QVBoxLayout* v = new QVBoxLayout( this ); v->addWidget( m_bar = new TabBar ); // m_bar->setSpacing( 3 ); v->addWidget( m_stack = new QStackedWidget ); v->setSpacing( 0 ); v->setMargin( 0 ); setAutoFillBackground( true ); QPalette p = palette(); p.setBrush( QPalette::Window, QBrush( 0x0e0e0e ) ); setPalette( p ); setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred ); connect( m_bar, SIGNAL(currentChanged( int )), m_stack, SLOT(setCurrentIndex( int )) ); connect( m_bar, SIGNAL(currentChanged( int )), SIGNAL(currentChanged(int))); } void unicorn::TabWidget::addTab( const QString& title, QWidget* w ) { m_bar->addTab( title ); setMinimumWidth( m_bar->minimumWidth()); m_stack->addWidget( w ); w->setAttribute( Qt::WA_MacShowFocusRect, false ); } void unicorn::TabWidget::addTab( QWidget* w ) { Q_ASSERT( !w->windowTitle().isEmpty()); addTab( w->windowTitle(), w ); } void unicorn::TabWidget::setTabEnabled( int index, bool b ) { m_bar->setTabEnabled( index, b ); } QWidget* unicorn::TabWidget::widget( int index ) const { return m_stack->widget( index ); } ================================================ FILE: lib/unicorn/widgets/UnicornTabWidget.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef UNICORN_TAB_WIDGET_H #define UNICORN_TAB_WIDGET_H #include <QPixmap> #include <QStackedWidget> #include <QTabBar> #include <QDebug> namespace unicorn { class TabBar : public QTabBar { friend class UnicornMacStyle; public: TabBar(); virtual QSize sizeHint() const; void setSpacing( int ); void setTearable( bool t ){ m_tearable = t; } void addWidget( QWidget* w ); protected: virtual void mousePressEvent( QMouseEvent* ); virtual void mouseReleaseEvent( QMouseEvent* ); virtual void mouseMoveEvent( QMouseEvent* ); virtual void paintEvent( QPaintEvent* ); virtual void tabInserted( int ); virtual void tabRemoved( int ); virtual bool eventFilter(QObject* , QEvent* ); int m_spacing; const int m_leftMargin; const QPixmap m_active; private: QPoint m_mouseDownPos; bool m_tearable; static const int k_startTearDistance; }; class TabWidget : public QWidget { Q_OBJECT QStackedWidget* m_stack; TabBar* m_bar; public: TabWidget(); TabBar* bar() const { return m_bar; } void addTab( const QString& title, QWidget* ); void addTab( QWidget* ); QWidget* currentWidget() const { return m_stack->currentWidget(); } virtual QSize sizeHint() const { return QWidget::sizeHint().expandedTo( m_bar->sizeHint() ); } void setTabEnabled( int index, bool ); void setTearable( bool t ){ bar()->setTearable( t ); } QWidget* widget( int index ) const; signals: void currentChanged( int ); }; } #endif ================================================ FILE: lib/unicorn/widgets/UserComboSelector.h ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole, William Viana Soares and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef USER_COMBO_SELECTOR_H_ #define USER_COMBO_SELECTOR_H_ #include <QComboBox> #include <QStandardItemModel> #include <lastfm/User.h> #include "lib/DllExportMacro.h" #include "lib/unicorn/UnicornApplication.h" #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/UnicornSession.h" class UNICORN_DLLEXPORT UserComboSelector : public QComboBox { Q_OBJECT public: UserComboSelector( QWidget* p = 0 ) :QComboBox( p ) { setStyle( qApp->style()); refresh(); connect( this, SIGNAL( activated( int)), SLOT( onActivated( int ))); connect( qApp, SIGNAL( rosterUpdated()), SLOT( refresh())); connect( qApp, SIGNAL( sessionChanged( unicorn::Session, unicorn::Session )) , SLOT( onSessionChanged( unicorn::Session ))); } protected: void changeUser( const QString& username ) { unicorn::UserSettings us( username ); QString sessionKey = us.value( "sessionKey", "" ).toString(); QMetaObject::invokeMethod( qApp, "changeSession", Q_ARG( const QString, username ), Q_ARG( const QString, sessionKey ) ); } protected slots: void onSessionChanged( unicorn::Session& s ) { int index = findText( s.user().name() ); setCurrentIndex( index ); } void onActivated( int index ) { if( itemData( index ).toBool()) return changeUser( itemText( index )); //Reset current user as selected item refresh(); //show manage users dialog QMetaObject::invokeMethod( qApp, "manageUsers", Qt::DirectConnection ); //Reset user list / selected user refresh(); } void refresh() { clear(); foreach( User u, unicorn::Settings().userRoster() ) { addItem( u.name(), true ); } insertSeparator( count()); //why is there no addSeparator?! addItem( "Manage Users", false ); onSessionChanged( qobject_cast<unicorn::Application*>(qApp)->currentSession() ); } }; #endif ================================================ FILE: lib/unicorn/widgets/UserManagerWidget.cpp ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole, William Viana Soares and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "UserManagerWidget.h" #include "lib/unicorn/widgets/AvatarWidget.h" #include "lib/unicorn/dialogs/LoginContinueDialog.h" #include "lib/unicorn/dialogs/LoginDialog.h" #include "lib/unicorn/LoginProcess.h" #include "lib/unicorn/QMessageBoxBuilder.h" #include "lib/unicorn/UnicornApplication.h" #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/UnicornSession.h" #include <lastfm/User.h> #include <QApplication> #include <QButtonGroup> #include <QDebug> #include <QDialogButtonBox> #include <QGroupBox> #include <QHBoxLayout> #include <QLabel> #include <QMouseEvent> #include <QPushButton> #include <QTimer> #include <QVBoxLayout> using lastfm::User; UserRadioButton::UserRadioButton( const User& user ) { addWidget( ui.button = new QRadioButton() ); ui.button->setObjectName( "button" ); addWidget( ui.image = new AvatarWidget() ); ui.image->setObjectName( "image" ); QVBoxLayout* userTextLayout = new QVBoxLayout(); userTextLayout->setContentsMargins( 0, 0, 0, 0 ); userTextLayout->setSpacing( 0 ); userTextLayout->addWidget( ui.username = new QLabel( user.name() ) ); userTextLayout->addWidget( ui.realName = new QLabel() ); userTextLayout->addWidget( ui.loggedIn = new QLabel() ); ui.realName->setObjectName( "realname" ); addLayout(userTextLayout); addStretch(); addWidget( ui.remove = new QPushButton( tr("Remove") )); setUser( user ); if( user.imageUrl( User::MediumImage ).isEmpty() ) { QNetworkReply* reply = User::getInfo( user.name() ); connect( reply, SIGNAL(finished()), SLOT( onUserFetched())); } connect( ui.button, SIGNAL(clicked()), this, SIGNAL(clicked()) ); connect( ui.remove, SIGNAL(clicked()), this, SIGNAL(remove()) ); connect( qApp, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onSessionChanged(unicorn::Session)) ); } void UserRadioButton::onSessionChanged( const unicorn::Session& session ) { if( ui.username->text() == session.user().name() ) ui.loggedIn->setText( tr( "(currently logged in)" ) ); else ui.loggedIn->clear(); } void UserRadioButton::click() { ui.button->click(); } bool UserRadioButton::isChecked() const { return ui.button->isChecked(); } void UserRadioButton::setUser( const lastfm::User& user ) { ui.username->setText( user.name() ); if( !user.realName().isEmpty() ) ui.realName->setText( QString( "(%1)" ).arg( user.realName() ) ); if( user == User() ) ui.loggedIn->setText( tr( "(currently logged in)" ) ); if ( !user.imageUrl( User::MediumImage ).isEmpty() ) ui.image->loadUrl( user.imageUrl( User::MediumImage ) ); ui.image->setHref( user.www() ); } void UserRadioButton::onUserFetched() { QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender()); Q_ASSERT( reply ); XmlQuery lfm; if ( lfm.parse( reply ) ) { User user( lfm["user"] ); setUser( user ); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } } const QString UserRadioButton::user() const { return ui.username->text(); } UserManagerWidget::UserManagerWidget( QWidget* parent ) :QWidget( parent ) { m_lcd = new LoginContinueDialog( this ); connect( m_lcd, SIGNAL( accepted()), SLOT( onLoginContinueDialogAccepted())); QVBoxLayout* layout = new QVBoxLayout( this ); layout->setSpacing( 10 ); layout->addWidget( ui.groupBox = new QGroupBox() ); ui.usersLayout = new QVBoxLayout( ui.groupBox ); ui.groupBox->setTitle( tr( "Connected User Accounts:" )); QHBoxLayout* buttonLayout = new QHBoxLayout(); ui.addUserButton = new QPushButton( tr( "Add New User Account" ), this ); buttonLayout->addWidget( ui.addUserButton ); buttonLayout->addStretch( 1 ); layout->addLayout( buttonLayout ); layout->addStretch(); QList<lastfm::User> roster = unicorn::Settings().userRoster(); foreach( lastfm::User u, roster ) { UserRadioButton* b = new UserRadioButton( u ); add( b, false ); } connect( ui.addUserButton, SIGNAL( clicked() ), SLOT( onAddUserClicked() ) ); } UserManagerWidget::~UserManagerWidget() { } void UserManagerWidget::onAddUserClicked() { LoginDialog* ld = new LoginDialog( this ); ld->setWindowFlags( Qt::Sheet ); ld->open(); connect( ld, SIGNAL( accepted()), SLOT( onLoginDialogAccepted()) ); } void UserManagerWidget::onLoginDialogAccepted() { LoginDialog* ld = qobject_cast<LoginDialog*>(sender()); Q_ASSERT( ld ); if ( m_loginProcess ) { delete m_loginProcess; m_loginProcess = 0; } m_loginProcess = new unicorn::LoginProcess( this ); ld->deleteLater(); connect( qApp, SIGNAL(sessionChanged(unicorn::Session)), SLOT(onLoginComplete(unicorn::Session)) ); m_loginProcess->authenticate(); m_lcd->setWindowFlags( Qt::Sheet ); m_lcd->open(); } void UserManagerWidget::onLoginComplete( const unicorn::Session& session ) { Q_ASSERT( m_loginProcess ); disconnect( qApp, SIGNAL(sessionChanged(unicorn::Session)), this, SLOT(onLoginComplete(unicorn::Session)) ); if ( session.user().name().isEmpty() ) { return; } bool alreadyAdded = false; foreach ( UserRadioButton* b, findChildren<UserRadioButton*>() ) { if ( session.user().name() == b->user() ) { alreadyAdded = true; break; } } if ( !alreadyAdded ) { User user( session.user().name() ); UserRadioButton* urb = new UserRadioButton( user ); add( urb ); if( ui.groupBox->layout()->count() <= 1 ) urb->click(); } else { QMessageBoxBuilder( this ) .setIcon( QMessageBox::Information ) .setTitle( tr( "Add User Error" ) ) .setText( tr( "This user has already been added." ) ) .exec(); } } void UserManagerWidget::onLoginContinueDialogAccepted() { m_loginProcess->getSession(); } void UserManagerWidget::onUserRemoved() { UserRadioButton* urb = qobject_cast<UserRadioButton*>(sender()); int result = QMessageBoxBuilder( parentWidget() ).setTitle( tr( "Removing %1" ).arg( urb->user() ) ) .setText( tr( "Are you sure you want to remove this user? All user settings will be lost and you will need to re authenticate in order to scrobble in the future." )) .setIcon( QMessageBox::Question ) .setButtons( QMessageBox::Yes | QMessageBox::No ) .exec(); if( result != QMessageBox::Yes ) return; unicorn::Settings settings; settings.beginGroup( "Users" ); settings.remove( urb->user() ); settings.endGroup(); if ( settings.userRoster().count() == 0 ) { settings.setFirstRunWizardCompleted( false ); qobject_cast<unicorn::Application*>( qApp )->restart(); } // if this is the currently logged in user we will have to choose someone else if( urb->ui.button->isChecked() ) { for ( int i = 0 ; i < ui.usersLayout->count() ; ++i ) { UserRadioButton* button = qobject_cast<UserRadioButton*>(ui.usersLayout->itemAt( i )->layout()); if ( button->user() != urb->user() ) { // This is the first user in the list that are not deleting! button->click(); unicorn::Settings s; s.setValue( "Username", button->user() ); unicorn::UserSettings us( button->user() ); QString sessionKey = us.value( "SessionKey", "" ).toString(); qobject_cast<unicorn::Application *>( qApp )->changeSession( button->user(), sessionKey, true ); break; } } } for ( int i = 0 ; i < layout()->count() ; ++i ) { QLayoutItem* item = layout()->itemAt( i ); if ( qobject_cast<UserRadioButton*>( item->widget() ) == urb ) { delete layout()->takeAt( i ); break; } } urb->deleteLater(); } void UserManagerWidget::add( UserRadioButton* urb, bool announce ) { ui.usersLayout->addLayout( urb ); if ( urb->user() == User().name() ) urb->click(); connect( urb, SIGNAL(remove()), SLOT(onUserRemoved())); connect( urb, SIGNAL( clicked() ), this, SIGNAL( userChanged() ) ); if ( announce ) emit rosterUpdated(); connect( urb, SIGNAL( destroyed(QObject*)), SIGNAL( rosterUpdated())); } UserRadioButton* UserManagerWidget::checkedButton() const { for ( int i = 0 ; i < ui.usersLayout->count() ; ++i ) { UserRadioButton* button = qobject_cast<UserRadioButton*>(ui.usersLayout->itemAt( i )->layout()); if ( button && button->isChecked() ) { return button; } } return 0; } ================================================ FILE: lib/unicorn/widgets/UserManagerWidget.h ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole, William Viana Soares and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef USER_MANAGER_WIDGET_H_ #define USER_MANAGER_WIDGET_H_ #include <QLayout> #include <QRadioButton> #include <QPointer> #include "lib/DllExportMacro.h" namespace lastfm{ class User; } namespace unicorn { class LoginProcess; class Session; } class LoginContinueDialog; class QAbstractButton; class QButtonGroup; class QFrame; class QLabel; class QPushButton; class QVBoxLayout; class UNICORN_DLLEXPORT UserRadioButton : public QHBoxLayout { Q_OBJECT friend class UserManagerWidget; public: UserRadioButton( const lastfm::User& user ); const QString user() const; void click(); bool isChecked() const; signals: void clicked(); void remove(); private: void setUser( const lastfm::User& user ); private slots: void onUserFetched(); void onSessionChanged( const unicorn::Session& session ); private: struct { QRadioButton* button; QPushButton* remove; QLabel* username; QLabel* realName; QLabel* loggedIn; class AvatarWidget* image; QFrame* frame; } ui; }; class UNICORN_DLLEXPORT UserManagerWidget : public QWidget { Q_OBJECT public: UserManagerWidget( QWidget* parent = 0 ); ~UserManagerWidget(); UserRadioButton* checkedButton() const; signals: void rosterUpdated(); void userChanged(); protected slots: void onAddUserClicked(); void onLoginDialogAccepted(); void onLoginContinueDialogAccepted(); void onUserRemoved(); protected: void add( UserRadioButton*, bool = true ); struct { class QGroupBox* groupBox; class QVBoxLayout* usersLayout; class QPushButton* addUserButton; } ui; private slots: void onLoginComplete( const unicorn::Session& session ); private: QPointer<unicorn::LoginProcess> m_loginProcess; QPointer<LoginContinueDialog> m_lcd; }; #endif //USER_MANAGER_WIDGET_H_ ================================================ FILE: lib/unicorn/widgets/UserMenu.cpp ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include <QDebug> #include <QTimer> #include <QDesktopServices> #include <lastfm/User.h> #include <lastfm/UrlBuilder.h> #include "lib/unicorn/UnicornApplication.h" #include "lib/unicorn/UnicornSettings.h" #include "lib/unicorn/DesktopServices.h" #include "UserMenu.h" UserMenu::UserMenu( QWidget* p ) :QMenu( p ) { connect( qApp, SIGNAL(rosterUpdated()), SLOT(refresh())); connect( qApp, SIGNAL(sessionChanged(unicorn::Session) ), SLOT( onSessionChanged( unicorn::Session) ) ); refresh(); } void UserMenu::onSessionChanged( const unicorn::Session& session ) { foreach( QAction* a, actions() ) if( a->text() == session.user().name() ) a->setChecked( true ); // show the subscribe link if they don't have radio and could get it by subscribing m_subscribe->setVisible( !session.youRadio() && session.subscriberRadio() ); } void UserMenu::onTriggered( QAction* a ) { unicorn::UserSettings us( a->text() ); QString username = a->text(); QString sessionKey = us.sessionKey(); QMetaObject::invokeMethod( qApp, "changeSession", Q_ARG( const QString, username ), Q_ARG( const QString, sessionKey ) ); //Refresh the user list to be certain that //the correct current user is checked. //(ie. the user change could be cancelled after confirmation.) refresh(); } void UserMenu::manageUsers() { //This is required so that the menu popup call can be completed //in the event loop before the manage users dialog is displayed. //(The manageUsers method may emit a signal which causes the menu // to be destroyed) QTimer::singleShot( 0, qApp, SLOT(manageUsers()) ); } void UserMenu::subscribe() { unicorn::DesktopServices::openUrl( lastfm::UrlBuilder( "subscribe" ).url() ); } void UserMenu::refresh() { clear(); m_subscribe = addAction( tr( "Subscribe" ), this, SLOT(subscribe())); addSeparator(); QActionGroup* ag = new QActionGroup( this ); foreach( User u, unicorn::Settings().userRoster() ) { QAction* a = ag->addAction( new QAction( u.name(), this )); addAction( a ); if( u == User()) a->setChecked( true ); a->setCheckable( true ); } ag->setExclusive( true ); connect( ag, SIGNAL(triggered(QAction*)), SLOT( onTriggered(QAction*))); onSessionChanged( qobject_cast<unicorn::Application*>(qApp)->currentSession() ); } ================================================ FILE: lib/unicorn/widgets/UserMenu.h ================================================ /* Copyright 2010-2012 Last.fm Ltd. - Primarily authored by Jono Cole and Michael Coffey This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef USER_MENU_H_ #define USER_MENU_H_ #include <QMenu> #include <QPointer> #include "lib/DllExportMacro.h" #include "lib/unicorn/UnicornSession.h" class UNICORN_DLLEXPORT UserMenu : public QMenu { Q_OBJECT public: UserMenu( QWidget* p = 0 ); protected slots: void onSessionChanged( const unicorn::Session& session ); void onTriggered( QAction* a ); void manageUsers(); void refresh(); void subscribe(); private: QPointer<QAction> m_subscribe; }; #endif ================================================ FILE: lib/unicorn/widgets/UserToolButton.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #include "UserToolButton.h" #include <QApplication> #include <QDebug> #include <QToolButton> #include <QPainter> #include <lastfm/User.h> #include "UserMenu.h" #include "../UnicornSettings.h" using namespace lastfm; UserToolButton::UserToolButton() { setMouseTracking( true ); setIconSize( QSize( 40, 40 )); setCheckable( true ); if( unicorn::Settings().userRoster().count() > 1 ) { setMenu( new UserMenu()); setPopupMode( QToolButton::MenuButtonPopup ); } connect( this, SIGNAL( toggled( bool )), window(), SLOT( toggleProfile( bool ))); connect( qApp, SIGNAL( sessionChanged(unicorn::Session)), SLOT( onSessionChanged(unicorn::Session))); connect( qApp, SIGNAL( gotUserInfo( lastfm::User )), SLOT( onUserGotInfo( lastfm::User ))); connect( qApp, SIGNAL( rosterUpdated()), SLOT( onRosterUpdated())); } void UserToolButton::onSessionChanged( const unicorn::Session& /*session*/ ) { setIcon( QIcon() ); } void UserToolButton::onUserGotInfo( const User& user ) { connect( lastfm::nam()->get(QNetworkRequest( user.imageUrl( lastfm::User::MediumImage))), SIGNAL( finished()), SLOT( onImageDownloaded())); } void UserToolButton::onImageDownloaded() { QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() ); Q_ASSERT( reply ); reply->deleteLater(); QPixmap pm; if( !pm.loadFromData( reply->readAll()) ) pm = QPixmap(":lastfm/default_user_small.png"); QPixmap on( ":lastfm/profile_on.png" ); QPixmap off( ":lastfm/profile_off.png" ); QPixmap hover( ":lastfm/profile_hover.png" ); QIcon icon; { QPainter p( &on ); p.setRenderHint( QPainter::SmoothPixmapTransform ); p.setCompositionMode( QPainter::CompositionMode_DestinationOver ); p.drawPixmap( on.rect().adjusted( 5, 5, -5, -5 ), pm, pm.rect()); } icon.addPixmap( on, QIcon::Normal, QIcon::On ); { QPainter p( &off ); p.setRenderHint( QPainter::SmoothPixmapTransform ); p.setCompositionMode( QPainter::CompositionMode_DestinationOver ); p.drawPixmap( off.rect().adjusted( 5, 5, -5, -5 ), pm, pm.rect()); } icon.addPixmap( off, QIcon::Normal, QIcon::Off ); { QPainter p( &hover ); p.setRenderHint( QPainter::SmoothPixmapTransform ); p.setCompositionMode( QPainter::CompositionMode_DestinationOver ); p.drawPixmap( hover.rect().adjusted( 5, 5, -5, -5 ), pm, pm.rect()); } icon.addPixmap( hover, QIcon::Active ); setIcon( icon ); } void UserToolButton::onRosterUpdated() { if( unicorn::Settings().userRoster().count() > 1 ) { setMenu( new UserMenu()); setPopupMode( QToolButton::MenuButtonPopup ); setStyle( QApplication::style()); } else { setPopupMode( QToolButton::DelayedPopup ); menu()->deleteLater(); setStyle( QApplication::style()); } } ================================================ FILE: lib/unicorn/widgets/UserToolButton.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Jono Cole and Doug Mansell This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef USER_TOOL_BUTTON_H #define USER_TOOL_BUTTON_H #include <QToolButton> #include "lib/DllExportMacro.h" namespace lastfm { class User; } namespace unicorn { class Session; } class UNICORN_DLLEXPORT UserToolButton : public QToolButton { Q_OBJECT public: UserToolButton(); protected slots: void onSessionChanged( const unicorn::Session& session ); void onUserGotInfo( const lastfm::User& user ); void onImageDownloaded(); void onRosterUpdated(); }; #endif //USER_TOOL_BUTTON_H ================================================ FILE: plugins/LFMRadio/PluginInfo.h ================================================ /* Copyright 2010 Last.fm Ltd. - Primarily authored by Jono Cole This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see <http://www.gnu.org/licenses/>. */ #ifndef LFM_PLUGIN_INFO_H_ #define LFM_PLUGIN_INFO_H_ #include "../../lib/unicorn/Updater/IPluginInfo.h" #include "../../lib/DllExportMacro.h" class UNICORN_DLLEXPORT LFMRadioPluginInfo : public IPluginInfo { public: std::string name() const { return "Last.fm Radio"; } Version minVersion() const { return Version(); } Version maxVersion() const { return Version(); } std::string pluginPath() const { return std::string( "" ); } std::string displayName() const { return std::string( "" ); } std::string processName() const { return std::string( "radio.exe" ); } std::string id() const { return "ass"; } BootstrapType bootstrapType() const { return NoBootstrap; } bool isPlatformSupported() const { return true; } bool isAppInstalled() const { return true; } bool isInstalled() const { return true; } #ifdef QT_VERSION std::tstring pluginInstallPath() const { return std::tstring(); } #endif }; #endif //LFM_PLUGIN_INFO_H_ ================================================ FILE: plugins/foobar08/audioscrobbler.cpp ================================================ /* This file is part of the Audioscrobbler component for Foobar2000. This component is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This component is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this source; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Copyright 2006-2009 Last.fm Ltd. */ #include <ScrobSubmitter.h> #include "../SDK/foobar2000.h" #include "../SDK/play_callback.h" #include "../SDK/console.h" #include "../SDK/metadb.h" #include "../SDK/initquit.h" #include "../SDK/componentversion.h" #include "../SDK/file_info_helper.h" #define coalesce(a) (a!=NULL)?a:"" CScrobSubmitter *scrobbler; class play_callback_jump : public play_callback { virtual void on_playback_starting() {} virtual void on_playback_new_track(metadb_handle * track) { file_info_i_full info; track->handle_query(&info); try { scrobbler->Start(std::string(coalesce(info.meta_get("artist"))), std::string(coalesce(info.meta_get("title"))), std::string(coalesce(info.meta_get("album"))), std::string(coalesce(info.meta_get("MUSICBRAINZ_TRACKID"))), info.get_length(), std::string(coalesce(info.get_file_path())) ); } catch (CScrobSubmitter::CAudioScrobblerException& e) { console::warning(e.what()); } } virtual void on_playback_stop(play_control::stop_reason reason) { try { scrobbler->Stop(); } catch (CScrobSubmitter::CAudioScrobblerException& e) { console::warning(e.what()); } } virtual void on_playback_pause(int state) { try { if (state) { // pausing scrobbler->Pause(); } else { // unpausing scrobbler->Resume(); } } catch (CScrobSubmitter::CAudioScrobblerException& e) { console::warning(e.what()); } } virtual void on_playback_seek(double time) {} virtual void on_playback_edited(metadb_handle * track) {} virtual void on_playback_dynamic_info(const file_info * info,bool b_track_change) {} }; class initquit_audioscrobbler : public initquit { public: void on_init() { scrobbler = new CScrobSubmitter(); try { scrobbler->Init("foo"); } catch (CScrobSubmitter::CAudioScrobblerException& e) { console::warning(e.what()); } console::info("Audioscrobbler loaded"); } void on_quit() { delete scrobbler; } }; static service_factory_single_t<play_callback, play_callback_jump> foo; static service_factory_single_t<initquit, initquit_audioscrobbler> bar; DECLARE_COMPONENT_VERSION("Audioscrobbler", "2.0", "http://www.last.fm"); ================================================ FILE: plugins/foobar08/foo_install.iss ================================================ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! [CustomMessages] Version=2.0.2 [Setup] OutputBaseFilename=FooPlugin0.8Setup_2.0.2 ; setup.exe version VersionInfoVersion=0.9.1.0 VersionInfoTextVersion=0.9.1.0 AppName=Audioscrobbler foobar2000 0.8 Plugin AppVerName=Audioscrobbler foobar2000 0.8 Plugin {cm:Version} VersionInfoDescription=Audioscrobbler foobar2000 0.8 Plugin Installer AppPublisher=Last.fm AppPublisherURL=http://www.last.fm AppSupportURL=http://www.last.fm AppUpdatesURL=http://www.last.fm AppCopyright=Copyright Last.fm Ltd. DefaultDirName="{pf}\foobar2000\components" UsePreviousAppDir=yes UninstallFilesDir={localappdata}\Last.fm\Audioscrobbler\UninstFoo OutputDir="." Compression=lzma SolidCompression=yes DirExistsWarning=no DisableReadyPage=yes ; Keep this the same across versions, even if they're incompatible. That will ensure ; uninstallation works fine after many upgrades. AppId=Audioscrobbler foobar2000 Plugin CreateUninstallRegKey=no [Registry] ; The name of the final subkey here must match the one in plugins.data Root: HKCU; Subkey: "Software\Last.fm\Audioscrobbler\Plugins\foobar2000 0.8"; ValueType: string; ValueName: "Version"; ValueData: "{cm:Version}"; Flags: uninsdeletekey [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [InstallDelete] Type: files; Name: "{app}\foo_audioscrobbler.dll" [UninstallDelete] ;Type: files; Name: "{app}\AudioScrobbler.log.txt" [Files] Source: "foo_audioscrobbler.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "tools\append_once.bat"; DestDir: "{tmp}" ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Run] ; Add uninstaller exe path to AS uninst script Filename: "{tmp}\append_once.bat"; Parameters: """{uninstallexe}"" ""{localappdata}\Last.fm\Audioscrobbler\uninst2.bat"""; Flags: runhidden ================================================ FILE: plugins/foobar08/tools/append_once.bat ================================================ REM %1 - string to append REM %2 - file to append it to find /C /I %1 %2 if not errorlevel 1 goto end echo start /WAIT "" %1 /SILENT >> %2 :end ================================================ FILE: plugins/foobar09/ChangeLog.txt ================================================ 20/8/12 2.3.1.3 ------------------------------------------------------------------------------- * Now sends album artist as parameter d in scrob sub protocol. 13/5/08 2.3.1.2 ------------------------------------------------------------------------------- * Updates broken Updater.exe in client if installed. 16/1/08 2.3.1.1 ------------------------------------------------------------------------------- * Fix installer package to actually contain 2.3.1 and not 2.2! 9/1/08 2.3.1.0 ------------------------------------------------------------------------------- * Make Scrobsubmitter global instead of heap-allocated to see if removing the delete helps fixing crashes. * Only print to console on errors. * Only send Stop when playback stops. 31/7/07 2.3.0.0 ------------------------------------------------------------------------------- * Recompiled with new ScrobSub for Vista compatibility. ================================================ FILE: plugins/foobar09/audioscrobbler.cpp ================================================ /* This file is part of the Audioscrobbler component for Foobar2000. This component is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This component is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this source; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Copyright 2007-2009 Last.fm Ltd. */ #include <ScrobSubmitter.h> #include "SDK/foobar2000.h" #include "SDK/play_callback.h" #include "SDK/console.h" #include "SDK/metadb.h" #include "SDK/initquit.h" #include "SDK/componentversion.h" #define coalesce(a) (a!=NULL)?a:"" ScrobSubmitter scrobbler; class play_callback_jump : public play_callback_static { virtual unsigned int get_flags() { return flag_on_playback_new_track | flag_on_playback_stop | flag_on_playback_pause; } virtual void on_playback_starting(play_control::t_track_command p_command, bool p_paused) {} virtual void on_playback_new_track(metadb_handle_ptr track) { file_info_impl info; track->get_info(info); std::string path = coalesce(track->get_path()); if (path.length() > 7 && path[0] == 'f' && path[1] == 'i' && path[2] == 'l' && path[3] == 'e' && path[4] == ':' && path[5] == '/' && path[6] == '/') { path = path.substr(7, path.length() - 1); } scrobbler.Start(std::string(coalesce(info.meta_get("artist", 0))), std::string(coalesce(info.meta_get("album artist", 0))), std::string(coalesce(info.meta_get("title", 0))), std::string(coalesce(info.meta_get("album", 0))), std::string(coalesce(info.meta_get("MUSICBRAINZ_TRACKID", 0))), (int)info.get_length(), path); } virtual void on_playback_stop(play_control::t_stop_reason reason) { if (reason != play_control::stop_reason_starting_another) { scrobbler.Stop(); } } virtual void on_playback_pause(bool state) { if (state) { // pausing scrobbler.Pause(); } else { // unpausing scrobbler.Resume(); } } virtual void on_playback_seek(double time) {} virtual void on_playback_edited(metadb_handle_ptr p_track) {} virtual void on_playback_dynamic_info(const file_info & info) {} virtual void on_playback_dynamic_info_track(const file_info & info) {} virtual void on_playback_time(double p_time) {} virtual void on_volume_change(float p_new_val) {}; }; void statusCallback (int reqId, bool error, std::string msg, void* userData) { if (error) { console::info(pfc::string8("Audioscrobbler: ") << msg.c_str()); } } class initquit_audioscrobbler : public initquit { public: void on_init() { scrobbler.Init("foo", &statusCallback, 0); console::info("Audioscrobbler: Loaded"); } void on_quit() { scrobbler.Stop(); scrobbler.Term(); } }; static play_callback_static_factory_t<play_callback_jump> foo; static initquit_factory_t<initquit_audioscrobbler> bar; DECLARE_COMPONENT_VERSION("Audioscrobbler", "2.3.1", "http://www.last.fm"); ================================================ FILE: plugins/foobar09/foo_audioscrobbler.rc ================================================ // Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.K.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 2,3,1,3 PRODUCTVERSION 2,3,1,3 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080904b0" BEGIN VALUE "Comments", "http://www.last.fm" VALUE "CompanyName", "Last.fm" VALUE "FileDescription", "Last.fm foobar2000 0.9.4 plugin" VALUE "FileVersion", "2, 3, 1, 3" VALUE "InternalName", "foo_audioscrobbler" VALUE "LegalCopyright", "Copyright (C) 2007" VALUE "OriginalFilename", "foo_audioscrobbler.dll" VALUE "ProductName", "Last.fm foobar2000 0.9.4 plugin" VALUE "ProductVersion", "2, 3, 1, 3" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x809, 1200 END END #endif // English (U.K.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: plugins/foobar09/foo_install.iss ================================================ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! [CustomMessages] Version=2.3.1.3 [Setup] OutputBaseFilename=FooPlugin0.9.4Setup_2.3.1.3 ; setup.exe version VersionInfoVersion=2.3.1.3 VersionInfoTextVersion=2.3.1.3 AppName=Last.fm foobar2000 0.9.4 Plugin AppVerName=Last.fm foobar2000 0.9.4 Plugin {cm:Version} VersionInfoDescription=Last.fm foobar2000 0.9.4 Plugin Installer AppPublisher=Last.fm AppPublisherURL=http://www.last.fm AppSupportURL=http://www.last.fm AppUpdatesURL=http://www.last.fm AppCopyright=Copyright Ltd (c) DefaultDirName="{pf}\foobar2000\components" UsePreviousAppDir=yes UninstallFilesDir={commonappdata}\Last.fm\Client\UninstFoo3 OutputDir="." Compression=lzma SolidCompression=yes DirExistsWarning=no DisableReadyPage=yes ; Keep this the same across versions, even if they're incompatible. That will ensure ; uninstallation works fine after many upgrades. AppId=Audioscrobbler foobar2000 Plugin CreateUninstallRegKey=no [Registry] ; The name of the final subkey here must match the one in plugins.data Root: HKLM; Subkey: "Software\Last.fm\Client\Plugins\foo3"; ValueType: string; ValueName: "Version"; ValueData: "{cm:Version}"; Flags: uninsdeletekey Root: HKLM; Subkey: "Software\Last.fm\Client\Plugins\foo3"; ValueType: string; ValueName: "Name"; ValueData: "foobar2000 0.9.4"; Flags: uninsdeletekey [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [InstallDelete] Type: files; Name: "{app}\foo_audioscrobbler.dll" [UninstallDelete] ;Type: files; Name: "{app}\AudioScrobbler.log.txt" [Files] Source: "release\foo_audioscrobbler.dll"; DestDir: "{app}"; Flags: ignoreversion [Run] [Code] procedure CurStepChanged(CurStep: TSetupStep); var batfile: String; batcontent: String; uninstaller: String; alreadyAdded: Boolean; cmdToAdd: String; begin if (CurStep = ssPostInstall) then begin //MsgBox('postinstall', mbInformation, MB_OK); batfile := ExpandConstant('{commonappdata}\Last.fm\Client\uninst2.bat'); LoadStringFromFile(batfile, batcontent); //MsgBox('loaded string: ' + batcontent, mbInformation, MB_OK); uninstaller := ExpandConstant('{uninstallexe}'); //MsgBox('uninstaller pre-OEM: ' + uninstaller, mbInformation, MB_OK); alreadyAdded := (Pos(uninstaller, batcontent) <> 0); if (alreadyAdded = False) then begin cmdToAdd := uninstaller + #13#10; //MsgBox('not present, will add: ' + cmdToAdd, mbInformation, MB_OK); SaveStringToFile(batfile, cmdToAdd, True) end; end; end; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/abort_callback.cpp ================================================ #include "foobar2000.h" void abort_callback::check() const { if (is_aborting()) throw exception_aborted(); } void abort_callback::sleep(double p_timeout_seconds) const { if (!sleep_ex(p_timeout_seconds)) throw exception_aborted(); } bool abort_callback::sleep_ex(double p_timeout_seconds) const { #ifdef _WIN32 return !win32_event::g_wait_for(get_abort_event(),p_timeout_seconds); #else #error PORTME #endif } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/abort_callback.h ================================================ #ifndef _foobar2000_sdk_abort_callback_h_ #define _foobar2000_sdk_abort_callback_h_ namespace foobar2000_io { PFC_DECLARE_EXCEPTION(exception_aborted,pfc::exception,"User abort"); #ifdef _WIN32 typedef HANDLE abort_callback_event; #else #error PORTME #endif //! This class is used to signal underlying worker code whether user has decided to abort a potentially time-consuming operation. It is commonly required by all file related operations. Code that receives an abort_callback object should periodically check it and abort any operations being performed if it is signaled, typically giving io_result_aborted return code (see: t_io_result). \n //! See abort_callback_impl for implementation. class NOVTABLE abort_callback { public: //! Returns whether user has requested the operation to be aborted. virtual bool is_aborting() const = 0; //! Retrieves event object that can be used with some OS calls. The even object becomes signaled when abort is triggered. On win32, this is equivalent to win32 event handle (see: CreateEvent). virtual abort_callback_event get_abort_event() const = 0; //! Checks if user has requested the operation to be aborted, and throws exception_aborted if so. void check() const; //! For compatibility with old code. inline void check_e() const {check();} //! Sleeps p_timeout_seconds or less when aborted, throws exception_aborted on abort. void sleep(double p_timeout_seconds) const; //! Sleeps p_timeout_seconds or less when aborted, returns true when execution should continue, false when not. bool sleep_ex(double p_timeout_seconds) const; protected: abort_callback() {} ~abort_callback() {} }; //! Implementation of abort_callback interface. class abort_callback_impl : public abort_callback { public: abort_callback_impl() : m_aborting(false) { m_event.create(true,false); } inline void abort() {set_state(true);} inline void reset() {set_state(false);} void set_state(bool p_state) {m_aborting = p_state; m_event.set_state(p_state);} bool is_aborting() const {return m_aborting;} abort_callback_event get_abort_event() const {return m_event.get();} private: abort_callback_impl(const abort_callback_impl &) {throw pfc::exception_not_implemented();} const abort_callback_impl & operator=(const abort_callback_impl&) {throw pfc::exception_not_implemented();} volatile bool m_aborting; #ifdef WIN32 win32_event m_event; #endif }; } using namespace foobar2000_io; #endif //_foobar2000_sdk_abort_callback_h_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/advconfig.h ================================================ #ifndef _FOOBAR2000_SDK_ADVCONFIG_H_ #define _FOOBAR2000_SDK_ADVCONFIG_H_ class advconfig_entry : public service_base { public: virtual void get_name(pfc::string_base & p_out) = 0; virtual GUID get_guid() = 0; virtual GUID get_parent() = 0; virtual void reset() = 0; virtual double get_sort_priority() = 0; static const GUID guid_root; static const GUID guid_branch_tagging,guid_branch_decoding,guid_branch_tools,guid_branch_playback,guid_branch_display; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(advconfig_entry); }; class advconfig_branch : public advconfig_entry { public: FB2K_MAKE_SERVICE_INTERFACE(advconfig_branch,advconfig_entry); }; class advconfig_entry_checkbox : public advconfig_entry { public: virtual bool get_state() = 0; virtual void set_state(bool p_state) = 0; virtual bool is_radio() = 0; FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_checkbox,advconfig_entry); }; class advconfig_entry_string : public advconfig_entry { public: virtual void get_state(pfc::string_base & p_out) = 0; virtual void set_state(const char * p_string,t_size p_length = infinite) = 0; virtual t_uint32 get_flags() = 0; enum { flag_is_integer = 1 << 0, flag_is_signed = 1 << 1, }; FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_string,advconfig_entry); }; class advconfig_branch_impl : public advconfig_branch { public: advconfig_branch_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority) : m_name(p_name), m_guid(p_guid), m_parent(p_parent), m_priority(p_priority) {} void get_name(pfc::string_base & p_out) {p_out = m_name;} GUID get_guid() {return m_guid;} GUID get_parent() {return m_parent;} void reset() {} double get_sort_priority() {return m_priority;} private: pfc::string8 m_name; GUID m_guid,m_parent; const double m_priority; }; template<bool p_is_radio = false> class advconfig_entry_checkbox_impl : public advconfig_entry_checkbox { public: advconfig_entry_checkbox_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,bool p_initialstate) : m_name(p_name), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate), m_parent(p_parent), m_priority(p_priority) {} void get_name(pfc::string_base & p_out) {p_out = m_name;} GUID get_guid() {return m_state.get_guid();} GUID get_parent() {return m_parent;} void reset() {m_state = m_initialstate;} bool get_state() {return m_state;} void set_state(bool p_state) {m_state = p_state;} bool is_radio() {return p_is_radio;} double get_sort_priority() {return m_priority;} private: pfc::string8 m_name; const bool m_initialstate; cfg_bool m_state; GUID m_parent; const double m_priority; }; class advconfig_branch_factory : public service_factory_single_t<advconfig_branch_impl> { public: advconfig_branch_factory(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority) : service_factory_single_t<advconfig_branch_impl>(p_name,p_guid,p_parent,p_priority) {} }; template<bool p_is_radio> class advconfig_checkbox_factory_t : public service_factory_single_t<advconfig_entry_checkbox_impl<p_is_radio> > { public: advconfig_checkbox_factory_t(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,bool p_initialstate) : service_factory_single_t<advconfig_entry_checkbox_impl<p_is_radio> >(p_name,p_guid,p_parent,p_priority,p_initialstate) {} }; typedef advconfig_checkbox_factory_t<false> advconfig_checkbox_factory; typedef advconfig_checkbox_factory_t<true> advconfig_radio_factory; class advconfig_entry_string_impl : public advconfig_entry_string { public: advconfig_entry_string_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate) : m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate) {} void get_name(pfc::string_base & p_out) {p_out = m_name;}//{p_out = pfc::string_formatter() << m_name << " : " << m_state;} GUID get_guid() {return m_state.get_guid();} GUID get_parent() {return m_parent;} void reset() {core_api::ensure_main_thread();m_state = m_initialstate;} double get_sort_priority() {return m_priority;} void get_state(pfc::string_base & p_out) {core_api::ensure_main_thread();p_out = m_state;} void set_state(const char * p_string,t_size p_length = infinite) {core_api::ensure_main_thread();m_state.set_string(p_string,p_length);} t_uint32 get_flags() {return 0;} private: const pfc::string8 m_initialstate, m_name; cfg_string m_state; const double m_priority; const GUID m_parent; }; class advconfig_string_factory : public service_factory_single_t<advconfig_entry_string_impl> { public: advconfig_string_factory(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate) : service_factory_single_t<advconfig_entry_string_impl>(p_name,p_guid,p_parent,p_priority,p_initialstate) {} }; class advconfig_entry_integer_impl : public advconfig_entry_string { public: advconfig_entry_integer_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,t_uint64 p_initialstate,t_uint64 p_min,t_uint64 p_max) : m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initval(p_initialstate), m_min(p_min), m_max(p_max), m_state(p_guid,p_initialstate) {} void get_name(pfc::string_base & p_out) {p_out = m_name;} GUID get_guid() {return m_state.get_guid();} GUID get_parent() {return m_parent;} void reset() {m_state = m_initval;} double get_sort_priority() {return m_priority;} void get_state(pfc::string_base & p_out) {p_out = pfc::format_uint(m_state.get_value());} void set_state(const char * p_string,t_size p_length) {m_state = pfc::clip_t<t_uint64>(pfc::atoui64_ex(p_string,p_length),m_min,m_max);} t_uint32 get_flags() {return advconfig_entry_string::flag_is_integer;} t_uint64 get_state_int() const {return m_state;} private: cfg_int_t<t_uint64> m_state; const double m_priority; const t_uint64 m_initval, m_min, m_max; const GUID m_parent; const pfc::string8 m_name; }; class advconfig_integer_factory : public service_factory_single_t<advconfig_entry_integer_impl> { public: advconfig_integer_factory(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,t_uint64 p_initialstate,t_uint64 p_min,t_uint64 p_max) : service_factory_single_t<advconfig_entry_integer_impl>(p_name,p_guid,p_parent,p_priority,p_initialstate,p_min,p_max) {} }; class advconfig_entry_enum : public advconfig_entry { public: virtual t_size get_value_count() = 0; virtual void enum_value(pfc::string_base & p_out,t_size p_index) = 0; virtual t_size get_state() = 0; virtual void set_state(t_size p_value) = 0; FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_enum,advconfig_entry); }; #endif //_FOOBAR2000_SDK_ADVCONFIG_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/app_close_blocker.cpp ================================================ #include "foobar2000.h" bool app_close_blocker::g_query() { service_ptr_t<app_close_blocker> ptr; service_enum_t<app_close_blocker> e; while(e.next(ptr)) { if (!ptr->query()) return false; } return true; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/app_close_blocker.h ================================================ #ifndef _APP_CLOSE_BLOCKER_H_ #define _APP_CLOSE_BLOCKER_H_ //! This service is used to signal whether something is currently preventing main window from being closed and app from being shut down. class NOVTABLE app_close_blocker : public service_base { public: //! Checks whether this service is currently preventing main window from being closed and app from being shut down. virtual bool query() = 0; //! Static helper function, checks whether any of registered app_close_blocker services is currently preventing main window from being closed and app from being shut down. static bool g_query(); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(app_close_blocker); }; #endif //_APP_CLOSE_BLOCKER_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/audio_chunk.cpp ================================================ #include "foobar2000.h" void audio_chunk::set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config) { t_size size = samples * nch; set_data_size(size); if (src) pfc::memcpy_t(get_data(),src,size); else pfc::memset_t(get_data(),(audio_sample)0,size); set_sample_count(samples); set_channels(nch,channel_config); set_srate(srate); } static bool check_exclusive(unsigned val, unsigned mask) { return (val&mask)!=0 && (val&mask)!=mask; } namespace { template<class T,bool b_swap,bool b_signed,bool b_pad> class msvc6_sucks_v2 { public: inline static void do_fixedpoint_convert(const void * source,unsigned bps,t_size count,audio_sample* buffer) { const char * src = (const char *) source; unsigned bytes = bps>>3; t_size n; T max = ((T)1)<<(bps-1); T negmask = - max; ASSUME(bytes<=sizeof(T)); const double div = 1.0 / (double)(1<<(bps-1)); for(n=0;n<count;n++) { T temp; if (b_pad) { temp = 0; memcpy(&temp,src,bytes); if (b_swap) pfc::byteswap_raw(&temp,bytes); } else { temp = * reinterpret_cast<const T*>(src); if (b_swap) temp = pfc::byteswap_t(temp); } if (!b_signed) temp ^= max; if (b_pad) { if (temp & max) temp |= negmask; } if (b_pad) src += bytes; else src += sizeof(T); buffer[n] = (audio_sample) ( (double)temp * div ); } } }; template <class T,bool b_pad> class msvc6_sucks { public: inline static void do_fixedpoint_convert(bool b_swap,bool b_signed,const void * source,unsigned bps,t_size count,audio_sample* buffer) { if (sizeof(T)==1) { if (b_signed) { msvc6_sucks_v2<T,false,true,b_pad>::do_fixedpoint_convert(source,bps,count,buffer); } else { msvc6_sucks_v2<T,false,false,b_pad>::do_fixedpoint_convert(source,bps,count,buffer); } } else if (b_swap) { if (b_signed) { msvc6_sucks_v2<T,true,true,b_pad>::do_fixedpoint_convert(source,bps,count,buffer); } else { msvc6_sucks_v2<T,true,false,b_pad>::do_fixedpoint_convert(source,bps,count,buffer); } } else { if (b_signed) { msvc6_sucks_v2<T,false,true,b_pad>::do_fixedpoint_convert(source,bps,count,buffer); } else { msvc6_sucks_v2<T,false,false,b_pad>::do_fixedpoint_convert(source,bps,count,buffer); } } } }; }; void audio_chunk::set_data_fixedpoint_ex(const void * source,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config) { assert( check_exclusive(flags,FLAG_SIGNED|FLAG_UNSIGNED) ); assert( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) ); bool need_swap = !!(flags & FLAG_BIG_ENDIAN); if (pfc::byte_order_is_big_endian) need_swap = !need_swap; t_size count = size / (bps/8); set_data_size(count); audio_sample * buffer = get_data(); bool b_signed = !!(flags & FLAG_SIGNED); switch(bps) { case 8: msvc6_sucks<t_int8,false>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer); break; case 16: if (!need_swap && b_signed) audio_math::convert_from_int16((const t_int16*)source,count,buffer,1.0); else msvc6_sucks<t_int16,false>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer); break; case 24: msvc6_sucks<t_int32,true>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer); break; case 32: if (!need_swap && b_signed) audio_math::convert_from_int32((const t_int32*)source,count,buffer,1.0); else msvc6_sucks<t_int32,false>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer); break; default: //unknown size, cant convert pfc::memset_t(buffer,(audio_sample)0,count); break; } set_sample_count(count/nch); set_srate(srate); set_channels(nch,p_channel_config); } template<class t_float> static void process_float_multi(audio_sample * p_out,const t_float * p_in,const t_size p_count) { t_size n; for(n=0;n<p_count;n++) p_out[n] = (audio_sample)p_in[n]; } template<class t_float> static void process_float_multi_swap(audio_sample * p_out,const t_float * p_in,const t_size p_count) { t_size n; for(n=0;n<p_count;n++) { p_out[n] = (audio_sample) pfc::byteswap_t(p_in[n]); } } void audio_chunk::set_data_floatingpoint_ex(const void * ptr,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config) { assert(bps==32 || bps==64); assert( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) ); assert( ! (flags & (FLAG_SIGNED|FLAG_UNSIGNED) ) ); bool use_swap = pfc::byte_order_is_big_endian ? !!(flags & FLAG_LITTLE_ENDIAN) : !!(flags & FLAG_BIG_ENDIAN); const t_size count = size / (bps/8); set_data_size(count); audio_sample * out = get_data(); if (bps == 32) { if (use_swap) process_float_multi_swap(out,reinterpret_cast<const float*>(ptr),count); else process_float_multi(out,reinterpret_cast<const float*>(ptr),count); } else if (bps == 64) { if (use_swap) process_float_multi_swap(out,reinterpret_cast<const double*>(ptr),count); else process_float_multi(out,reinterpret_cast<const double*>(ptr),count); } else throw exception_io_data("invalid bit depth"); set_sample_count(count/nch); set_srate(srate); set_channels(nch,p_channel_config); } bool audio_chunk::is_valid() const { unsigned nch = get_channels(); if (nch==0 || nch>256) return false; unsigned srate = get_srate(); if (srate<1000 || srate>1000000) return false; t_size samples = get_sample_count(); if (samples==0 || samples >= 0x80000000 / (sizeof(audio_sample) * nch) ) return false; t_size size = get_data_size(); if (samples * nch > size) return false; if (!get_data()) return false; return true; } void audio_chunk::pad_with_silence_ex(t_size samples,unsigned hint_nch,unsigned hint_srate) { if (is_empty()) { if (hint_srate && hint_nch) { return set_data(0,samples,hint_nch,hint_srate); } else throw exception_io_data(); } else { if (hint_srate && hint_srate != get_srate()) samples = MulDiv_Size(samples,get_srate(),hint_srate); if (samples > get_sample_count()) { t_size old_size = get_sample_count() * get_channels(); t_size new_size = samples * get_channels(); set_data_size(new_size); pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size); set_sample_count(samples); } } } void audio_chunk::pad_with_silence(t_size samples) { if (samples > get_sample_count()) { t_size old_size = get_sample_count() * get_channels(); t_size new_size = samples * get_channels(); set_data_size(new_size); pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size); set_sample_count(samples); } } void audio_chunk::insert_silence_fromstart(t_size samples) { t_size old_size = get_sample_count() * get_channels(); t_size delta = samples * get_channels(); t_size new_size = old_size + delta; set_data_size(new_size); audio_sample * ptr = get_data(); pfc::memmove_t(ptr+delta,ptr,old_size); pfc::memset_t(ptr,(audio_sample)0,delta); set_sample_count(get_sample_count() + samples); } t_size audio_chunk::skip_first_samples(t_size samples_delta) { t_size samples_old = get_sample_count(); if (samples_delta >= samples_old) { set_sample_count(0); set_data_size(0); return samples_old; } else { t_size samples_new = samples_old - samples_delta; unsigned nch = get_channels(); audio_sample * ptr = get_data(); pfc::memmove_t(ptr,ptr+nch*samples_delta,nch*samples_new); set_sample_count(samples_new); set_data_size(nch*samples_new); return samples_delta; } } audio_sample audio_chunk::get_peak(audio_sample peak) const { return pfc::max_t<audio_sample>(peak,audio_math::calculate_peak(get_data(),get_sample_count() * get_channels() )); } void audio_chunk::scale(audio_sample p_value) { audio_sample * ptr = get_data(); audio_math::scale(ptr,get_sample_count() * get_channels(),ptr,p_value); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/audio_chunk.h ================================================ #ifndef _AUDIO_CHUNK_H_ #define _AUDIO_CHUNK_H_ //! Interface to container of a chunk of audio data. See audio_chunk_impl for an implementation. class NOVTABLE audio_chunk { public: //! Channel map flag declarations. Note that order of interleaved channel data in the stream is same as order of these flags. enum { channel_front_left = 1<<0, channel_front_right = 1<<1, channel_front_center = 1<<2, channel_lfe = 1<<3, channel_back_left = 1<<4, channel_back_right = 1<<5, channel_front_center_left = 1<<6, channel_front_center_right = 1<<7, channel_back_center = 1<<8, channel_side_left = 1<<9, channel_side_right = 1<<10, channel_top_center = 1<<11, channel_top_front_left = 1<<12, channel_top_front_center = 1<<13, channel_top_front_right = 1<<14, channel_top_back_left = 1<<15, channel_top_back_center = 1<<16, channel_top_back_right = 1<<17, channel_config_mono = channel_front_center, channel_config_stereo = channel_front_left | channel_front_right, channel_config_5point1 = channel_front_left | channel_front_right | channel_back_left | channel_back_right | channel_front_center | channel_lfe, defined_channel_count = 18, }; //! Helper function; guesses default channel map for specified channel count. static unsigned g_guess_channel_config(unsigned count); #ifdef _WIN32 //! Helper function; translates audio_chunk channel map to WAVEFORMATEXTENSIBLE channel map. static DWORD g_channel_config_to_wfx(unsigned p_config); //! Helper function; translates WAVEFORMATEXTENSIBLE channel map to audio_chunk channel map. static unsigned g_channel_config_from_wfx(DWORD p_wfx); #endif //! Extracts flag describing Nth channel from specified map. Usable to figure what specific channel in a stream means. static unsigned g_extract_channel_flag(unsigned p_config,unsigned p_index); //! Counts channels specified by channel map. static unsigned g_count_channels(unsigned p_config); //! Calculates index of a channel specified by p_flag in a stream where channel map is described by p_config. static unsigned g_channel_index_from_flag(unsigned p_config,unsigned p_flag); //! Retrieves audio data buffer pointer (non-const version). Returned pointer is for temporary use only; it is valid until next set_data_size call, or until the object is destroyed. \n //! Size of returned buffer is equal to get_data_size() return value (in audio_samples). Amount of actual data may be smaller, depending on sample count and channel count. Conditions where sample count * channel count are greater than data size should not be possible. virtual audio_sample * get_data() = 0; //! Retrieves audio data buffer pointer (const version). Returned pointer is for temporary use only; it is valid until next set_data_size call, or until the object is destroyed. \n //! Size of returned buffer is equal to get_data_size() return value (in audio_samples). Amount of actual data may be smaller, depending on sample count and channel count. Conditions where sample count * channel count are greater than data size should not be possible. virtual const audio_sample * get_data() const = 0; //! Retrieves size of allocated buffer space, in audio_samples. virtual t_size get_data_size() const = 0; //! Resizes audio data buffer to specified size. Throws std::bad_alloc on failure. virtual void set_data_size(t_size p_new_size) = 0; //! Retrieves sample rate of contained audio data. virtual unsigned get_srate() const = 0; //! Sets sample rate of contained audio data. virtual void set_srate(unsigned val) = 0; //! Retrieves channel count of contained audio data. virtual unsigned get_channels() const = 0; //! Retrieves channel map of contained audio data. Conditions where number of channels specified by channel map don't match get_channels() return value should not be possible. virtual unsigned get_channel_config() const = 0; //! Sets channel count / channel map. virtual void set_channels(unsigned p_count,unsigned p_config) = 0; //! Retrieves number of valid samples in the buffer. \n //! Note that a "sample" means a unit of interleaved PCM data representing states of each channel at given point of time, not a single PCM value. \n //! For an example, duration of contained audio data is equal to sample count / sample rate, while actual size of contained data is equal to sample count * channel count. virtual t_size get_sample_count() const = 0; //! Sets number of valid samples in the buffer. WARNING: sample count * channel count should never be above allocated buffer size. virtual void set_sample_count(t_size val) = 0; //! Helper, same as get_srate(). inline unsigned get_sample_rate() const {return get_srate();} //! Helper, same as set_srate(). inline void set_sample_rate(unsigned val) {set_srate(val);} //! Helper; sets channel count to specified value and uses default channel map for this channel count. void set_channels(unsigned val) {set_channels(val,g_guess_channel_config(val));} //! Helper; resizes audio data buffer when it's current size is smaller than requested. inline void grow_data_size(t_size p_requested) {if (p_requested > get_data_size()) set_data_size(p_requested);} //! Retrieves duration of contained audio data, in seconds. inline double get_duration() const { double rv = 0; t_size srate = get_srate (), samples = get_sample_count(); if (srate>0 && samples>0) rv = (double)samples/(double)srate; return rv; } //! Returns whether the chunk is empty (contains no audio data). inline bool is_empty() const {return get_channels()==0 || get_srate()==0 || get_sample_count()==0;} //! Returns whether the chunk contents are valid (for bug check purposes). bool is_valid() const; //! Returns actual amount of audio data contained in the buffer (sample count * channel count). Must not be greater than data size (see get_data_size()). inline t_size get_data_length() const {return get_sample_count() * get_channels();} //! Resets all audio_chunk data. inline void reset() { set_sample_count(0); set_srate(0); set_channels(0); set_data_size(0); } //! Helper, sets chunk data to contents of specified buffer, with specified number of channels / sample rate / channel map. void set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config); //! Helper, sets chunk data to contents of specified buffer, with specified number of channels / sample rate, using default channel map for specified channel count. inline void set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate) {set_data(src,samples,nch,srate,g_guess_channel_config(nch));} //! Helper, sets chunk data to contents of specified buffer, using default win32/wav conventions for signed/unsigned switch. inline void set_data_fixedpoint(const void * ptr,t_size bytes,unsigned srate,unsigned nch,unsigned bps,unsigned channel_config) { set_data_fixedpoint_ex(ptr,bytes,srate,nch,bps,(bps==8 ? FLAG_UNSIGNED : FLAG_SIGNED) | flags_autoendian(), channel_config); } inline void set_data_fixedpoint_unsigned(const void * ptr,t_size bytes,unsigned srate,unsigned nch,unsigned bps,unsigned channel_config) { return set_data_fixedpoint_ex(ptr,bytes,srate,nch,bps,FLAG_UNSIGNED | flags_autoendian(), channel_config); } inline void set_data_fixedpoint_signed(const void * ptr,t_size bytes,unsigned srate,unsigned nch,unsigned bps,unsigned channel_config) { return set_data_fixedpoint_ex(ptr,bytes,srate,nch,bps,FLAG_SIGNED | flags_autoendian(), channel_config); } enum { FLAG_LITTLE_ENDIAN = 1, FLAG_BIG_ENDIAN = 2, FLAG_SIGNED = 4, FLAG_UNSIGNED = 8, }; inline static unsigned flags_autoendian() { return pfc::byte_order_is_big_endian ? FLAG_BIG_ENDIAN : FLAG_LITTLE_ENDIAN; } void set_data_fixedpoint_ex(const void * ptr,t_size bytes,unsigned p_sample_rate,unsigned p_channels,unsigned p_bits_per_sample,unsigned p_flags,unsigned p_channel_config);//p_flags - see FLAG_* above void set_data_floatingpoint_ex(const void * ptr,t_size bytes,unsigned p_sample_rate,unsigned p_channels,unsigned p_bits_per_sample,unsigned p_flags,unsigned p_channel_config);//signed/unsigned flags dont apply inline void set_data_32(const float * src,t_size samples,unsigned nch,unsigned srate) {return set_data(src,samples,nch,srate);} void pad_with_silence_ex(t_size samples,unsigned hint_nch,unsigned hint_srate); void pad_with_silence(t_size samples); void insert_silence_fromstart(t_size samples); t_size skip_first_samples(t_size samples); //! Helper, calculates peak value of data in the chunk. The optional parameter specifies initial peak value, to simplify calling code. audio_sample get_peak(audio_sample p_peak = 0) const; //! Helper function; scales entire chunk content by specified value. void scale(audio_sample p_value); //! Helper; copies content of another audio chunk to this chunk. void copy(const audio_chunk & p_source) { set_data(p_source.get_data(),p_source.get_sample_count(),p_source.get_channels(),p_source.get_srate(),p_source.get_channel_config()); } const audio_chunk & operator=(const audio_chunk & p_source) { copy(p_source); return *this; } protected: audio_chunk() {} ~audio_chunk() {} }; //! Implementation of audio_chunk. Takes pfc allocator template as template parameter. template<template<typename> class t_alloc = pfc::alloc_standard> class audio_chunk_impl_t : public audio_chunk { typedef audio_chunk_impl_t<t_alloc> t_self; pfc::array_t<audio_sample,t_alloc> m_data; unsigned m_srate,m_nch,m_setup; t_size m_samples; public: audio_chunk_impl_t() : m_srate(0), m_nch(0), m_samples(0), m_setup(0) {} audio_chunk_impl_t(const audio_sample * src,unsigned samples,unsigned nch,unsigned srate) : m_srate(0), m_nch(0), m_samples(0) {set_data(src,samples,nch,srate);} audio_chunk_impl_t(const audio_chunk & p_source) : m_srate(0), m_nch(0), m_samples(0), m_setup(0) {copy(p_source);} audio_chunk_impl_t(const t_self & p_source) : m_srate(0), m_nch(0), m_samples(0), m_setup(0) {copy(p_source);} virtual audio_sample * get_data() {return m_data.get_ptr();} virtual const audio_sample * get_data() const {return m_data.get_ptr();} virtual t_size get_data_size() const {return m_data.get_size();} virtual void set_data_size(t_size new_size) {m_data.set_size(new_size);} virtual unsigned get_srate() const {return m_srate;} virtual void set_srate(unsigned val) {m_srate=val;} virtual unsigned get_channels() const {return m_nch;} virtual unsigned get_channel_config() const {return m_setup;} virtual void set_channels(unsigned val,unsigned setup) {m_nch = val;m_setup = setup;} void set_channels(unsigned val) {set_channels(val,g_guess_channel_config(val));} virtual t_size get_sample_count() const {return m_samples;} virtual void set_sample_count(t_size val) {m_samples = val;} const t_self & operator=(const audio_chunk & p_source) {copy(p_source);return *this;} const t_self & operator=(const t_self & p_source) {copy(p_source);return *this;} }; typedef audio_chunk_impl_t<> audio_chunk_impl; typedef audio_chunk_impl audio_chunk_i;//for compatibility //! Implements const methods of audio_chunk only, referring to an external buffer. For temporary use only (does not maintain own storage), e.g.: somefunc( audio_chunk_temp_impl(mybuffer,....) ); class audio_chunk_temp_impl : public audio_chunk { public: audio_chunk_temp_impl(const audio_sample * p_data,t_size p_samples,t_uint32 p_sample_rate,t_uint32 p_channels,t_uint32 p_channel_config) : m_data(p_data), m_samples(p_samples), m_sample_rate(p_sample_rate), m_channels(p_channels), m_channel_config(p_channel_config) { PFC_ASSERT(is_valid()); } audio_sample * get_data() {throw pfc::exception_not_implemented();} const audio_sample * get_data() const {return m_data;} t_size get_data_size() const {return m_samples * m_channels;} void set_data_size(t_size p_new_size) {throw pfc::exception_not_implemented();} unsigned get_srate() const {return m_sample_rate;} void set_srate(unsigned val) {throw pfc::exception_not_implemented();} unsigned get_channels() const {return m_channels;} unsigned get_channel_config() const {return m_channel_config;} void set_channels(unsigned p_count,unsigned p_config) {throw pfc::exception_not_implemented();} t_size get_sample_count() const {return m_samples;} void set_sample_count(t_size val) {throw pfc::exception_not_implemented();} private: t_size m_samples; t_uint32 m_sample_rate,m_channels,m_channel_config; const audio_sample * m_data; }; #endif //_AUDIO_CHUNK_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/audio_chunk_channel_config.cpp ================================================ #include "foobar2000.h" #ifdef _WIN32 #include <ks.h> #include <ksmedia.h> #if 0 #define SPEAKER_FRONT_LEFT 0x1 #define SPEAKER_FRONT_RIGHT 0x2 #define SPEAKER_FRONT_CENTER 0x4 #define SPEAKER_LOW_FREQUENCY 0x8 #define SPEAKER_BACK_LEFT 0x10 #define SPEAKER_BACK_RIGHT 0x20 #define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 #define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 #define SPEAKER_BACK_CENTER 0x100 #define SPEAKER_SIDE_LEFT 0x200 #define SPEAKER_SIDE_RIGHT 0x400 #define SPEAKER_TOP_CENTER 0x800 #define SPEAKER_TOP_FRONT_LEFT 0x1000 #define SPEAKER_TOP_FRONT_CENTER 0x2000 #define SPEAKER_TOP_FRONT_RIGHT 0x4000 #define SPEAKER_TOP_BACK_LEFT 0x8000 #define SPEAKER_TOP_BACK_CENTER 0x10000 #define SPEAKER_TOP_BACK_RIGHT 0x20000 #endif static struct {DWORD m_wfx; unsigned m_native; } const g_translation_table[] = { {SPEAKER_FRONT_LEFT, audio_chunk::channel_front_left}, {SPEAKER_FRONT_RIGHT, audio_chunk::channel_front_right}, {SPEAKER_FRONT_CENTER, audio_chunk::channel_front_center}, {SPEAKER_LOW_FREQUENCY, audio_chunk::channel_lfe}, {SPEAKER_BACK_LEFT, audio_chunk::channel_back_left}, {SPEAKER_BACK_RIGHT, audio_chunk::channel_back_right}, {SPEAKER_FRONT_LEFT_OF_CENTER, audio_chunk::channel_front_center_left}, {SPEAKER_FRONT_RIGHT_OF_CENTER, audio_chunk::channel_front_center_right}, {SPEAKER_BACK_CENTER, audio_chunk::channel_back_center}, {SPEAKER_SIDE_LEFT, audio_chunk::channel_side_left}, {SPEAKER_SIDE_RIGHT, audio_chunk::channel_side_right}, {SPEAKER_TOP_CENTER, audio_chunk::channel_top_center}, {SPEAKER_TOP_FRONT_LEFT, audio_chunk::channel_top_front_left}, {SPEAKER_TOP_FRONT_CENTER, audio_chunk::channel_top_front_center}, {SPEAKER_TOP_FRONT_RIGHT, audio_chunk::channel_top_front_right}, {SPEAKER_TOP_BACK_LEFT, audio_chunk::channel_top_back_left}, {SPEAKER_TOP_BACK_CENTER, audio_chunk::channel_top_back_center}, {SPEAKER_TOP_BACK_RIGHT, audio_chunk::channel_top_back_right}, }; DWORD audio_chunk::g_channel_config_to_wfx(unsigned p_config) { DWORD ret = 0; unsigned n; for(n=0;n<tabsize(g_translation_table);n++) { if (p_config & g_translation_table[n].m_native) ret |= g_translation_table[n].m_wfx; } return ret; } unsigned audio_chunk::g_channel_config_from_wfx(DWORD p_wfx) { unsigned ret = 0; unsigned n; for(n=0;n<tabsize(g_translation_table);n++) { if (p_wfx & g_translation_table[n].m_wfx) ret |= g_translation_table[n].m_native; } return ret; } #endif static unsigned g_audio_channel_config_table[] = { 0, audio_chunk::channel_config_mono, audio_chunk::channel_config_stereo, audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_lfe, audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right, audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right | audio_chunk::channel_lfe, audio_chunk::channel_config_5point1, audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right | audio_chunk::channel_lfe | audio_chunk::channel_front_center_right | audio_chunk::channel_front_center_left, audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right | audio_chunk::channel_front_center | audio_chunk::channel_lfe | audio_chunk::channel_front_center_right | audio_chunk::channel_front_center_left, }; unsigned audio_chunk::g_guess_channel_config(unsigned count) { if (count >= tabsize(g_audio_channel_config_table)) return 0; return g_audio_channel_config_table[count]; } unsigned audio_chunk::g_channel_index_from_flag(unsigned p_config,unsigned p_flag) { unsigned index = 0; for(unsigned walk = 0; walk < 32; walk++) { unsigned query = 1 << walk; if (p_flag & query) return index; if (p_config & query) index++; } return infinite; } unsigned audio_chunk::g_extract_channel_flag(unsigned p_config,unsigned p_index) { unsigned toskip = p_index; unsigned flag = 1; while(flag) { if (p_config & flag) { if (toskip == 0) break; toskip--; } flag <<= 1; } return flag; } unsigned audio_chunk::g_count_channels(unsigned p_config) { unsigned ret = 0; while(p_config) { ret += (p_config & 1); p_config >>= 1; } return ret; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/audio_postprocessor.h ================================================ #ifndef _CVT_FLOAT_TO_LINEAR_H_ #define _CVT_FLOAT_TO_LINEAR_H_ //! This class handles conversion of audio data (audio_chunk) to various linear PCM types, with optional dithering. class NOVTABLE audio_postprocessor : public service_base { public: //! Processes one chunk of audio data. //! @param p_chunk Chunk of audio data to process. //! @param p_output Receives output linear signed PCM data. //! @param p_out_bps Desired bit depth of output. //! @param p_out_bps_physical Desired physical word width of output. Must be either 8, 16, 24 or 32, greater or equal to p_out_bps. This is typically set to same value as p_out_bps. //! @param p_dither Indicates whether dithering should be used. Note that dithering is CPU-heavy. //! @param p_prescale Value to scale all audio samples by when converting. Set to 1.0 to do nothing. virtual void run(const audio_chunk & p_chunk, mem_block_container & p_output, t_uint32 p_out_bps, t_uint32 p_out_bps_physical, bool p_dither, audio_sample p_prescale ) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(audio_postprocessor); }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/cfg_var.cpp ================================================ #include "foobar2000.h" cfg_var * cfg_var::list=0; static int cfg_var_guid_compare(const cfg_var * p_var1,const cfg_var * p_var2) { return pfc::guid_compare(p_var1->get_guid(),p_var2->get_guid()); } static int cfg_var_guid_compare_search(const cfg_var * p_var1,const GUID & p_var2) { return pfc::guid_compare(p_var1->get_guid(),p_var2); } void cfg_var::config_read_file(stream_reader * p_stream,abort_callback & p_abort) { for(;;) { GUID guid; t_uint32 size; if (p_stream->read(&guid,sizeof(guid),p_abort) != sizeof(guid)) break; guid = pfc::byteswap_if_be_t(guid); p_stream->read_lendian_t(size,p_abort); bool found = false; cfg_var * ptr; for(ptr = list; ptr; ptr=ptr->next) { if (ptr->get_guid() == guid) { stream_reader_limited_ref wrapper(p_stream,size); try { ptr->set_data_raw(&wrapper,size,p_abort); } catch(exception_io_data const & ) {} wrapper.flush_remaining(p_abort); found = true; break; } } if (!found) p_stream->skip_object(size,p_abort); } } void cfg_var::config_write_file(stream_writer * p_stream,abort_callback & p_abort) { cfg_var * ptr; pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> temp; for(ptr = list; ptr; ptr=ptr->next) { temp.set_size(0); ptr->get_data_raw(&stream_writer_buffer_append_ref_t<pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> >(temp),p_abort); p_stream->write_lendian_t(ptr->get_guid(),p_abort); p_stream->write_lendian_t(pfc::downcast_guarded<t_uint32>(temp.get_size()),p_abort); if (temp.get_size() > 0) { p_stream->write_object(temp.get_ptr(),temp.get_size(),p_abort); } } } void cfg_string::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { p_stream->write_object(get_ptr(),length(),p_abort); } void cfg_string::set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { pfc::string8_fastalloc temp; p_stream->read_string_raw(temp,p_abort); set_string(temp); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/cfg_var.h ================================================ #ifndef _FOOBAR2000_SDK_CFG_VAR_H_ #define _FOOBAR2000_SDK_CFG_VAR_H_ //! Base class for configuration variable classes; provides self-registration mechaisms and methods to set/retrieve configuration data; those methods are automatically called for all registered instances by backend when configuration file is being read or written.\n //! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such). class NOVTABLE cfg_var { protected: //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. cfg_var(const GUID & p_guid) : m_guid(p_guid) {PFC_ASSERT(!core_api::are_services_available());/*imperfect check for nonstatic instantiation*/next=list;list=this;}; ~cfg_var() {PFC_ASSERT(!core_api::are_services_available());/*imperfect check for nonstatic instantiation*/} public: //! Retrieves state of the variable. Called only from main thread, when writing configuration file. //! @param p_stream Stream receiving state of the variable. virtual void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) = 0; //! Sets state of the variable. Called only from main thread, when reading configuration file. //! @param p_stream Stream containing new state of the variable. //! @param p_sizehint Number of bytes contained in the stream; reading past p_sizehint bytes will fail (EOF). virtual void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) = 0; //! For internal use only, do not call. inline const GUID & get_guid() const {return m_guid;} //! For internal use only, do not call. static void config_read_file(stream_reader * p_stream,abort_callback & p_abort); //! For internal use only, do not call. static void config_write_file(stream_writer * p_stream,abort_callback & p_abort); private: GUID m_guid; static cfg_var * list; cfg_var * next; cfg_var(const cfg_var& ) {throw pfc::exception_not_implemented();} const cfg_var & operator=(const cfg_var& ) {throw pfc::exception_not_implemented();} }; //! Generic integer config variable class. Template parameter can be used to specify integer type to use.\n //! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such). template<typename t_inttype> class cfg_int_t : public cfg_var { private: t_inttype m_val; protected: void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {p_stream->write_lendian_t(m_val,p_abort);} void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { t_inttype temp; p_stream->read_lendian_t(temp,p_abort);//alter member data only on success, this will throw an exception when something isn't right m_val = temp; } public: //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. //! @param p_default Default value of the variable. explicit inline cfg_int_t(const GUID & p_guid,t_inttype p_default) : cfg_var(p_guid), m_val(p_default) {} inline const cfg_int_t<t_inttype> & operator=(const cfg_int_t<t_inttype> & p_val) {m_val=p_val.m_val;return *this;} inline t_inttype operator=(t_inttype p_val) {m_val=p_val;return m_val;} inline operator t_inttype() const {return m_val;} inline t_inttype get_value() const {return m_val;} }; typedef cfg_int_t<t_int32> cfg_int; typedef cfg_int_t<t_uint32> cfg_uint; //! Since relevant byteswapping functions also understand GUIDs, this can be abused to declare a cfg_guid. typedef cfg_int_t<GUID> cfg_guid; typedef cfg_int_t<bool> cfg_bool; //! String config variable. Stored in the stream with int32 header containing size in bytes, followed by non-null-terminated UTF-8 data.\n //! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such). class cfg_string : public cfg_var, public pfc::string8 { protected: void get_data_raw(stream_writer * p_stream,abort_callback & p_abort); void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort); public: //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. //! @param p_defaultval Default/initial value of the variable. explicit inline cfg_string(const GUID & p_guid,const char * p_defaultval) : cfg_var(p_guid), pfc::string8(p_defaultval) {} inline const cfg_string& operator=(const cfg_string & p_val) {set_string(p_val);return *this;} inline const cfg_string& operator=(const char* p_val) {set_string(p_val);return *this;} inline operator const char * () const {return get_ptr();} }; //! Struct config variable template. Warning: not endian safe, should be used only for nonportable code.\n //! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such). template<typename t_struct> class cfg_struct_t : public cfg_var { private: t_struct m_val; protected: void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {p_stream->write_object(&m_val,sizeof(m_val),p_abort);} void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { t_struct temp; p_stream->read_object(&temp,sizeof(temp),p_abort); m_val = temp; } public: //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. inline cfg_struct_t(const GUID & p_guid,const t_struct & p_val) : cfg_var(p_guid), m_val(p_val) {} //! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var. inline cfg_struct_t(const GUID & p_guid,int filler) : cfg_var(p_guid) {memset(&m_val,filler,sizeof(t_struct));} inline const cfg_struct_t<t_struct> & operator=(const cfg_struct_t<t_struct> & p_val) {m_val = p_val.get_value();return *this;} inline const cfg_struct_t<t_struct> & operator=(const t_struct & p_val) {m_val = p_val;return *this;} inline const t_struct& get_value() const {return m_val;} inline t_struct& get_value() {return m_val;} inline operator t_struct() const {return m_val;} }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/chapterizer.cpp ================================================ #include "foobar2000.h" void chapter_list::copy(const chapter_list & p_source) { t_size n, count = p_source.get_chapter_count(); set_chapter_count(count); for(n=0;n<count;n++) set_info(n,p_source.get_info(n)); } bool chapterizer::g_find(service_ptr_t<chapterizer> & p_out,const char * p_path,abort_callback & p_abort) { service_ptr_t<chapterizer> ptr; service_enum_t<chapterizer> e; while(e.next(ptr)) { if (ptr->is_our_file(p_path,p_abort)) { p_out = ptr; return true; } } return false; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/chapterizer.h ================================================ //! Interface for object storing list of chapters. class NOVTABLE chapter_list { public: //! Returns number of chapters. virtual t_size get_chapter_count() const = 0; //! Queries description of specified chapter. //! @param p_chapter Index of chapter to query, greater or equal zero and less than get_chapter_count() value. If p_chapter value is out of valid range, results are undefined (e.g. crash). //! @returns reference to file_info object describing specified chapter (length part of file_info indicates distance between beginning of this chapter and next chapter mark). Returned reference value for temporary use only, becomes invalid after any non-const operation on the chapter_list object. virtual const file_info & get_info(t_size p_chapter) const = 0; //! Sets number of chapters. virtual void set_chapter_count(t_size p_count) = 0; //! Modifies description of specified chapter. //! @param p_chapter_index Index of chapter to modify, greater or equal zero and less than get_chapter_count() value. If p_chapter value is out of valid range, results are undefined (e.g. crash). //! @param p_info New chapter description. Note that length part of file_info is used to calculate chapter marks. virtual void set_info(t_size p_chapter,const file_info & p_info) = 0; //! Copies contents of specified chapter_list object to this object. void copy(const chapter_list & p_source); inline const chapter_list & operator=(const chapter_list & p_source) {copy(p_source); return *this;} protected: chapter_list() {} ~chapter_list() {} }; //! Implements chapter_list. class chapter_list_impl : public chapter_list { public: chapter_list_impl(const chapter_list_impl & p_source) {copy(p_source);} chapter_list_impl(const chapter_list & p_source) {copy(p_source);} chapter_list_impl() {} const chapter_list_impl & operator=(const chapter_list_impl & p_source) {copy(p_source); return *this;} const chapter_list_impl & operator=(const chapter_list & p_source) {copy(p_source); return *this;} t_size get_chapter_count() const {return m_infos.get_size();} const file_info & get_info(t_size p_chapter) const {return m_infos[p_chapter];} void set_chapter_count(t_size p_count) {m_infos.set_size(p_count);} void set_info(t_size p_chapter,const file_info & p_info) {m_infos[p_chapter] = p_info;} private: pfc::array_t<file_info_impl> m_infos; }; //! This service implements chapter list editing operations for various file formats, e.g. for MP4 chapters or CD images with embedded cuesheets. Used by converter "encode single file with chapters" feature. class NOVTABLE chapterizer : public service_base { public: //! Tests whether specified path is supported by this implementation. //! @param p_path Path of file to examine. //! @param p_abort abort_callback object signaling user aborting the operation. virtual bool is_our_file(const char * p_path,abort_callback & p_abort) = 0; //! Writes new chapter list to specified file. //! @param p_path Path of file to modify. //! @param p_list New chapter list to write. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void set_chapters(const char * p_path,chapter_list const & p_list,abort_callback & p_abort) = 0; //! Retrieves chapter list from specified file. //! @param p_path Path of file to examine. //! @param p_list Object receiving chapter list. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void get_chapters(const char * p_path,chapter_list & p_list,abort_callback & p_abort) = 0; //! Static helper, tries to find chapterizer interface that supports specified file. static bool g_find(service_ptr_t<chapterizer> & p_out,const char * p_path,abort_callback & p_abort); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(chapterizer); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/commandline.cpp ================================================ #include "foobar2000.h" void commandline_handler_metadb_handle::on_file(const char * url) { abort_callback_impl blah; { playlist_loader_callback_impl callback(blah); bool fail = false; try { playlist_loader::g_process_path_ex(url,callback); } catch(std::exception const & e) { console::formatter() << "Unhandled exception in playlist loader: " << e; fail = true; } catch(...) { console::formatter() << "Unhandled exception in playlist loader"; fail = true; } if (!fail) { t_size n,m=callback.get_count(); for(n=0;n<m;n++) on_file(callback.get_item(n)); } } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/commandline.h ================================================ #ifndef _FOOBAR2000_SDK_COMMANDLINE_H_ #define _FOOBAR2000_SDK_COMMANDLINE_H_ #include "service.h" class NOVTABLE commandline_handler : public service_base { public: enum result { RESULT_NOT_OURS,//not our command RESULT_PROCESSED,//command processed RESULT_PROCESSED_EXPECT_FILES,//command processed, we want to takeover file urls after this command }; virtual result on_token(const char * token)=0; virtual void on_file(const char * url) {};//optional virtual void on_files_done() {};//optional virtual bool want_directories() {return false;} FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(commandline_handler); }; class commandline_handler_metadb_handle : public commandline_handler//helper { protected: virtual void on_file(const char * url); virtual bool want_directories() {return true;} public: virtual result on_token(const char * token)=0; virtual void on_files_done() {}; virtual void on_file(const metadb_handle_ptr & ptr)=0; }; /* how commandline_handler is used: scenario #1: creation => on_token() => deletion scenario #2: creation => on_token() returning RESULT_PROCESSED_EXPECT_FILES => on_file(), on_file().... => on_files_done() => deletion */ template<typename T> class commandline_handler_factory_t : public service_factory_t<T> {}; #endif //_FOOBAR2000_SDK_COMMANDLINE_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/completion_notify.cpp ================================================ #include "foobar2000.h" namespace { class main_thread_callback_myimpl : public main_thread_callback { public: void callback_run() { m_notify->on_completion(m_code); } main_thread_callback_myimpl(completion_notify_ptr p_notify,unsigned p_code) : m_notify(p_notify), m_code(p_code) {} private: completion_notify_ptr m_notify; unsigned m_code; }; } void completion_notify::g_signal_completion_async(completion_notify_ptr p_notify,unsigned p_code) { if (p_notify.is_valid()) { static_api_ptr_t<main_thread_callback_manager>()->add_callback(new service_impl_t<main_thread_callback_myimpl>(p_notify,p_code)); } } void completion_notify::on_completion_async(unsigned p_code) { static_api_ptr_t<main_thread_callback_manager>()->add_callback(new service_impl_t<main_thread_callback_myimpl>(this,p_code)); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/completion_notify.h ================================================ //! Generic service for receiving notifications about async operation completion. Used by various other services. class completion_notify : public service_base { public: //! Called when an async operation has been completed. Note that on_completion is always called from main thread. You can use on_completion_async() helper if you need to signal completion while your context is in another thread.\n //! IMPLEMENTATION WARNING: If process being completed creates a window taking caller's window as parent, you must not destroy the parent window inside on_completion(). If you need to do so, use PostMessage() or main_thread_callback to delay the deletion. //! @param p_code Context-specific status code. Possible values depend on the operation being performed. virtual void on_completion(unsigned p_code) = 0; //! Helper. Queues a notification, using main_thread_callback. void on_completion_async(unsigned p_code); //! Helper. Checks for null ptr and calls on_completion_async when the ptr is not null. static void g_signal_completion_async(service_ptr_t<completion_notify> p_notify,unsigned p_code); FB2K_MAKE_SERVICE_INTERFACE(completion_notify,service_base); }; //! Helper implementation. class completion_notify_orphanable : public completion_notify { public: virtual void orphan() = 0; }; //! Helper implementation. //! IMPLEMENTATION WARNING: If process being completed creates a window taking caller's window as parent, you must not destroy the parent window inside on_task_completion(). If you need to do so, use PostMessage() or main_thread_callback to delay the deletion. template<typename t_receiver> class completion_notify_impl : public completion_notify_orphanable { public: void on_completion(unsigned p_code) { if (m_receiver != NULL) { m_receiver->on_task_completion(m_taskid,p_code); } } void setup(t_receiver * p_receiver, unsigned p_task_id) {m_receiver = p_receiver; m_taskid = p_task_id;} void orphan() {m_receiver = NULL; m_taskid = 0;} private: t_receiver * m_receiver; unsigned m_taskid; }; template<typename t_receiver> service_ptr_t<completion_notify_orphanable> completion_notify_create(t_receiver * p_receiver,unsigned p_taskid) { service_ptr_t<completion_notify_impl<t_receiver> > instance = new service_impl_t<completion_notify_impl<t_receiver> >(); instance->setup(p_receiver,p_taskid); return instance; } typedef service_ptr_t<completion_notify> completion_notify_ptr; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/component.h ================================================ #ifndef _COMPONENT_H_ #define _COMPONENT_H_ #include "foobar2000.h" class NOVTABLE foobar2000_client { public: typedef service_factory_base* pservice_factory_base; enum {FOOBAR2000_CLIENT_VERSION_COMPATIBLE = 70, FOOBAR2000_CLIENT_VERSION = 72}; //changes everytime global compatibility is broken virtual t_uint32 FB2KAPI get_version() = 0; virtual pservice_factory_base FB2KAPI get_service_list() = 0; virtual void FB2KAPI get_config(stream_writer * p_stream,abort_callback & p_abort) = 0; virtual void FB2KAPI set_config(stream_reader * p_stream,abort_callback & p_abort) = 0; virtual void FB2KAPI set_library_path(const char * path,const char * name) = 0; virtual void FB2KAPI services_init(bool val) = 0; virtual bool is_debug() = 0; protected: foobar2000_client() {} ~foobar2000_client() {} }; class NOVTABLE foobar2000_api { public: virtual service_class_ref FB2KAPI service_enum_find_class(const GUID & p_guid) = 0; virtual bool FB2KAPI service_enum_create(service_ptr_t<service_base> & p_out,service_class_ref p_class,t_size p_index) = 0; virtual t_size FB2KAPI service_enum_get_count(service_class_ref p_class) = 0; virtual HWND FB2KAPI get_main_window()=0; virtual bool FB2KAPI assert_main_thread()=0; virtual bool FB2KAPI is_main_thread()=0; virtual bool FB2KAPI is_shutting_down()=0; virtual pcchar FB2KAPI get_profile_path()=0; virtual bool FB2KAPI is_initializing() = 0; protected: foobar2000_api() {} ~foobar2000_api() {} }; extern foobar2000_api * g_api; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/component_client.h ================================================ #ifndef _COMPONENT_CLIENT_H_ #define _COMPONENT_CLIENT_H_ #endif //_COMPONENT_CLIENT_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/components_menu.h ================================================ #ifndef _COMPONENTS_MENU_H_ #define _COMPONENTS_MENU_H_ #error deprecated, see menu_item.h #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/componentversion.h ================================================ #ifndef _COMPONENTVERSION_H_ #define _COMPONENTVERSION_H_ class NOVTABLE componentversion : public service_base { public: virtual void get_file_name(pfc::string_base & out)=0; virtual void get_component_name(pfc::string_base & out)=0; virtual void get_component_version(pfc::string_base & out)=0; virtual void get_about_message(pfc::string_base & out)=0;//about message uses "\n" for line separators FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(componentversion); }; class componentversion_impl_simple : public componentversion { const char * name,*version,*about; public: //do not derive/override virtual void get_file_name(pfc::string_base & out) {out.set_string(core_api::get_my_file_name());} virtual void get_component_name(pfc::string_base & out) {out.set_string(name?name:"");} virtual void get_component_version(pfc::string_base & out) {out.set_string(version?version:"");} virtual void get_about_message(pfc::string_base & out) {out.set_string(about?about:"");} explicit componentversion_impl_simple(const char * p_name,const char * p_version,const char * p_about) : name(p_name), version(p_version), about(p_about ? p_about : "") {} }; class componentversion_impl_copy : public componentversion { pfc::string8 name,version,about; public: //do not derive/override virtual void get_file_name(pfc::string_base & out) {out.set_string(core_api::get_my_file_name());} virtual void get_component_name(pfc::string_base & out) {out.set_string(name);} virtual void get_component_version(pfc::string_base & out) {out.set_string(version);} virtual void get_about_message(pfc::string_base & out) {out.set_string(about);} explicit componentversion_impl_copy(const char * p_name,const char * p_version,const char * p_about) : name(p_name), version(p_version), about(p_about ? p_about : "") {} }; typedef service_factory_single_transparent_t<componentversion_impl_simple> __componentversion_impl_simple_factory; typedef service_factory_single_transparent_t<componentversion_impl_copy> __componentversion_impl_copy_factory; class componentversion_impl_simple_factory : public __componentversion_impl_simple_factory { public: componentversion_impl_simple_factory(const char * p_name,const char * p_version,const char * p_about) : __componentversion_impl_simple_factory(p_name,p_version,p_about) {} }; class componentversion_impl_copy_factory : public __componentversion_impl_copy_factory { public: componentversion_impl_copy_factory(const char * p_name,const char * p_version,const char * p_about) : __componentversion_impl_copy_factory(p_name,p_version,p_about) {} }; #define DECLARE_COMPONENT_VERSION(NAME,VERSION,ABOUT) \ static componentversion_impl_simple_factory g_componentversion_service(NAME,VERSION,ABOUT); #define DECLARE_COMPONENT_VERSION_COPY(NAME,VERSION,ABOUT) \ static componentversion_impl_copy_factory g_componentversion_service(NAME,VERSION,ABOUT); //usage: DECLARE_COMPONENT_VERSION("blah","v1.337",(const char*)NULL) //_copy version copies strings around instead of keeping pointers (bigger but sometimes needed, eg. if strings are created as string_printf() or something inside constructor #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/config_io_callback.cpp ================================================ #include "foobar2000.h" void config_io_callback::g_on_read() { service_enum_t<config_io_callback> e; service_ptr_t<config_io_callback> ptr; if (e.first(ptr)) do { ptr->on_read(); } while(e.next(ptr)); } void config_io_callback::g_on_write(bool reset) { service_enum_t<config_io_callback> e; service_ptr_t<config_io_callback> ptr; if (e.first(ptr)) do { ptr->on_write(reset); } while(e.next(ptr)); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/config_io_callback.h ================================================ #ifndef _config_io_callback_h_ #define _config_io_callback_h_ class NOVTABLE config_io_callback : public service_base { public: virtual void on_read() = 0; virtual void on_write(bool reset) = 0; //for core use only static void g_on_read(); static void g_on_write(bool reset); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_io_callback); }; #endif //_config_io_callback_h_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/config_object.cpp ================================================ #include "foobar2000.h" void config_object_notify_manager::g_on_changed(const service_ptr_t<config_object> & p_object) { if (core_api::assert_main_thread()) { service_enum_t<config_object_notify_manager> e; service_ptr_t<config_object_notify_manager> ptr; while(e.next(ptr)) ptr->on_changed(p_object); } } bool config_object::g_find(service_ptr_t<config_object> & p_out,const GUID & p_guid) { service_ptr_t<config_object> ptr; service_enum_t<config_object> e; while(e.next(ptr)) { if (ptr->get_guid() == p_guid) { p_out = ptr; return true; } } return false; } void config_object::g_get_data_string(const GUID & p_guid,pfc::string_base & p_out) { service_ptr_t<config_object> ptr; if (!g_find(ptr,p_guid)) throw exception_service_not_found(); ptr->get_data_string(p_out); } void config_object::g_set_data_string(const GUID & p_guid,const char * p_data,t_size p_length) { service_ptr_t<config_object> ptr; if (!g_find(ptr,p_guid)) throw exception_service_not_found(); ptr->set_data_string(p_data,p_length); } void config_object::get_data_int32(t_int32 & p_out) { t_int32 temp; get_data_struct_t<t_int32>(temp); byte_order::order_le_to_native_t(temp); p_out = temp; } void config_object::set_data_int32(t_int32 p_val) { t_int32 temp = p_val; byte_order::order_native_to_le_t(temp); set_data_struct_t<t_int32>(temp); } bool config_object::get_data_bool_simple(bool p_default) { try { bool ret = p_default; get_data_bool(ret); return ret; } catch(std::exception const &) {return p_default;} } t_int32 config_object::get_data_int32_simple(t_int32 p_default) { try { t_int32 ret = p_default; get_data_int32(ret); return ret; } catch(std::exception const &) {return p_default;} } void config_object::g_get_data_int32(const GUID & p_guid,t_int32 & p_out) { service_ptr_t<config_object> ptr; if (!g_find(ptr,p_guid)) throw exception_service_not_found(); ptr->get_data_int32(p_out); } void config_object::g_set_data_int32(const GUID & p_guid,t_int32 p_val) { service_ptr_t<config_object> ptr; if (!g_find(ptr,p_guid)) throw exception_service_not_found(); ptr->set_data_int32(p_val); } bool config_object::g_get_data_bool_simple(const GUID & p_guid,bool p_default) { service_ptr_t<config_object> ptr; if (!g_find(ptr,p_guid)) throw exception_service_not_found(); return ptr->get_data_bool_simple(p_default); } t_int32 config_object::g_get_data_int32_simple(const GUID & p_guid,t_int32 p_default) { service_ptr_t<config_object> ptr; if (!g_find(ptr,p_guid)) throw exception_service_not_found(); return ptr->get_data_int32_simple(p_default); } void config_object::get_data_bool(bool & p_out) {get_data_struct_t<bool>(p_out);} void config_object::set_data_bool(bool p_val) {set_data_struct_t<bool>(p_val);} void config_object::g_get_data_bool(const GUID & p_guid,bool & p_out) {g_get_data_struct_t<bool>(p_guid,p_out);} void config_object::g_set_data_bool(const GUID & p_guid,bool p_val) {g_set_data_struct_t<bool>(p_guid,p_val);} namespace { class stream_writer_string : public stream_writer { public: void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { m_out.add_string((const char*)p_buffer,p_bytes); } stream_writer_string(pfc::string_base & p_out) : m_out(p_out) {m_out.reset();} private: pfc::string_base & m_out; }; class stream_writer_fixedbuffer : public stream_writer { public: void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { if (p_bytes > 0) { if (p_bytes > m_bytes - m_bytes_read) throw pfc::exception_overflow(); memcpy((t_uint8*)m_out,p_buffer,p_bytes); m_bytes_read += p_bytes; } } stream_writer_fixedbuffer(void * p_out,t_size p_bytes,t_size & p_bytes_read) : m_out(p_out), m_bytes(p_bytes), m_bytes_read(p_bytes_read) {m_bytes_read = 0;} private: void * m_out; t_size m_bytes; t_size & m_bytes_read; }; class stream_writer_get_length : public stream_writer { public: void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { m_length += p_bytes; } stream_writer_get_length(t_size & p_length) : m_length(p_length) {m_length = 0;} private: t_size & m_length; }; }; t_size config_object::get_data_raw(void * p_out,t_size p_bytes) { t_size ret = 0; get_data(&stream_writer_fixedbuffer(p_out,p_bytes,ret),abort_callback_impl()); return ret; } t_size config_object::get_data_raw_length() { t_size ret = 0; get_data(&stream_writer_get_length(ret),abort_callback_impl()); return ret; } void config_object::set_data_raw(const void * p_data,t_size p_bytes, bool p_notify) { set_data(&stream_reader_memblock_ref(p_data,p_bytes),abort_callback_impl(),p_notify); } void config_object::set_data_string(const char * p_data,t_size p_length) { set_data_raw(p_data,pfc::strlen_max(p_data,p_length)); } void config_object::get_data_string(pfc::string_base & p_out) { get_data(&stream_writer_string(p_out),abort_callback_impl()); } //config_object_impl stuff void config_object_impl::get_data(stream_writer * p_stream,abort_callback & p_abort) const { insync(m_sync); p_stream->write_object(m_data.get_ptr(),m_data.get_size(),p_abort); } void config_object_impl::set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify) { core_api::ensure_main_thread(); { insync(m_sync); m_data.set_size(0); enum {delta = 1024}; t_uint8 buffer[delta]; for(;;) { t_size delta_done = p_stream->read(buffer,delta,p_abort); if (delta_done > 0) { m_data.append_fromptr(buffer,delta_done); } if (delta_done != delta) break; } } if (p_notify) config_object_notify_manager::g_on_changed(this); } config_object_impl::config_object_impl(const GUID & p_guid,const void * p_data,t_size p_bytes) : cfg_var(p_guid) { m_data.set_data_fromptr((const t_uint8*)p_data,p_bytes); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/config_object.h ================================================ #ifndef _CONFIG_OBJECT_H_ #define _CONFIG_OBJECT_H_ class config_object; class NOVTABLE config_object_notify_manager : public service_base { public: virtual void on_changed(const service_ptr_t<config_object> & p_object) = 0; static void g_on_changed(const service_ptr_t<config_object> & p_object); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object_notify_manager); }; class NOVTABLE config_object : public service_base { public: //interface virtual GUID get_guid() const = 0; virtual void get_data(stream_writer * p_stream,abort_callback & p_abort) const = 0; virtual void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_sendnotify = true) = 0; //helpers static bool g_find(service_ptr_t<config_object> & p_out,const GUID & p_guid); void set_data_raw(const void * p_data,t_size p_bytes,bool p_sendnotify = true); t_size get_data_raw(void * p_out,t_size p_bytes); t_size get_data_raw_length(); template<class T> void get_data_struct_t(T& p_out); template<class T> void set_data_struct_t(const T& p_in); template<class T> static void g_get_data_struct_t(const GUID & p_guid,T & p_out); template<class T> static void g_set_data_struct_t(const GUID & p_guid,const T & p_in); void set_data_string(const char * p_data,t_size p_length); void get_data_string(pfc::string_base & p_out); void get_data_bool(bool & p_out); void set_data_bool(bool p_val); void get_data_int32(t_int32 & p_out); void set_data_int32(t_int32 p_val); bool get_data_bool_simple(bool p_default); t_int32 get_data_int32_simple(t_int32 p_default); static void g_get_data_string(const GUID & p_guid,pfc::string_base & p_out); static void g_set_data_string(const GUID & p_guid,const char * p_data,t_size p_length = ~0); static void g_get_data_bool(const GUID & p_guid,bool & p_out); static void g_set_data_bool(const GUID & p_guid,bool p_val); static void g_get_data_int32(const GUID & p_guid,t_int32 & p_out); static void g_set_data_int32(const GUID & p_guid,t_int32 p_val); static bool g_get_data_bool_simple(const GUID & p_guid,bool p_default); static t_int32 g_get_data_int32_simple(const GUID & p_guid,t_int32 p_default); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object); }; class standard_config_objects { public: static const GUID bool_remember_window_positions, bool_ui_always_on_top,bool_playlist_stop_after_current; static const GUID bool_playback_follows_cursor, bool_cursor_follows_playback; static const GUID bool_show_keyboard_shortcuts_in_menus; static const GUID string_gui_last_directory_media,string_gui_last_directory_playlists; static const GUID int32_dynamic_bitrate_display_rate; inline static bool query_show_keyboard_shortcuts_in_menus() {return config_object::g_get_data_bool_simple(standard_config_objects::bool_show_keyboard_shortcuts_in_menus,true);} inline static bool query_remember_window_positions() {return config_object::g_get_data_bool_simple(standard_config_objects::bool_remember_window_positions,true);} }; class config_object_notify : public service_base { public: virtual t_size get_watched_object_count() = 0; virtual GUID get_watched_object(t_size p_index) = 0; virtual void on_watched_object_changed(const service_ptr_t<config_object> & p_object) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object_notify); }; #endif _CONFIG_OBJECT_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/config_object_impl.h ================================================ #ifndef _CONFIG_OBJECT_IMPL_H_ #define _CONFIG_OBJECT_IMPL_H_ //template function bodies from config_object class template<class T> void config_object::get_data_struct_t(T& p_out) { if (get_data_raw(&p_out,sizeof(T)) != sizeof(T)) throw exception_io_data_truncation(); } template<class T> void config_object::set_data_struct_t(const T& p_in) { return set_data_raw(&p_in,sizeof(T)); } template<class T> void config_object::g_get_data_struct_t(const GUID & p_guid,T & p_out) { service_ptr_t<config_object> ptr; if (!g_find(ptr,p_guid)) throw exception_service_not_found(); return ptr->get_data_struct_t<T>(p_out); } template<class T> void config_object::g_set_data_struct_t(const GUID & p_guid,const T & p_in) { service_ptr_t<config_object> ptr; if (!g_find(ptr,p_guid)) throw exception_service_not_found(); return ptr->set_data_struct_t<T>(p_in); } class config_object_impl : public config_object, private cfg_var { public: GUID get_guid() const {return cfg_var::get_guid();} void get_data(stream_writer * p_stream,abort_callback & p_abort) const ; void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify); config_object_impl(const GUID & p_guid,const void * p_data,t_size p_bytes); private: //cfg_var methods void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {get_data(p_stream,p_abort);} void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {set_data(p_stream,p_abort,false);} mutable critical_section m_sync; pfc::array_t<t_uint8> m_data; }; typedef service_factory_single_transparent_t<config_object_impl> config_object_factory; template<t_size p_size> class config_object_fixed_impl_t : public config_object, private cfg_var { public: GUID get_guid() const {return cfg_var::get_guid();} void get_data(stream_writer * p_stream,abort_callback & p_abort) const { insync(m_sync); p_stream->write_object(m_data,p_size,p_abort); } void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify) { core_api::ensure_main_thread(); { t_uint8 temp[p_size]; p_stream->read_object(temp,p_size,p_abort); insync(m_sync); memcpy(m_data,temp,p_size); } if (p_notify) config_object_notify_manager::g_on_changed(this); } config_object_fixed_impl_t (const GUID & p_guid,const void * p_data) : cfg_var(p_guid) { memcpy(m_data,p_data,p_size); } private: //cfg_var methods void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {get_data(p_stream,p_abort);} void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {set_data(p_stream,p_abort,false);} mutable critical_section m_sync; t_uint8 m_data[p_size]; }; template<t_size p_size> class config_object_fixed_factory_t : public service_factory_single_transparent_t<config_object_fixed_impl_t<p_size> > { public: config_object_fixed_factory_t(const GUID & p_guid,const void * p_initval) : service_factory_single_transparent_t<config_object_fixed_impl_t<p_size> > (p_guid,p_initval) {} }; class config_object_string_factory : public config_object_factory { public: config_object_string_factory(const GUID & p_guid,const char * p_string,t_size p_string_length = infinite) : config_object_factory(p_guid,p_string,pfc::strlen_max(p_string,infinite)) {} }; class config_object_bool_factory : public config_object_fixed_factory_t<1> { public: config_object_bool_factory(const GUID & p_guid,bool p_initval) : config_object_fixed_factory_t<1>(p_guid,&p_initval) {} }; template<class T> class config_object_int_factory_t : public config_object_fixed_factory_t<sizeof(T)> { private: template<class T> struct t_initval { T m_initval; t_initval(T p_initval) : m_initval(p_initval) {byte_order::order_native_to_le_t(m_initval);} T * get_ptr() {return &m_initval;} }; public: config_object_int_factory_t(const GUID & p_guid,T p_initval) : config_object_fixed_factory_t<sizeof(T)>(p_guid,t_initval<T>(p_initval).get_ptr() ) {} }; typedef config_object_int_factory_t<t_int32> config_object_int32_factory; class config_object_notify_impl_simple : public config_object_notify { public: t_size get_watched_object_count() {return 1;} GUID get_watched_object(t_size p_index) {return m_guid;} void on_watched_object_changed(const service_ptr_t<config_object> & p_object) {m_func(p_object);} typedef void (*t_func)(const service_ptr_t<config_object> &); config_object_notify_impl_simple(const GUID & p_guid,t_func p_func) : m_guid(p_guid), m_func(p_func) {} private: GUID m_guid; t_func m_func; }; typedef service_factory_single_transparent_t<config_object_notify_impl_simple> config_object_notify_simple_factory; #endif _CONFIG_OBJECT_IMPL_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/console.cpp ================================================ #include "foobar2000.h" void console::info(const char * p_message) {print(p_message);} void console::error(const char * p_message) {formatter() << "ERROR : " << p_message;} void console::warning(const char * p_message) {formatter() << "WARNING : " << p_message;} void console::info_location(const playable_location & src) {print_location(src);} void console::info_location(const metadb_handle_ptr & src) {print_location(src);} void console::print_location(const metadb_handle_ptr & src) { print_location(src->get_location()); } void console::print_location(const playable_location & src) { formatter() << src; } void console::print(const char* p_message) { if (core_api::are_services_available()) { service_ptr_t<console_receiver> ptr; service_enum_t<console_receiver> e; while(e.next(ptr)) ptr->print(p_message,infinite); } } void console::printf(const char* p_format,...) { va_list list; va_start(list,p_format); printfv(p_format,list); va_end(list); } void console::printfv(const char* p_format,va_list p_arglist) { pfc::string8_fastalloc temp; uPrintfV(temp,p_format,p_arglist); print(temp); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/console.h ================================================ #ifndef _CONSOLE_H_ #define _CONSOLE_H_ //! Namespace with functions for sending text to console. All functions are fully multi-thread safe, though they must not be called during dll initialization or deinitialization (e.g. static object constructors or destructors) when service system is not available. namespace console { void info(const char * p_message); void error(const char * p_message); void warning(const char * p_message); void info_location(const playable_location & src); void info_location(const metadb_handle_ptr & src); void print_location(const playable_location & src); void print_location(const metadb_handle_ptr & src); void print(const char*); void printf(const char*,...); void printfv(const char*,va_list p_arglist); //! Usage: console::formatter() << "blah " << somenumber << " asdf" << somestring; class formatter : public pfc::string_formatter { public: ~formatter() {if (!is_empty()) console::print(get_ptr());} }; }; //! Interface receiving console output. Do not reimplement or call directly; use console namespace functions instead. class NOVTABLE console_receiver : public service_base { public: virtual void print(const char * p_message,t_size p_message_length) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(console_receiver); }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/contextmenu.h ================================================ #ifndef _FOOBAR2000_MENU_ITEM_H_ #define _FOOBAR2000_MENU_ITEM_H_ typedef void * t_glyph; class NOVTABLE contextmenu_item_node { public: enum t_flags { FLAG_CHECKED = 1, FLAG_DISABLED = 2, FLAG_GRAYED = 4, FLAG_DISABLED_GRAYED = FLAG_DISABLED|FLAG_GRAYED, }; enum t_type { TYPE_POPUP,TYPE_COMMAND,TYPE_SEPARATOR }; virtual bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) = 0; virtual t_type get_type() = 0; virtual void execute(const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) = 0; virtual t_glyph get_glyph(const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) {return 0;}//RESERVED virtual t_size get_children_count() = 0; virtual contextmenu_item_node * get_child(t_size p_index) = 0; virtual bool get_description(pfc::string_base & p_out) = 0; virtual GUID get_guid() = 0; virtual bool is_mappable_shortcut() = 0; protected: contextmenu_item_node() {} ~contextmenu_item_node() {} }; class NOVTABLE contextmenu_item_node_root : public contextmenu_item_node { public: virtual ~contextmenu_item_node_root() {} }; class NOVTABLE contextmenu_item_node_leaf : public contextmenu_item_node { public: t_type get_type() {return TYPE_COMMAND;} t_size get_children_count() {return 0;} contextmenu_item_node * get_child(t_size) {return NULL;} }; class NOVTABLE contextmenu_item_node_root_leaf : public contextmenu_item_node_root { public: t_type get_type() {return TYPE_COMMAND;} t_size get_children_count() {return 0;} contextmenu_item_node * get_child(t_size) {return NULL;} }; class NOVTABLE contextmenu_item_node_popup : public contextmenu_item_node { public: t_type get_type() {return TYPE_POPUP;} void execute(const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller) {} bool get_description(pfc::string_base & p_out) {return false;} }; class NOVTABLE contextmenu_item_node_root_popup : public contextmenu_item_node_root { public: t_type get_type() {return TYPE_POPUP;} void execute(const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller) {} bool get_description(pfc::string_base & p_out) {return false;} }; class contextmenu_item_node_separator : public contextmenu_item_node { public: t_type get_type() {return TYPE_SEPARATOR;} void execute(const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller) {} bool get_description(pfc::string_base & p_out) {return false;} t_size get_children_count() {return 0;} bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) { p_displayflags = 0; p_out = "---"; return true; } contextmenu_item_node * get_child(t_size) {return NULL;} }; /*! Service class for declaring context menu commands.\n See contextmenu_item_simple for implementation helper without dynamic menu generation features.\n All methods are valid from main app thread only. */ class NOVTABLE contextmenu_item : public service_base { public: enum t_enabled_state { FORCE_OFF, DEFAULT_OFF, DEFAULT_ON, }; virtual unsigned get_num_items() = 0; virtual contextmenu_item_node_root * instantiate_item(unsigned p_index,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) = 0; virtual GUID get_item_guid(unsigned p_index) = 0; virtual void get_item_name(unsigned p_index,pfc::string_base & p_out) = 0; virtual void get_item_default_path(unsigned p_index,pfc::string_base & p_out) = 0; virtual bool get_item_description(unsigned p_index,pfc::string_base & p_out) = 0; virtual t_enabled_state get_enabled_state(unsigned p_index) = 0; virtual void item_execute_simple(unsigned p_index,const GUID & p_node,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) = 0; bool item_get_display_data_root(pfc::string_base & p_out,unsigned & displayflags,unsigned p_index,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller); bool item_get_display_data(pfc::string_base & p_out,unsigned & displayflags,unsigned p_index,const GUID & p_node,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller); static const GUID caller_now_playing; static const GUID caller_playlist; static const GUID caller_undefined; static const GUID caller_keyboard_shortcut_list; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(contextmenu_item); }; //! contextmenu_item implementation helper for implementing non-dynamically-generated context menu items; derive from this instead of from contextmenu_item directly if your menu items are static. class NOVTABLE contextmenu_item_simple : public contextmenu_item { private: class contextmenu_item_node_impl : public contextmenu_item_node_root_leaf { public: contextmenu_item_node_impl(contextmenu_item_simple * p_owner,unsigned p_index) : m_owner(p_owner), m_index(p_index) {} bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) {return m_owner->get_display_data(m_index,p_data,p_out,p_displayflags,p_caller);} void execute(const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) {m_owner->context_command(m_index,p_data,p_caller);} bool get_description(pfc::string_base & p_out) {return m_owner->get_item_description(m_index,p_out);} GUID get_guid() {return pfc::guid_null;} bool is_mappable_shortcut() {return m_owner->item_is_mappable_shortcut(m_index);} private: service_ptr_t<contextmenu_item_simple> m_owner; unsigned m_index; }; contextmenu_item_node_root * instantiate_item(unsigned p_index,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) { return new contextmenu_item_node_impl(this,p_index); } void item_execute_simple(unsigned p_index,const GUID & p_node,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) { if (p_node == pfc::guid_null) context_command(p_index,p_data,p_caller); } virtual bool item_is_mappable_shortcut(unsigned p_index) { return true; } virtual bool get_display_data(unsigned n,const pfc::list_base_const_t<metadb_handle_ptr> & data,pfc::string_base & p_out,unsigned & displayflags,const GUID & caller) { bool rv = false; assert(n>=0 && n<get_num_items()); if (data.get_count()>0) { rv = context_get_display(n,data,p_out,displayflags,caller); } return rv; } public: //! Same as contextmenu_item_node::t_flags. enum t_flags { FLAG_CHECKED = 1, FLAG_DISABLED = 2, FLAG_GRAYED = 4, FLAG_DISABLED_GRAYED = FLAG_DISABLED|FLAG_GRAYED, }; virtual t_enabled_state get_enabled_state(unsigned p_index) {return contextmenu_item::DEFAULT_ON;} virtual unsigned get_num_items()=0; virtual void get_item_name(unsigned p_index,pfc::string_base & p_out)=0; virtual void get_item_default_path(unsigned p_index,pfc::string_base & p_out) = 0; virtual void context_command(unsigned p_index,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID& p_caller)=0; virtual bool context_get_display(unsigned p_index,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,pfc::string_base & p_out,unsigned & p_displayflags,const GUID & p_caller) { PFC_ASSERT(p_index>=0 && p_index<get_num_items()); get_item_name(p_index,p_out); return true; } virtual GUID get_item_guid(unsigned p_index) = 0; virtual bool get_item_description(unsigned p_index,pfc::string_base & p_out) = 0; }; template<typename T> class contextmenu_item_factory_t : public service_factory_single_t<T> {}; #define DECLARE_CONTEXT_MENU_ITEM(P_CLASSNAME,P_NAME,P_DEFAULTPATH,P_FUNC,P_GUID,P_DESCRIPTION) \ namespace { \ class P_CLASSNAME : public contextmenu_item_simple { \ public: \ unsigned get_num_items() {return 1;} \ void get_item_name(unsigned p_index,pfc::string_base & p_out) {p_out = P_NAME;} \ void get_item_default_path(unsigned p_index,pfc::string_base & p_out) {p_out = P_DEFAULTPATH;} \ void context_command(unsigned p_index,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID& p_caller) {P_FUNC(p_data);} \ GUID get_item_guid(unsigned p_index) {return P_GUID;} \ bool get_item_description(unsigned p_index,pfc::string_base & p_out) {if (P_DESCRIPTION[0] == 0) return false;p_out = P_DESCRIPTION; return true;} \ }; \ static contextmenu_item_factory_t<P_CLASSNAME> g_##P_CLASSNAME##_factory; \ } #endif //_FOOBAR2000_MENU_ITEM_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/contextmenu_manager.h ================================================ #ifndef _FOOBAR2000_MENU_MANAGER_H_ #define _FOOBAR2000_MENU_MANAGER_H_ class NOVTABLE keyboard_shortcut_manager : public service_base { public: static bool g_get(service_ptr_t<keyboard_shortcut_manager> & p_out) {return service_enum_create_t(p_out,0);} enum shortcut_type { TYPE_MAIN, TYPE_CONTEXT, TYPE_CONTEXT_PLAYLIST, TYPE_CONTEXT_NOW_PLAYING, }; virtual bool process_keydown(shortcut_type type,const pfc::list_base_const_t<metadb_handle_ptr> & data,unsigned keycode)=0; virtual bool process_keydown_ex(shortcut_type type,const pfc::list_base_const_t<metadb_handle_ptr> & data,unsigned keycode,const GUID & caller)=0; bool on_keydown(shortcut_type type,WPARAM wp); bool on_keydown_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller); bool on_keydown_auto(WPARAM wp); bool on_keydown_auto_playlist(WPARAM wp); bool on_keydown_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller); virtual bool get_key_description_for_action(const GUID & p_command,const GUID & p_subcommand, pfc::string_base & out, shortcut_type type, bool is_global)=0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(keyboard_shortcut_manager); /* usage: in a windowproc: case WM_KEYDOWN: keyboard_shortcut_manager::get()->on_keydown(wparam); break; case WM_SYSKEYDOWN: keyboard_shortcut_manager::get()->on_keydown(wparam); break; return value is true if key got translated to one of user-configured actions, false if not */ }; class NOVTABLE contextmenu_node { public: virtual contextmenu_item_node::t_type get_type()=0; virtual const char * get_name()=0; virtual t_size get_num_children()=0;//TYPE_POPUP only virtual contextmenu_node * get_child(t_size n)=0;//TYPE_POPUP only virtual unsigned get_display_flags()=0;//TYPE_COMMAND/TYPE_POPUP only, see contextmenu_item::FLAG_* virtual unsigned get_id()=0;//TYPE_COMMAND only, returns zero-based index (helpful for win32 menu command ids) virtual void execute()=0;//TYPE_COMMAND only virtual bool get_description(pfc::string_base & out)=0;//TYPE_COMMAND only virtual bool get_full_name(pfc::string_base & out)=0;//TYPE_COMMAND only virtual void * get_glyph()=0;//RESERVED, do not use protected: contextmenu_node() {} ~contextmenu_node() {} }; class NOVTABLE contextmenu_manager : public service_base { public: enum { FLAG_SHOW_SHORTCUTS = 1, FLAG_SHOW_SHORTCUTS_GLOBAL = 2, }; virtual void init_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,unsigned flags)=0;//flags - see FLAG_* above virtual void init_context_playlist(unsigned flags)=0; virtual contextmenu_node * get_root()=0;//releasing contextmenu_manager service releaases nodes; root may be null in case of error or something virtual contextmenu_node * find_by_id(unsigned id)=0; virtual void set_shortcut_preference(const keyboard_shortcut_manager::shortcut_type * data,unsigned count)=0; static void g_create(service_ptr_t<contextmenu_manager> & p_out) {p_out = standard_api_create_t<contextmenu_manager>();} #ifdef WIN32 static void win32_build_menu(HMENU menu,contextmenu_node * parent,int base_id,int max_id);//menu item identifiers are base_id<=N<base_id+max_id (if theres too many items, they will be clipped) static void win32_run_menu_context(HWND parent,const pfc::list_base_const_t<metadb_handle_ptr> & data, const POINT * pt = 0,unsigned flags = 0); static void win32_run_menu_context_playlist(HWND parent,const POINT * pt = 0,unsigned flags = 0); void win32_run_menu_popup(HWND parent,const POINT * pt = 0); void win32_build_menu(HMENU menu,int base_id,int max_id) {win32_build_menu(menu,get_root(),base_id,max_id);} #endif virtual void init_context_ex(const pfc::list_base_const_t<metadb_handle_ptr> & data,unsigned flags,const GUID & caller)=0; virtual bool init_context_now_playing(unsigned flags)=0;//returns false if not playing bool execute_by_id(unsigned id); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(contextmenu_manager); }; #endif //_FOOBAR2000_MENU_MANAGER_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/core_api.h ================================================ #ifndef _CORE_API_H_ #define _CORE_API_H_ namespace core_api { //! Exception thrown by APIs locked to main app thread when called from another thread. PFC_DECLARE_EXCEPTION(exception_wrong_thread,pfc::exception_bug_check,"This method can be called only from the main thread"); //! Retrieves HINSTANCE of calling DLL. HINSTANCE get_my_instance(); //! Retrieves filename of calling dll, excluding extension, e.g. "foo_asdf" const char * get_my_file_name(); //! Retrieves full path of calling dll, e.g. file://c:\blah\foobar2000\foo_asdf.dll const char * get_my_full_path(); //! Retrieves main app window. WARNING: this is provided for parent of dialog windows and such only; using it for anything else (such as hooking windowproc to alter app behaviors) is absolutely illegal. HWND get_main_window(); //! Tests whether services are available at this time. They are not available only during DLL startup or shutdown (e.g. inside static object constructors or destructors). bool are_services_available(); //! Tests whether calling thread is main app thread, and shows diagnostic message in debugger output if it's not. bool assert_main_thread(); //! Throws exception_wrong_thread if calling thread is not main app thread. void ensure_main_thread(); //! Returns true if calling thread is main app thread, false otherwise. bool is_main_thread(); //! Returns whether the app is currently shutting down. bool is_shutting_down(); //! Returns whether the app is currently initializing. bool is_initializing(); //! Returns filesystem path to directory with user settings, e.g. file://c:\documents_and_settings\username\blah\foobar2000 const char * get_profile_path(); }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/coreversion.h ================================================ #ifndef _COREVERSION_H_ #define _COREVERSION_H_ class NOVTABLE core_version_info : public service_base { public: virtual const char * get_version_string() = 0; static const char * g_get_version_string() {return static_api_ptr_t<core_version_info>()->get_version_string();} FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(core_version_info); }; struct t_core_version_data { t_uint32 m_major, m_minor1, m_minor2, m_minor3; }; //! New (0.9.4.2) class NOVTABLE core_version_info_v2 : public core_version_info { public: virtual const char * get_name() = 0;//"foobar2000" virtual const char * get_version_as_text() = 0;//"N.N.N.N" virtual t_core_version_data get_version() = 0; FB2K_MAKE_SERVICE_INTERFACE(core_version_info_v2, core_version_info); }; #endif //_COREVERSION_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/dsp.cpp ================================================ #include "foobar2000.h" #include <math.h> t_size dsp_chunk_list_impl::get_count() const {return m_data.get_count();} audio_chunk * dsp_chunk_list_impl::get_item(t_size n) const {return n>=0 && n<m_data.get_count() ? &*m_data[n] : 0;} void dsp_chunk_list_impl::remove_by_idx(t_size idx) { if (idx>=0 && idx<m_data.get_count()) m_recycled.add_item(m_data.remove_by_idx(idx)); } void dsp_chunk_list_impl::remove_mask(const bit_array & mask) { t_size n, m = m_data.get_count(); for(n=0;n<m;n++) if (mask[m]) m_recycled.add_item(m_data[n]); m_data.remove_mask(mask); } audio_chunk * dsp_chunk_list_impl::insert_item(t_size idx,t_size hint_size) { t_size max = get_count(); if (idx<0) idx=0; else if (idx>max) idx = max; pfc::rcptr_t<audio_chunk> ret; if (m_recycled.get_count()>0) { t_size best; if (hint_size>0) { best = 0; t_size best_found = m_recycled[0]->get_data_size(), n, total = m_recycled.get_count(); for(n=1;n<total;n++) { if (best_found==hint_size) break; t_size size = m_recycled[n]->get_data_size(); int delta_old = abs((int)best_found - (int)hint_size), delta_new = abs((int)size - (int)hint_size); if (delta_new < delta_old) { best_found = size; best = n; } } } else best = m_recycled.get_count()-1; ret = m_recycled.remove_by_idx(best); ret->set_sample_count(0); ret->set_channels(0); ret->set_srate(0); } else ret = pfc::rcnew_t<audio_chunk_impl>(); if (idx==max) m_data.add_item(ret); else m_data.insert_item(ret,idx); return &*ret; } void dsp_chunk_list::remove_bad_chunks() { bool blah = false; t_size idx; for(idx=0;idx<get_count();) { audio_chunk * chunk = get_item(idx); if (!chunk->is_valid()) { chunk->reset(); remove_by_idx(idx); blah = true; } else idx++; } if (blah) console::info("one or more bad chunks removed from dsp chunk list"); } bool dsp_entry::g_instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) { service_ptr_t<dsp_entry> ptr; if (!g_get_interface(ptr,p_preset.get_owner())) return false; return ptr->instantiate(p_out,p_preset); } bool dsp_entry::g_instantiate_default(service_ptr_t<dsp> & p_out,const GUID & p_guid) { service_ptr_t<dsp_entry> ptr; if (!g_get_interface(ptr,p_guid)) return false; dsp_preset_impl preset; if (!ptr->get_default_preset(preset)) return false; return ptr->instantiate(p_out,preset); } bool dsp_entry::g_name_from_guid(pfc::string_base & p_out,const GUID & p_guid) { service_ptr_t<dsp_entry> ptr; if (!g_get_interface(ptr,p_guid)) return false; ptr->get_name(p_out); return true; } bool dsp_entry::g_dsp_exists(const GUID & p_guid) { service_ptr_t<dsp_entry> blah; return g_get_interface(blah,p_guid); } bool dsp_entry::g_get_default_preset(dsp_preset & p_out,const GUID & p_guid) { service_ptr_t<dsp_entry> ptr; if (!g_get_interface(ptr,p_guid)) return false; return ptr->get_default_preset(p_out); } void dsp_chain_config::contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const { t_size n, count = get_count(); p_stream->write_lendian_t(count,p_abort); for(n=0;n<count;n++) { get_item(n).contents_to_stream(p_stream,p_abort); } } void dsp_chain_config::contents_from_stream(stream_reader * p_stream,abort_callback & p_abort) { t_uint32 n,count; remove_all(); p_stream->read_lendian_t(count,p_abort); dsp_preset_impl temp; for(n=0;n<count;n++) { temp.contents_from_stream(p_stream,p_abort); add_item(temp); } } bool cfg_dsp_chain_config::get_data(dsp_chain_config & p_data) const { p_data.copy(m_data); return true; } void cfg_dsp_chain_config::set_data(const dsp_chain_config & p_data) { m_data.copy(p_data); } void cfg_dsp_chain_config::reset() { m_data.remove_all(); } void cfg_dsp_chain_config::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { m_data.contents_to_stream(p_stream,p_abort); } void cfg_dsp_chain_config::set_data_raw(stream_reader * p_stream,t_size,abort_callback & p_abort) { m_data.contents_from_stream(p_stream,p_abort); } void dsp_chain_config::remove_item(t_size p_index) { remove_mask(bit_array_one(p_index)); } void dsp_chain_config::add_item(const dsp_preset & p_data) { insert_item(p_data,get_count()); } void dsp_chain_config::remove_all() { remove_mask(bit_array_true()); } void dsp_chain_config::instantiate(service_list_t<dsp> & p_out) { p_out.remove_all(); t_size n, m = get_count(); for(n=0;n<m;n++) { service_ptr_t<dsp> temp; if (dsp_entry::g_instantiate(temp,get_item(n))) p_out.add_item(temp); } } t_size dsp_chain_config_impl::get_count() const { return m_data.get_count(); } const dsp_preset & dsp_chain_config_impl::get_item(t_size p_index) const { return *m_data[p_index]; } void dsp_chain_config_impl::replace_item(const dsp_preset & p_data,t_size p_index) { *m_data[p_index] = p_data; } void dsp_chain_config_impl::insert_item(const dsp_preset & p_data,t_size p_index) { m_data.insert_item(new dsp_preset_impl(p_data),p_index); } void dsp_chain_config_impl::remove_mask(const bit_array & p_mask) { m_data.delete_mask(p_mask); } dsp_chain_config_impl::~dsp_chain_config_impl() { m_data.delete_all(); } void dsp_preset::contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const { t_size size = get_data_size(); p_stream->write_lendian_t(get_owner(),p_abort); p_stream->write_lendian_t(size,p_abort); if (size > 0) { p_stream->write_object(get_data(),size,p_abort); } } void dsp_preset::contents_from_stream(stream_reader * p_stream,abort_callback & p_abort) { t_uint32 size; GUID guid; p_stream->read_lendian_t(guid,p_abort); set_owner(guid); p_stream->read_lendian_t(size,p_abort); if (size > 1024*1024*32) throw exception_io_data(); set_data_from_stream(p_stream,size,p_abort); } void dsp_preset::g_contents_from_stream_skip(stream_reader * p_stream,abort_callback & p_abort) { t_uint32 size; GUID guid; p_stream->read_lendian_t(guid,p_abort); p_stream->read_lendian_t(size,p_abort); if (size > 1024*1024*32) throw exception_io_data(); p_stream->skip_object(size,p_abort); } void dsp_preset_impl::set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) { m_data.set_size(p_bytes); if (p_bytes > 0) p_stream->read_object(m_data.get_ptr(),p_bytes,p_abort); } void dsp_chain_config::copy(const dsp_chain_config & p_source) { remove_all(); t_size n, m = p_source.get_count(); for(n=0;n<m;n++) add_item(p_source.get_item(n)); } bool dsp_entry::g_have_config_popup(const GUID & p_guid) { service_ptr_t<dsp_entry> entry; if (!g_get_interface(entry,p_guid)) return false; return entry->have_config_popup(); } bool dsp_entry::g_have_config_popup(const dsp_preset & p_preset) { return g_have_config_popup(p_preset.get_owner()); } bool dsp_entry::g_show_config_popup(dsp_preset & p_preset,HWND p_parent) { service_ptr_t<dsp_entry> entry; if (!g_get_interface(entry,p_preset.get_owner())) return false; return entry->show_config_popup(p_preset,p_parent); } void dsp_entry::g_show_config_popup_v2(const dsp_preset & p_preset,HWND p_parent,dsp_preset_edit_callback & p_callback) { service_ptr_t<dsp_entry> entry; if (g_get_interface(entry,p_preset.get_owner())) { service_ptr_t<dsp_entry_v2> entry_v2; if (entry->service_query_t(entry_v2)) { entry_v2->show_config_popup_v2(p_preset,p_parent,p_callback); } else { dsp_preset_impl temp(p_preset); if (entry->show_config_popup(temp,p_parent)) p_callback.on_preset_changed(temp); } } } bool dsp_entry::g_get_interface(service_ptr_t<dsp_entry> & p_out,const GUID & p_guid) { service_ptr_t<dsp_entry> ptr; service_enum_t<dsp_entry> e; e.reset(); while(e.next(ptr)) { if (ptr->get_guid() == p_guid) { p_out = ptr; return true; } } return false; } bool resampler_entry::g_get_interface(service_ptr_t<resampler_entry> & p_out,unsigned p_srate_from,unsigned p_srate_to) { service_ptr_t<dsp_entry> ptr_dsp; service_ptr_t<resampler_entry> ptr_resampler; service_enum_t<dsp_entry> e; e.reset(); float found_priority = 0; service_ptr_t<resampler_entry> found; while(e.next(ptr_dsp)) { if (ptr_dsp->service_query_t(ptr_resampler)) { if (p_srate_from == 0 || ptr_resampler->is_conversion_supported(p_srate_from,p_srate_to)) { float priority = ptr_resampler->get_priority(); if (found.is_empty() || priority > found_priority) { found = ptr_resampler; found_priority = priority; } } } } if (found.is_empty()) return false; p_out = found; return true; } bool resampler_entry::g_create_preset(dsp_preset & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale) { service_ptr_t<resampler_entry> entry; if (!g_get_interface(entry,p_srate_from,p_srate_to)) return false; return entry->create_preset(p_out,p_srate_to,p_qualityscale); } bool resampler_entry::g_create(service_ptr_t<dsp> & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale) { service_ptr_t<resampler_entry> entry; if (!g_get_interface(entry,p_srate_from,p_srate_to)) return false; dsp_preset_impl preset; if (!entry->create_preset(preset,p_srate_to,p_qualityscale)) return false; return entry->instantiate(p_out,preset); } void dsp_chain_config::get_name_list(pfc::string_base & p_out) const { const t_size count = get_count(); bool added = false; for(unsigned n=0;n<count;n++) { service_ptr_t<dsp_entry> ptr; if (dsp_entry::g_get_interface(ptr,get_item(n).get_owner())) { if (added) p_out += ", "; added = true; pfc::string8 temp; ptr->get_name(temp); p_out += temp; } } } void dsp::run_abortable(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) { service_ptr_t<dsp_v2> this_v2; if (this->service_query_t(this_v2)) this_v2->run_v2(p_chunk_list,p_cur_file,p_flags,p_abort); else run(p_chunk_list,p_cur_file,p_flags); } namespace { class dsp_preset_edit_callback_impl : public dsp_preset_edit_callback { public: dsp_preset_edit_callback_impl(dsp_preset & p_data) : m_data(p_data) {} void on_preset_changed(const dsp_preset & p_data) {m_data = p_data;} private: dsp_preset & m_data; }; }; bool dsp_entry_v2::show_config_popup(dsp_preset & p_data,HWND p_parent) { PFC_ASSERT(p_data.get_owner() == get_guid()); dsp_preset_impl temp(p_data); show_config_popup_v2(p_data,p_parent,dsp_preset_edit_callback_impl(temp)); PFC_ASSERT(temp.get_owner() == get_guid()); if (temp == p_data) return false; p_data = temp; return true; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/dsp.h ================================================ class NOVTABLE dsp_chunk_list { public: virtual t_size get_count() const = 0; virtual audio_chunk * get_item(t_size n) const = 0; virtual void remove_by_idx(t_size idx) = 0; virtual void remove_mask(const bit_array & mask) = 0; virtual audio_chunk * insert_item(t_size idx,t_size hint_size=0) = 0; audio_chunk * add_item(t_size hint_size=0) {return insert_item(get_count(),hint_size);} void remove_all() {remove_mask(bit_array_true());} double get_duration() { double rv = 0; t_size n,m = get_count(); for(n=0;n<m;n++) rv += get_item(n)->get_duration(); return rv; } void add_chunk(const audio_chunk * chunk) { audio_chunk * dst = insert_item(get_count(),chunk->get_data_length()); if (dst) dst->copy(*chunk); } void remove_bad_chunks(); protected: dsp_chunk_list() {} ~dsp_chunk_list() {} }; class dsp_chunk_list_impl : public dsp_chunk_list//implementation { pfc::list_t<pfc::rcptr_t<audio_chunk> > m_data, m_recycled; public: t_size get_count() const; audio_chunk * get_item(t_size n) const; void remove_by_idx(t_size idx); void remove_mask(const bit_array & mask); audio_chunk * insert_item(t_size idx,t_size hint_size=0); }; //! Instance of a DSP.\n //! Implementation: Derive from dsp_impl_base instead of deriving from dsp directly.\n //! Instantiation: Use dsp_entry static helper methods to instantiate DSPs, or dsp_chain_config / dsp_manager to deal with entire DSP chains. class NOVTABLE dsp : public service_base { public: enum { //! Flush whatever you need to when tracks change. END_OF_TRACK = 1, //! Flush everything. FLUSH = 2 }; //! @param p_chunk_list List of chunks to process. The implementation may alter the list in any way, inserting chunks of different sample rate / channel configuration etc. //! @param p_cur_file Optional, location of currently decoded file. May be null. //! @param p_flags Flags. Can be null, or a combination of END_OF_TRACK and FLUSH constants. virtual void run(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags)=0; //! Flushes the DSP (reinitializes / drops any buffered data). Called after seeking, etc. virtual void flush() = 0; //! Retrieves amount of data buffered by the DSP, for syncing visualisation. //! @returns Amount of buffered audio data, in seconds. virtual double get_latency() = 0; //! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n //! Signaling this will force-flush any DSPs placed before this DSP so when it gets END_OF_TRACK, relevant chunks contain last samples of the track.\n //! Signaling this will often break regular gapless playback so don't use it unless you have reasons to. virtual bool need_track_change_mark() = 0; void run_abortable(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort); FB2K_MAKE_SERVICE_INTERFACE(dsp,service_base); }; //! Backwards-compatible extension to dsp interface, allows abortable operation. Introduced in 0.9.2. class NOVTABLE dsp_v2 : public dsp { public: //! Abortable version of dsp::run(). See dsp::run() for descriptions of parameters. virtual void run_v2(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) = 0; private: void run(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags) { run_v2(p_chunk_list,p_cur_file,p_flags,abort_callback_impl()); } FB2K_MAKE_SERVICE_INTERFACE(dsp_v2,dsp); }; //! Helper class for implementing dsps. You should derive from dsp_impl_base instead of from dsp directly.\n //! The dsp_impl_base_t template allows you to use a custom interface class as a base class for your implementation, in case you provide extended functionality.\n //! Use dsp_factory_t<> template to register your dsp implementation. //! The implementation - as required by dsp_factory_t<> template - must also provide following methods:\n //! A constructor taking const dsp_preset&, initializing the DSP with specified preset data.\n //! static void g_get_name(pfc::string_base &); - retrieving human-readable name of the DSP to display.\n //! static bool g_get_default_preset(dsp_preset &); - retrieving default preset for this DSP. Return value is reserved for future use and should always be true.\n //! static GUID g_get_guid(); - retrieving GUID of your DSP implementation, to be used to identify it when storing DSP chain configuration.\n //! static bool g_have_config_popup(); - retrieving whether your DSP implementation supplies a popup dialog for configuring it.\n //! static void g_show_config_popup(const dsp_preset & p_data,HWND p_parent, dsp_preset_edit_callback & p_callback); - displaying your DSP's settings dialog; called only when g_have_config_popup() returns true; call p_callback.on_preset_changed() whenever user has made adjustments to the preset data.\n template<class t_baseclass> class dsp_impl_base_t : public t_baseclass { private: typedef dsp_impl_base_t<t_baseclass> t_self; dsp_chunk_list * m_list; t_size m_chunk_ptr; metadb_handle* m_cur_file; void run_v2(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort); protected: bool get_cur_file(metadb_handle_ptr & p_out) {p_out = m_cur_file; return p_out.is_valid();}// call only from on_chunk / on_endoftrack (on_endoftrack will give info on track being finished); may return null !! dsp_impl_base_t() : m_list(NULL), m_cur_file(NULL), m_chunk_ptr(0) {} audio_chunk * insert_chunk(t_size p_hint_size = 0) //call only from on_endoftrack / on_endofplayback / on_chunk {//hint_size - optional, amout of buffer space you want to use PFC_ASSERT(m_list != NULL); return m_list->insert_item(m_chunk_ptr++,p_hint_size); } //! To be overridden by a DSP implementation.\n //! Called on track change. You can use insert_chunk() to dump any data you have to flush. \n //! Note that you must implement need_track_change_mark() to return true if you need this method called. virtual void on_endoftrack(abort_callback & p_abort) = 0; //! To be overridden by a DSP implementation.\n //! Called at the end of played stream, typically at the end of last played track, to allow the DSP to return all data it has buffered-ahead.\n //! Use insert_chunk() to return any data you have buffered.\n //! Note that this call does not imply that the DSP will be destroyed next. \n //! This is also called on track changes if some DSP placed after your DSP requests track change marks. virtual void on_endofplayback(abort_callback & p_abort) = 0; //! To be overridden by a DSP implementation.\n //! Processes a chunk of audio data.\n //! You can call insert_chunk() from inside on_chunk() to insert any audio data before currently processed chunk.\n //! @param p_chunk Current chunk being processed. You can alter it in any way you like. //! @returns True to keep p_chunk (with alterations made inside on_chunk()) in the stream, false to remove it. virtual bool on_chunk(audio_chunk * p_chunk,abort_callback & p_abort) = 0; public: //! To be overridden by a DSP implementation.\n //! Flushes the DSP (reinitializes / drops any buffered data). Called after seeking, etc. virtual void flush() = 0; //! To be overridden by a DSP implementation.\n //! Retrieves amount of data buffered by the DSP, for syncing visualisation. //! @returns Amount of buffered audio data, in seconds. virtual double get_latency() = 0; //! To be overridden by a DSP implementation.\n //! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n //! Signaling this will force-flush any DSPs placed before this DSP so when it gets on_endoftrack(), relevant chunks contain last samples of the track.\n //! Signaling this may interfere with gapless playback in certain scenarios (forces flush of DSPs placed before you) so don't use it unless you have reasons to. virtual bool need_track_change_mark() = 0; private: dsp_impl_base_t(const t_self&) {throw pfc::exception_bug_check();} const t_self & operator=(const t_self &) {throw pfc::exception_bug_check();} }; template<class t_baseclass> void dsp_impl_base_t<t_baseclass>::run_v2(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) { pfc::vartoggle_t<dsp_chunk_list*> l_list_toggle(m_list,p_list); pfc::vartoggle_t<metadb_handle*> l_cur_file_toggle(m_cur_file,p_cur_file.get_ptr()); for(m_chunk_ptr = 0;m_chunk_ptr<m_list->get_count();m_chunk_ptr++) { audio_chunk * c = m_list->get_item(m_chunk_ptr); if (c->is_empty() || !on_chunk(c,p_abort)) m_list->remove_by_idx(m_chunk_ptr--); } if (p_flags & FLUSH) { on_endofplayback(p_abort); } else if (p_flags & END_OF_TRACK) { if (need_track_change_mark()) on_endoftrack(p_abort); } } typedef dsp_impl_base_t<dsp_v2> dsp_impl_base; class NOVTABLE dsp_preset { public: virtual GUID get_owner() const = 0; virtual void set_owner(const GUID & p_owner) = 0; virtual const void * get_data() const = 0; virtual t_size get_data_size() const = 0; virtual void set_data(const void * p_data,t_size p_data_size) = 0; virtual void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) = 0; const dsp_preset & operator=(const dsp_preset & p_source) {copy(p_source); return *this;} void copy(const dsp_preset & p_source) {set_owner(p_source.get_owner());set_data(p_source.get_data(),p_source.get_data_size());} void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const; void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort); static void g_contents_from_stream_skip(stream_reader * p_stream,abort_callback & p_abort); bool operator==(const dsp_preset & p_other) const { if (get_owner() != p_other.get_owner()) return false; if (get_data_size() != p_other.get_data_size()) return false; if (memcmp(get_data(),p_other.get_data(),get_data_size()) != 0) return false; return true; } bool operator!=(const dsp_preset & p_other) const { return !(*this == p_other); } protected: dsp_preset() {} ~dsp_preset() {} }; class dsp_preset_writer : public stream_writer { public: void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { p_abort.check(); m_data.append_fromptr((const t_uint8 *) p_buffer,p_bytes); } void flush(dsp_preset & p_preset) { p_preset.set_data(m_data.get_ptr(),m_data.get_size()); m_data.set_size(0); } private: pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> m_data; }; class dsp_preset_reader : public stream_reader { public: dsp_preset_reader() : m_walk(0) {} dsp_preset_reader(const dsp_preset_reader & p_source) : m_walk(0) {*this = p_source;} void init(const dsp_preset & p_preset) { m_data.set_data_fromptr( (const t_uint8*) p_preset.get_data(), p_preset.get_data_size() ); m_walk = 0; } t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { p_abort.check(); t_size todo = pfc::min_t<t_size>(p_bytes,m_data.get_size()-m_walk); memcpy(p_buffer,m_data.get_ptr()+m_walk,todo); m_walk += todo; return todo; } bool is_finished() {return m_walk == m_data.get_size();} private: t_size m_walk; pfc::array_t<t_uint8> m_data; }; class dsp_preset_impl : public dsp_preset { public: dsp_preset_impl() {} dsp_preset_impl(const dsp_preset_impl & p_source) {copy(p_source);} dsp_preset_impl(const dsp_preset & p_source) {copy(p_source);} const dsp_preset_impl& operator=(const dsp_preset_impl & p_source) {copy(p_source); return *this;} const dsp_preset_impl& operator=(const dsp_preset & p_source) {copy(p_source); return *this;} GUID get_owner() const {return m_owner;} void set_owner(const GUID & p_owner) {m_owner = p_owner;} const void * get_data() const {return m_data.get_ptr();} t_size get_data_size() const {return m_data.get_size();} void set_data(const void * p_data,t_size p_data_size) {m_data.set_data_fromptr((const t_uint8*)p_data,p_data_size);} void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort); private: GUID m_owner; pfc::array_t<t_uint8> m_data; }; class NOVTABLE dsp_preset_edit_callback { public: virtual void on_preset_changed(const dsp_preset &) = 0; private: dsp_preset_edit_callback(const dsp_preset_edit_callback&) {throw pfc::exception_not_implemented();} const dsp_preset_edit_callback & operator=(const dsp_preset_edit_callback &) {throw pfc::exception_not_implemented();} protected: dsp_preset_edit_callback() {} ~dsp_preset_edit_callback() {} }; class NOVTABLE dsp_entry : public service_base { public: virtual void get_name(pfc::string_base & p_out) = 0; virtual bool get_default_preset(dsp_preset & p_out) = 0; virtual bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) = 0; virtual GUID get_guid() = 0; virtual bool have_config_popup() = 0; virtual bool show_config_popup(dsp_preset & p_data,HWND p_parent) = 0; static bool g_get_interface(service_ptr_t<dsp_entry> & p_out,const GUID & p_guid); static bool g_instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset); static bool g_instantiate_default(service_ptr_t<dsp> & p_out,const GUID & p_guid); static bool g_name_from_guid(pfc::string_base & p_out,const GUID & p_guid); static bool g_dsp_exists(const GUID & p_guid); static bool g_get_default_preset(dsp_preset & p_out,const GUID & p_guid); static bool g_have_config_popup(const GUID & p_guid); static bool g_have_config_popup(const dsp_preset & p_preset); static bool g_show_config_popup(dsp_preset & p_preset,HWND p_parent); static void g_show_config_popup_v2(const dsp_preset & p_preset,HWND p_parent,dsp_preset_edit_callback & p_callback); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_entry); }; class NOVTABLE dsp_entry_v2 : public dsp_entry { public: virtual void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) = 0; private: bool show_config_popup(dsp_preset & p_data,HWND p_parent); FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v2,dsp_entry); }; template<class T,class t_entry = dsp_entry> class dsp_entry_impl_nopreset_t : public t_entry { public: void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);} bool get_default_preset(dsp_preset & p_out) { p_out.set_owner(T::g_get_guid()); p_out.set_data(0,0); return true; } bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) { if (p_preset.get_owner() == T::g_get_guid() && p_preset.get_data_size() == 0) { p_out = new service_impl_t<T>(); return p_out.is_valid(); } else return false; } GUID get_guid() {return T::g_get_guid();} bool have_config_popup() {return false;} bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return false;} }; template<class T, class t_entry = dsp_entry_v2> class dsp_entry_impl_t : public t_entry { public: void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);} bool get_default_preset(dsp_preset & p_out) {return T::g_get_default_preset(p_out);} bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) { if (p_preset.get_owner() == T::g_get_guid()) { p_out = new service_impl_t<T>(p_preset); return true; } else return false; } GUID get_guid() {return T::g_get_guid();} bool have_config_popup() {return T::g_have_config_popup();} bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return T::g_show_config_popup(p_data,p_parent);} //void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) {T::g_show_config_popup(p_data,p_parent,p_callback);} }; template<class T, class t_entry = dsp_entry_v2> class dsp_entry_v2_impl_t : public t_entry { public: void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);} bool get_default_preset(dsp_preset & p_out) {return T::g_get_default_preset(p_out);} bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) { if (p_preset.get_owner() == T::g_get_guid()) { p_out = new service_impl_t<T>(p_preset); return true; } else return false; } GUID get_guid() {return T::g_get_guid();} bool have_config_popup() {return T::g_have_config_popup();} //bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return T::g_show_config_popup(p_data,p_parent);} void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) {T::g_show_config_popup(p_data,p_parent,p_callback);} }; template<class T> class dsp_factory_nopreset_t : public service_factory_single_t<dsp_entry_impl_nopreset_t<T> > {}; template<class T> class dsp_factory_t : public service_factory_single_t<dsp_entry_v2_impl_t<T> > {}; class NOVTABLE dsp_chain_config { public: virtual t_size get_count() const = 0; virtual const dsp_preset & get_item(t_size p_index) const = 0; virtual void replace_item(const dsp_preset & p_data,t_size p_index) = 0; virtual void insert_item(const dsp_preset & p_data,t_size p_index) = 0; virtual void remove_mask(const bit_array & p_mask) = 0; void remove_item(t_size p_index); void remove_all(); void add_item(const dsp_preset & p_data); void copy(const dsp_chain_config & p_source); const dsp_chain_config & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;} void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const; void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort); void instantiate(service_list_t<dsp> & p_out); void get_name_list(pfc::string_base & p_out) const; }; class dsp_chain_config_impl : public dsp_chain_config { public: dsp_chain_config_impl() {} dsp_chain_config_impl(const dsp_chain_config & p_source) {copy(p_source);} dsp_chain_config_impl(const dsp_chain_config_impl & p_source) {copy(p_source);} t_size get_count() const; const dsp_preset & get_item(t_size p_index) const; void replace_item(const dsp_preset & p_data,t_size p_index); void insert_item(const dsp_preset & p_data,t_size p_index); void remove_mask(const bit_array & p_mask); const dsp_chain_config_impl & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;} const dsp_chain_config_impl & operator=(const dsp_chain_config_impl & p_source) {copy(p_source); return *this;} ~dsp_chain_config_impl(); private: pfc::ptr_list_t<dsp_preset_impl> m_data; }; class cfg_dsp_chain_config : public cfg_var { protected: void get_data_raw(stream_writer * p_stream,abort_callback & p_abort); void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort); public: void reset(); inline cfg_dsp_chain_config(const GUID & p_guid) : cfg_var(p_guid) {} t_size get_count() const {return m_data.get_count();} const dsp_preset & get_item(t_size p_index) const {return m_data.get_item(p_index);} bool get_data(dsp_chain_config & p_data) const; void set_data(const dsp_chain_config & p_data); private: dsp_chain_config_impl m_data; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/dsp_manager.cpp ================================================ #include "foobar2000.h" void dsp_manager::close() { m_chain.remove_all(); m_config_changed = true; } void dsp_manager::set_config( const dsp_chain_config & p_data ) { //dsp_chain_config::g_instantiate(m_dsp_list,p_data); m_config.copy(p_data); m_config_changed = true; } void dsp_manager::dsp_run(t_dsp_chain::const_iterator p_iter,dsp_chunk_list * p_list,const metadb_handle_ptr & cur_file,unsigned flags,double & latency,abort_callback & p_abort) { p_list->remove_bad_chunks(); TRACK_CODE("dsp::run",p_iter->m_dsp->run_abortable(p_list,cur_file,flags,p_abort)); TRACK_CODE("dsp::get_latency",latency += p_iter->m_dsp->get_latency()); } double dsp_manager::run(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,unsigned p_flags,abort_callback & p_abort) { TRACK_CALL_TEXT("dsp_manager::run"); try { fpu_control_default l_fpu_control; double latency=0; bool done = false; t_dsp_chain::const_iterator flush_mark; if ((p_flags & dsp::END_OF_TRACK) && ! (p_flags & dsp::FLUSH)) { for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) { if (iter->m_dsp->need_track_change_mark()) flush_mark = iter; } } if (m_config_changed) { t_dsp_chain newchain; bool recycle_available = true; for(t_size n=0;n<m_config.get_count();n++) { service_ptr_t<dsp> temp; const dsp_preset & preset = m_config.get_item(n); if (dsp_entry::g_dsp_exists(preset.get_owner())) { t_dsp_chain::iterator iter = newchain.insert_last(); iter->m_preset = m_config.get_item(n); iter->m_recycle_flag = false; } } //HACK: recycle existing DSPs in a special case when user has apparently only altered settings of one of DSPs. if (newchain.get_count() == m_chain.get_count()) { t_size data_mismatch_count = 0; t_size owner_mismatch_count = 0; t_dsp_chain::iterator iter_src, iter_dst; iter_src = m_chain.first(); iter_dst = newchain.first(); while(iter_src.is_valid() && iter_dst.is_valid()) { if (iter_src->m_preset.get_owner() != iter_dst->m_preset.get_owner()) { owner_mismatch_count++; } else if (iter_src->m_preset != iter_dst->m_preset) { data_mismatch_count++; } ++iter_src; ++iter_dst; } recycle_available = (owner_mismatch_count == 0 && data_mismatch_count <= 1); } else { recycle_available = false; } if (recycle_available) { t_dsp_chain::iterator iter_src, iter_dst; iter_src = m_chain.first(); iter_dst = newchain.first(); while(iter_src.is_valid() && iter_dst.is_valid()) { if (iter_src->m_preset == iter_dst->m_preset) { iter_src->m_recycle_flag = true; iter_dst->m_dsp = iter_src->m_dsp; } ++iter_src; ++iter_dst; } } for(t_dsp_chain::iterator iter = newchain.first(); iter.is_valid(); ++iter) { if (iter->m_dsp.is_empty()) { if (!dsp_entry::g_instantiate(iter->m_dsp,iter->m_preset)) throw pfc::exception_bug_check(); } } if (m_chain.get_count()>0) { bool flushflag = flush_mark.is_valid(); for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) { unsigned flags2 = p_flags; if (iter == flush_mark) flushflag = false; if (flushflag || !iter->m_recycle_flag) flags2|=dsp::FLUSH; dsp_run(iter,p_list,p_cur_file,flags2,latency,p_abort); } done = true; } m_chain = newchain; m_config_changed = false; } if (!done) { bool flushflag = flush_mark.is_valid(); for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) { unsigned flags2 = p_flags; if (iter == flush_mark) flushflag = false; if (flushflag) flags2|=dsp::FLUSH; dsp_run(iter,p_list,p_cur_file,flags2,latency,p_abort); } done = true; } p_list->remove_bad_chunks(); return latency; } catch(...) { p_list->remove_all(); throw; } } void dsp_manager::flush() { for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) { TRACK_CODE("dsp::flush",iter->m_dsp->flush()); } } bool dsp_manager::is_active() {return m_config.get_count()>0;} ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/dsp_manager.h ================================================ struct t_dsp_chain_entry { service_ptr_t<dsp> m_dsp; dsp_preset_impl m_preset; bool m_recycle_flag; }; typedef pfc::chain_list_t<t_dsp_chain_entry> t_dsp_chain; class dsp_manager { public: dsp_manager() : m_config_changed(false) {} void set_config( const dsp_chain_config & p_data ); double run(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,unsigned p_flags,abort_callback & p_abort); void flush(); void close(); bool is_active(); private: t_dsp_chain m_chain; dsp_chain_config_impl m_config; bool m_config_changed; void dsp_run(t_dsp_chain::const_iterator p_iter,dsp_chunk_list * list,const metadb_handle_ptr & cur_file,unsigned flags,double & latency,abort_callback&); dsp_manager(const dsp_manager &) {throw pfc::exception_not_implemented();} const dsp_manager & operator=(const dsp_manager&) {throw pfc::exception_not_implemented();} }; class dsp_config_manager : public service_base { public: virtual void get_core_settings(dsp_chain_config & p_out) = 0; virtual void set_core_settings(const dsp_chain_config & p_data) = 0; virtual bool configure_popup(dsp_chain_config & p_data,HWND p_parent,const char * p_title) = 0; virtual HWND configure_embedded(const dsp_chain_config & p_initdata,HWND p_parent,unsigned p_id,bool p_from_modal) = 0; virtual void configure_embedded_retrieve(HWND wnd,dsp_chain_config & p_data) = 0; virtual void configure_embedded_change(HWND wnd,const dsp_chain_config & p_data) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_config_manager); }; class NOVTABLE dsp_config_callback : public service_base { public: virtual void on_core_settings_change(const dsp_chain_config & p_newdata) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_config_callback); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/file_info.cpp ================================================ #include "foobar2000.h" t_size file_info::meta_find_ex(const char * p_name,t_size p_name_length) const { t_size n, m = meta_get_count(); for(n=0;n<m;n++) { if (pfc::stricmp_ascii_ex(meta_enum_name(n),infinite,p_name,p_name_length) == 0) return n; } return infinite; } bool file_info::meta_exists_ex(const char * p_name,t_size p_name_length) const { return meta_find_ex(p_name,p_name_length) != infinite; } void file_info::meta_remove_field_ex(const char * p_name,t_size p_name_length) { t_size index = meta_find_ex(p_name,p_name_length); if (index!=infinite) meta_remove_index(index); } void file_info::meta_remove_index(t_size p_index) { meta_remove_mask(bit_array_one(p_index)); } void file_info::meta_remove_all() { meta_remove_mask(bit_array_true()); } void file_info::meta_remove_value(t_size p_index,t_size p_value) { meta_remove_values(p_index,bit_array_one(p_value)); } t_size file_info::meta_get_count_by_name_ex(const char * p_name,t_size p_name_length) const { t_size index = meta_find_ex(p_name,p_name_length); if (index == infinite) return 0; return meta_enum_value_count(index); } t_size file_info::info_find_ex(const char * p_name,t_size p_name_length) const { t_size n, m = info_get_count(); for(n=0;n<m;n++) { if (pfc::stricmp_ascii_ex(info_enum_name(n),infinite,p_name,p_name_length) == 0) return n; } return infinite; } bool file_info::info_exists_ex(const char * p_name,t_size p_name_length) const { return info_find_ex(p_name,p_name_length) != infinite; } void file_info::info_remove_index(t_size p_index) { info_remove_mask(bit_array_one(p_index)); } void file_info::info_remove_all() { info_remove_mask(bit_array_true()); } bool file_info::info_remove_ex(const char * p_name,t_size p_name_length) { t_size index = info_find_ex(p_name,p_name_length); if (index != infinite) { info_remove_index(index); return true; } else return false; } void file_info::copy_meta_single(const file_info & p_source,t_size p_index) { copy_meta_single_rename(p_source,p_index,p_source.meta_enum_name(p_index)); } void file_info::copy_meta_single_nocheck(const file_info & p_source,t_size p_index) { const char * name = p_source.meta_enum_name(p_index); t_size n, m = p_source.meta_enum_value_count(p_index); t_size new_index = infinite; for(n=0;n<m;n++) { const char * value = p_source.meta_enum_value(p_index,n); if (n == 0) new_index = meta_set_nocheck(name,value); else meta_add_value(new_index,value); } } void file_info::copy_meta_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length) { t_size index = p_source.meta_find_ex(p_name,p_name_length); if (index != infinite) copy_meta_single(p_source,index); } void file_info::copy_info_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length) { t_size index = p_source.info_find_ex(p_name,p_name_length); if (index != infinite) copy_info_single(p_source,index); } void file_info::copy_meta_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length) { t_size index = p_source.meta_find_ex(p_name,p_name_length); if (index != infinite) copy_meta_single_nocheck(p_source,index); } void file_info::copy_info_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length) { t_size index = p_source.info_find_ex(p_name,p_name_length); if (index != infinite) copy_info_single_nocheck(p_source,index); } void file_info::copy_info_single(const file_info & p_source,t_size p_index) { info_set(p_source.info_enum_name(p_index),p_source.info_enum_value(p_index)); } void file_info::copy_info_single_nocheck(const file_info & p_source,t_size p_index) { info_set_nocheck(p_source.info_enum_name(p_index),p_source.info_enum_value(p_index)); } void file_info::copy_meta(const file_info & p_source) { if (&p_source != this) { meta_remove_all(); t_size n, m = p_source.meta_get_count(); for(n=0;n<m;n++) copy_meta_single_nocheck(p_source,n); } } void file_info::copy_info(const file_info & p_source) { if (&p_source != this) { info_remove_all(); t_size n, m = p_source.info_get_count(); for(n=0;n<m;n++) copy_info_single_nocheck(p_source,n); } } void file_info::copy(const file_info & p_source) { if (&p_source != this) { copy_meta(p_source); copy_info(p_source); set_length(p_source.get_length()); set_replaygain(p_source.get_replaygain()); } } const char * file_info::meta_get_ex(const char * p_name,t_size p_name_length,t_size p_index) const { t_size index = meta_find_ex(p_name,p_name_length); if (index == infinite) return 0; t_size max = meta_enum_value_count(index); if (p_index >= max) return 0; return meta_enum_value(index,p_index); } const char * file_info::info_get_ex(const char * p_name,t_size p_name_length) const { t_size index = info_find_ex(p_name,p_name_length); if (index == infinite) return 0; return info_enum_value(index); } t_int64 file_info::info_get_int(const char * name) const { PFC_ASSERT(pfc::is_valid_utf8(name)); const char * val = info_get(name); if (val==0) return 0; return _atoi64(val); } t_int64 file_info::info_get_length_samples() const { t_int64 ret = 0; double len = get_length(); t_int64 srate = info_get_int("samplerate"); if (srate>0 && len>0) { ret = audio_math::time_to_samples(len,(unsigned)srate); } return ret; } double file_info::info_get_float(const char * name) const { const char * ptr = info_get(name); if (ptr) return pfc::string_to_float(ptr); else return 0; } void file_info::info_set_int(const char * name,t_int64 value) { PFC_ASSERT(pfc::is_valid_utf8(name)); info_set(name,pfc::format_int(value)); } void file_info::info_set_float(const char * name,double value,unsigned precision,bool force_sign,const char * unit) { PFC_ASSERT(pfc::is_valid_utf8(name)); PFC_ASSERT(unit==0 || strlen(unit) <= 64); char temp[128]; pfc::float_to_string(temp,64,value,precision,force_sign); temp[63] = 0; if (unit) { strcat(temp," "); strcat(temp,unit); } info_set(name,temp); } void file_info::info_set_replaygain_album_gain(float value) { replaygain_info temp = get_replaygain(); temp.m_album_gain = value; set_replaygain(temp); } void file_info::info_set_replaygain_album_peak(float value) { replaygain_info temp = get_replaygain(); temp.m_album_peak = value; set_replaygain(temp); } void file_info::info_set_replaygain_track_gain(float value) { replaygain_info temp = get_replaygain(); temp.m_track_gain = value; set_replaygain(temp); } void file_info::info_set_replaygain_track_peak(float value) { replaygain_info temp = get_replaygain(); temp.m_track_peak = value; set_replaygain(temp); } static bool is_valid_bps(t_int64 val) { return val>0 && val<=256; } unsigned file_info::info_get_decoded_bps() const { t_int64 val = info_get_int("decoded_bitspersample"); if (is_valid_bps(val)) return (unsigned)val; val = info_get_int("bitspersample"); if (is_valid_bps(val)) return (unsigned)val; return 0; } void file_info::reset() { info_remove_all(); meta_remove_all(); set_length(0); reset_replaygain(); } void file_info::reset_replaygain() { replaygain_info temp; temp.reset(); set_replaygain(temp); } void file_info::copy_meta_single_rename_ex(const file_info & p_source,t_size p_index,const char * p_new_name,t_size p_new_name_length) { t_size n, m = p_source.meta_enum_value_count(p_index); t_size new_index = infinite; for(n=0;n<m;n++) { const char * value = p_source.meta_enum_value(p_index,n); if (n == 0) new_index = meta_set_ex(p_new_name,p_new_name_length,value,infinite); else meta_add_value(new_index,value); } } t_size file_info::meta_add_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) { t_size index = meta_find_ex(p_name,p_name_length); if (index == infinite) return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length); else { meta_add_value_ex(index,p_value,p_value_length); return index; } } void file_info::meta_add_value_ex(t_size p_index,const char * p_value,t_size p_value_length) { meta_insert_value_ex(p_index,meta_enum_value_count(p_index),p_value,p_value_length); } t_size file_info::meta_calc_total_value_count() const { t_size n, m = meta_get_count(), ret = 0; for(n=0;n<m;n++) ret += meta_enum_value_count(n); return ret; } bool file_info::info_set_replaygain_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len) { replaygain_info temp = get_replaygain(); if (temp.set_from_meta_ex(p_name,p_name_len,p_value,p_value_len)) { set_replaygain(temp); return true; } else return false; } void file_info::info_set_replaygain_auto_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len) { if (!info_set_replaygain_ex(p_name,p_name_len,p_value,p_value_len)) info_set_ex(p_name,p_name_len,p_value,p_value_len); } bool replaygain_info::g_equal(const replaygain_info & item1,const replaygain_info & item2) { return item1.m_album_gain == item2.m_album_gain && item1.m_track_gain == item2.m_track_gain && item1.m_album_peak == item2.m_album_peak && item1.m_track_peak == item2.m_track_peak; } bool file_info::are_meta_fields_identical(t_size p_index1,t_size p_index2) const { const t_size count = meta_enum_value_count(p_index1); if (count != meta_enum_value_count(p_index2)) return false; t_size n; for(n=0;n<count;n++) { if (strcmp(meta_enum_value(p_index1,n),meta_enum_value(p_index2,n))) return false; } return true; } bool file_info::meta_format(const char * p_name,pfc::string_base & p_out) const { p_out.reset(); t_size index = meta_find(p_name); if (index == infinite) return false; t_size val, count = meta_enum_value_count(index); if (count == 0) return false; for(val=0;val<count;val++) { if (val > 0) p_out += ", "; p_out += meta_enum_value(index,val); } return true; } void file_info::info_calculate_bitrate(t_filesize p_filesize,double p_length) { info_set_bitrate((unsigned)floor((double)p_filesize * 8 / (p_length * 1000) + 0.5)); } bool file_info::is_encoding_lossy() const { const char * encoding = info_get("encoding"); if (encoding != NULL) { if (pfc::stricmp_ascii(encoding,"lossy") == 0 /*|| pfc::stricmp_ascii(encoding,"hybrid") == 0*/) return true; } else { //the old way if (info_get("bitspersample") == NULL) return true; } return false; } bool file_info::g_is_meta_equal(const file_info & p_item1,const file_info & p_item2) { const t_size count = p_item1.meta_get_count(); if (count != p_item2.meta_get_count()) { //uDebugLog() << "meta count mismatch"; return false; } pfc::map_t<const char*,t_size,field_name_comparator> item2_meta_map; for(t_size n=0; n<count; n++) { item2_meta_map.set(p_item2.meta_enum_name(n),n); } for(t_size n1=0; n1<count; n1++) { t_size n2; if (!item2_meta_map.query(p_item1.meta_enum_name(n1),n2)) { //uDebugLog() << "item2 doesn't have " << p_item1.meta_enum_name(n1); return false; } t_size value_count = p_item1.meta_enum_value_count(n1); if (value_count != p_item2.meta_enum_value_count(n2)) { //uDebugLog() << "meta value count mismatch: " << p_item1.meta_enum_name(n1) << " : " << value_count << " vs " << p_item2.meta_enum_value_count(n2); return false; } for(t_size v = 0; v < value_count; v++) { if (strcmp(p_item1.meta_enum_value(n1,v),p_item2.meta_enum_value(n2,v)) != 0) { //uDebugLog() << "meta mismatch: " << p_item1.meta_enum_name(n1) << " : " << p_item1.meta_enum_value(n1,v) << " vs " << p_item2.meta_enum_value(n2,v); return false; } } } return true; } bool file_info::g_is_info_equal(const file_info & p_item1,const file_info & p_item2) { t_size count = p_item1.info_get_count(); if (count != p_item2.info_get_count()) return false; for(t_size n1=0; n1<count; n1++) { t_size n2 = p_item2.info_find(p_item1.info_enum_name(n1)); if (n2 == infinite) return false; if (strcmp(p_item1.info_enum_value(n1),p_item2.info_enum_value(n2)) != 0) return false; } return true; } static bool is_valid_field_name_char(char p_char) { return p_char >= 32 && p_char < 127 && p_char != '=' && p_char != '%' && p_char != '<' && p_char != '>'; } bool file_info::g_is_valid_field_name(const char * p_name,t_size p_length) { t_size walk; for(walk = 0; walk < p_length && p_name[walk] != 0; walk++) { if (!is_valid_field_name_char(p_name[walk])) return false; } return walk > 0; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/file_info.h ================================================ #ifndef _FILE_INFO_H_ #define _FILE_INFO_H_ //! Structure containing ReplayGain scan results from some playable object, also providing various helper methods to manipulate those results. struct replaygain_info { float m_album_gain,m_track_gain; float m_album_peak,m_track_peak; enum {text_buffer_size = 16 }; typedef char t_text_buffer[text_buffer_size]; enum { peak_invalid = -1, gain_invalid = -1000 }; static bool g_format_gain(float p_value,char p_buffer[text_buffer_size]); static bool g_format_peak(float p_value,char p_buffer[text_buffer_size]); inline bool format_album_gain(char p_buffer[text_buffer_size]) const {return g_format_gain(m_album_gain,p_buffer);} inline bool format_track_gain(char p_buffer[text_buffer_size]) const {return g_format_gain(m_track_gain,p_buffer);} inline bool format_album_peak(char p_buffer[text_buffer_size]) const {return g_format_peak(m_album_peak,p_buffer);} inline bool format_track_peak(char p_buffer[text_buffer_size]) const {return g_format_peak(m_track_peak,p_buffer);} void set_album_gain_text(const char * p_text,t_size p_text_len = infinite); void set_track_gain_text(const char * p_text,t_size p_text_len = infinite); void set_album_peak_text(const char * p_text,t_size p_text_len = infinite); void set_track_peak_text(const char * p_text,t_size p_text_len = infinite); static bool g_is_meta_replaygain(const char * p_name,t_size p_name_len = infinite); bool set_from_meta_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len); inline bool set_from_meta(const char * p_name,const char * p_value) {return set_from_meta_ex(p_name,infinite,p_value,infinite);} inline bool is_album_gain_present() const {return m_album_gain != gain_invalid;} inline bool is_track_gain_present() const {return m_track_gain != gain_invalid;} inline bool is_album_peak_present() const {return m_album_peak != peak_invalid;} inline bool is_track_peak_present() const {return m_track_peak != peak_invalid;} inline void remove_album_gain() {m_album_gain = gain_invalid;} inline void remove_track_gain() {m_track_gain = gain_invalid;} inline void remove_album_peak() {m_album_peak = peak_invalid;} inline void remove_track_peak() {m_track_peak = peak_invalid;} t_size get_value_count(); static replaygain_info g_merge(replaygain_info r1,replaygain_info r2); static bool g_equal(const replaygain_info & item1,const replaygain_info & item2); void reset(); }; inline bool operator==(const replaygain_info & item1,const replaygain_info & item2) {return replaygain_info::g_equal(item1,item2);} inline bool operator!=(const replaygain_info & item1,const replaygain_info & item2) {return !replaygain_info::g_equal(item1,item2);} static const replaygain_info replaygain_info_invalid = {replaygain_info::gain_invalid,replaygain_info::gain_invalid,replaygain_info::peak_invalid,replaygain_info::peak_invalid}; //! Main interface class for information about some playable object. class NOVTABLE file_info { public: //! Retrieves length, in seconds. virtual double get_length() const = 0; //! Sets length, in seconds. virtual void set_length(double p_length) = 0; //! Sets ReplayGain information. virtual void set_replaygain(const replaygain_info & p_info) = 0; //! Retrieves ReplayGain information. virtual replaygain_info get_replaygain() const = 0; //! Retrieves count of metadata entries. virtual t_size meta_get_count() const = 0; //! Retrieves the name of metadata entry of specified index. Return value is a null-terminated UTF-8 encoded string. virtual const char* meta_enum_name(t_size p_index) const = 0; //! Retrieves count of values in metadata entry of specified index. The value is always equal to or greater than 1. virtual t_size meta_enum_value_count(t_size p_index) const = 0; //! Retrieves specified value from specified metadata entry. Return value is a null-terminated UTF-8 encoded string. virtual const char* meta_enum_value(t_size p_index,t_size p_value_number) const = 0; //! Finds index of metadata entry of specified name. Returns infinite when not found. virtual t_size meta_find_ex(const char * p_name,t_size p_name_length) const; //! Creates a new metadata entry of specified name with specified value. If an entry of same name already exists, it is erased. Return value is the index of newly created metadata entry. virtual t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0; //! Inserts a new value into specified metadata entry. virtual void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) = 0; //! Removes metadata entries according to specified bit mask. virtual void meta_remove_mask(const bit_array & p_mask) = 0; //! Reorders metadata entries according to specified permutation. virtual void meta_reorder(const t_size * p_order) = 0; //! Removes values according to specified bit mask from specified metadata entry. If all values are removed, entire metadata entry is removed as well. virtual void meta_remove_values(t_size p_index,const bit_array & p_mask) = 0; //! Alters specified value in specified metadata entry. virtual void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) = 0; //! Retrieves number of technical info entries. virtual t_size info_get_count() const = 0; //! Retrieves the name of specified technical info entry. Return value is a null-terminated UTF-8 encoded string. virtual const char* info_enum_name(t_size p_index) const = 0; //! Retrieves the value of specified technical info entry. Return value is a null-terminated UTF-8 encoded string. virtual const char* info_enum_value(t_size p_index) const = 0; //! Creates a new technical info entry with specified name and specified value. If an entry of the same name already exists, it is erased. Return value is the index of newly created entry. virtual t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0; //! Removes technical info entries indicated by specified bit mask. virtual void info_remove_mask(const bit_array & p_mask) = 0; //! Finds technical info entry of specified name. Returns index of found entry on success, infinite on failure. virtual t_size info_find_ex(const char * p_name,t_size p_name_length) const; //! Copies entire file_info contents from specified file_info object. virtual void copy(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass //! Copies metadata from specified file_info object. virtual void copy_meta(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass //! Copies technical info from specified file_info object. virtual void copy_info(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass bool meta_exists_ex(const char * p_name,t_size p_name_length) const; void meta_remove_field_ex(const char * p_name,t_size p_name_length); void meta_remove_index(t_size p_index); void meta_remove_all(); void meta_remove_value(t_size p_index,t_size p_value); const char * meta_get_ex(const char * p_name,t_size p_name_length,t_size p_index) const; t_size meta_get_count_by_name_ex(const char * p_name,t_size p_name_length) const; void meta_add_value_ex(t_size p_index,const char * p_value,t_size p_value_length); t_size meta_add_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); t_size meta_calc_total_value_count() const; bool meta_format(const char * p_name,pfc::string_base & p_out) const; bool info_exists_ex(const char * p_name,t_size p_name_length) const; void info_remove_index(t_size p_index); void info_remove_all(); bool info_remove_ex(const char * p_name,t_size p_name_length); const char * info_get_ex(const char * p_name,t_size p_name_length) const; inline t_size meta_find(const char * p_name) const {return meta_find_ex(p_name,infinite);} inline bool meta_exists(const char * p_name) const {return meta_exists_ex(p_name,infinite);} inline void meta_remove_field(const char * p_name) {meta_remove_field_ex(p_name,infinite);} inline t_size meta_set(const char * p_name,const char * p_value) {return meta_set_ex(p_name,infinite,p_value,infinite);} inline void meta_insert_value(t_size p_index,t_size p_value_index,const char * p_value) {meta_insert_value_ex(p_index,p_value_index,p_value,infinite);} inline void meta_add_value(t_size p_index,const char * p_value) {meta_add_value_ex(p_index,p_value,infinite);} inline const char* meta_get(const char * p_name,t_size p_index) const {return meta_get_ex(p_name,infinite,p_index);} inline t_size meta_get_count_by_name(const char * p_name) const {return meta_get_count_by_name_ex(p_name,infinite);} inline t_size meta_add(const char * p_name,const char * p_value) {return meta_add_ex(p_name,infinite,p_value,infinite);} inline void meta_modify_value(t_size p_index,t_size p_value_index,const char * p_value) {meta_modify_value_ex(p_index,p_value_index,p_value,infinite);} inline t_size info_set(const char * p_name,const char * p_value) {return info_set_ex(p_name,infinite,p_value,infinite);} inline t_size info_find(const char * p_name) const {return info_find_ex(p_name,infinite);} inline t_size info_exists(const char * p_name) const {return info_exists_ex(p_name,infinite);} inline bool info_remove(const char * p_name) {return info_remove_ex(p_name,infinite);} inline const char * info_get(const char * p_name) const {return info_get_ex(p_name,infinite);} bool info_set_replaygain_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len); inline bool info_set_replaygain(const char * p_name,const char * p_value) {return info_set_replaygain_ex(p_name,infinite,p_value,infinite);} void info_set_replaygain_auto_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len); inline void info_set_replaygain_auto(const char * p_name,const char * p_value) {info_set_replaygain_auto_ex(p_name,infinite,p_value,infinite);} void copy_meta_single(const file_info & p_source,t_size p_index); void copy_info_single(const file_info & p_source,t_size p_index); void copy_meta_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length); void copy_info_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length); inline void copy_meta_single_by_name(const file_info & p_source,const char * p_name) {copy_meta_single_by_name_ex(p_source,p_name,infinite);} inline void copy_info_single_by_name(const file_info & p_source,const char * p_name) {copy_info_single_by_name_ex(p_source,p_name,infinite);} void reset(); void reset_replaygain(); void copy_meta_single_rename_ex(const file_info & p_source,t_size p_index,const char * p_new_name,t_size p_new_name_length); inline void copy_meta_single_rename(const file_info & p_source,t_size p_index,const char * p_new_name) {copy_meta_single_rename_ex(p_source,p_index,p_new_name,infinite);} void overwrite_info(const file_info & p_source); t_int64 info_get_int(const char * name) const; t_int64 info_get_length_samples() const; double info_get_float(const char * name) const; void info_set_int(const char * name,t_int64 value); void info_set_float(const char * name,double value,unsigned precision,bool force_sign = false,const char * unit = 0); void info_set_replaygain_track_gain(float value); void info_set_replaygain_album_gain(float value); void info_set_replaygain_track_peak(float value); void info_set_replaygain_album_peak(float value); inline t_int64 info_get_bitrate_vbr() const {return info_get_int("bitrate_dynamic");} inline void info_set_bitrate_vbr(t_int64 val) {info_set_int("bitrate_dynamic",val);} inline t_int64 info_get_bitrate() const {return info_get_int("bitrate");} inline void info_set_bitrate(t_int64 val) {info_set_int("bitrate",val);} bool is_encoding_lossy() const; void info_calculate_bitrate(t_filesize p_filesize,double p_length); unsigned info_get_decoded_bps() const;//what bps the stream originally was (before converting to audio_sample), 0 if unknown void merge(const pfc::list_base_const_t<const file_info*> & p_sources); bool are_meta_fields_identical(t_size p_index1,t_size p_index2) const; inline const file_info & operator=(const file_info & p_source) {copy(p_source);return *this;} static bool g_is_meta_equal(const file_info & p_item1,const file_info & p_item2); static bool g_is_info_equal(const file_info & p_item1,const file_info & p_item2); //! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally. t_size __meta_add_unsafe_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);} //! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally. t_size __meta_add_unsafe(const char * p_name,const char * p_value) {return meta_set_nocheck_ex(p_name,infinite,p_value,infinite);} //! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally. t_size __info_add_unsafe_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {return info_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);} //! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally. t_size __info_add_unsafe(const char * p_name,const char * p_value) {return info_set_nocheck_ex(p_name,infinite,p_value,infinite);} static bool g_is_valid_field_name(const char * p_name,t_size p_length = infinite); typedef pfc::comparator_stricmp_ascii field_name_comparator; protected: file_info() {} ~file_info() {} void copy_meta_single_nocheck(const file_info & p_source,t_size p_index); void copy_info_single_nocheck(const file_info & p_source,t_size p_index); void copy_meta_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length); void copy_info_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length); inline void copy_meta_single_by_name_nocheck(const file_info & p_source,const char * p_name) {copy_meta_single_by_name_nocheck_ex(p_source,p_name,infinite);} inline void copy_info_single_by_name_nocheck(const file_info & p_source,const char * p_name) {copy_info_single_by_name_nocheck_ex(p_source,p_name,infinite);} virtual t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0; virtual t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0; inline t_size meta_set_nocheck(const char * p_name,const char * p_value) {return meta_set_nocheck_ex(p_name,infinite,p_value,infinite);} inline t_size info_set_nocheck(const char * p_name,const char * p_value) {return info_set_nocheck_ex(p_name,infinite,p_value,infinite);} }; #endif //_FILE_INFO_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/file_info_impl.cpp ================================================ #include "foobar2000.h" t_size file_info_impl::meta_get_count() const { return m_meta.get_count(); } const char* file_info_impl::meta_enum_name(t_size p_index) const { return m_meta.get_name(p_index); } t_size file_info_impl::meta_enum_value_count(t_size p_index) const { return m_meta.get_value_count(p_index); } const char* file_info_impl::meta_enum_value(t_size p_index,t_size p_value_number) const { return m_meta.get_value(p_index,p_value_number); } t_size file_info_impl::meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) { meta_remove_field_ex(p_name,p_name_length); return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length); } t_size file_info_impl::meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) { return m_meta.add_entry(p_name,p_name_length,p_value,p_value_length); } void file_info_impl::meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) { m_meta.insert_value(p_index,p_value_index,p_value,p_value_length); } void file_info_impl::meta_remove_mask(const bit_array & p_mask) { m_meta.remove_mask(p_mask); } void file_info_impl::meta_reorder(const t_size * p_order) { m_meta.reorder(p_order); } void file_info_impl::meta_remove_values(t_size p_index,const bit_array & p_mask) { m_meta.remove_values(p_index,p_mask); if (m_meta.get_value_count(p_index) == 0) m_meta.remove_mask(bit_array_one(p_index)); } t_size file_info_impl::info_get_count() const { return m_info.get_count(); } const char* file_info_impl::info_enum_name(t_size p_index) const { return m_info.get_name(p_index); } const char* file_info_impl::info_enum_value(t_size p_index) const { return m_info.get_value(p_index); } t_size file_info_impl::info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) { info_remove_ex(p_name,p_name_length); return info_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length); } t_size file_info_impl::info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) { return m_info.add_item(p_name,p_name_length,p_value,p_value_length); } void file_info_impl::info_remove_mask(const bit_array & p_mask) { m_info.remove_mask(p_mask); } file_info_impl::file_info_impl(const file_info & p_source) : m_length(0) { copy(p_source); } file_info_impl::file_info_impl(const file_info_impl & p_source) : m_length(0) { copy(p_source); } const file_info_impl & file_info_impl::operator=(const file_info_impl & p_source) { copy(p_source); return *this; } file_info_impl::file_info_impl() : m_length(0) { m_replaygain.reset(); } double file_info_impl::get_length() const { return m_length; } void file_info_impl::set_length(double p_length) { m_length = p_length; } void file_info_impl::meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) { m_meta.modify_value(p_index,p_value_index,p_value,p_value_length); } replaygain_info file_info_impl::get_replaygain() const { return m_replaygain; } void file_info_impl::set_replaygain(const replaygain_info & p_info) { m_replaygain = p_info; } file_info_impl::~file_info_impl() { } t_size file_info_impl_utils::info_storage::add_item(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) { t_size index = m_info.get_size(); m_info.set_size(index + 1); m_info[index].init(p_name,p_name_length,p_value,p_value_length); return index; } void file_info_impl_utils::info_storage::remove_mask(const bit_array & p_mask) { pfc::remove_mask_t(m_info,p_mask); } t_size file_info_impl_utils::meta_storage::add_entry(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) { meta_entry temp(p_name,p_name_length,p_value,p_value_length); return pfc::append_swap_t(m_data,temp); } void file_info_impl_utils::meta_storage::insert_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) { m_data[p_index].insert_value(p_value_index,p_value,p_value_length); } void file_info_impl_utils::meta_storage::modify_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) { m_data[p_index].modify_value(p_value_index,p_value,p_value_length); } void file_info_impl_utils::meta_storage::remove_values(t_size p_index,const bit_array & p_mask) { m_data[p_index].remove_values(p_mask); } void file_info_impl_utils::meta_storage::remove_mask(const bit_array & p_mask) { pfc::remove_mask_t(m_data,p_mask); } file_info_impl_utils::meta_entry::meta_entry(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len) { m_name.set_string(p_name,p_name_len); m_values.set_size(1); m_values[0].set_string(p_value,p_value_len); } void file_info_impl_utils::meta_entry::remove_values(const bit_array & p_mask) { pfc::remove_mask_t(m_values,p_mask); } void file_info_impl_utils::meta_entry::insert_value(t_size p_value_index,const char * p_value,t_size p_value_length) { pfc::string_simple temp; temp.set_string(p_value,p_value_length); pfc::insert_swap_t(m_values,temp,p_value_index); } void file_info_impl_utils::meta_entry::modify_value(t_size p_value_index,const char * p_value,t_size p_value_length) { m_values[p_value_index].set_string(p_value,p_value_length); } void file_info_impl_utils::meta_storage::reorder(const t_size * p_order) { pfc::reorder_t(m_data,p_order,m_data.get_size()); } void file_info_impl::copy_meta(const file_info & p_source) { m_meta.copy_from(p_source); } void file_info_impl::copy_info(const file_info & p_source) { m_info.copy_from(p_source); } void file_info_impl_utils::meta_storage::copy_from(const file_info & p_info) { t_size meta_index,meta_count = p_info.meta_get_count(); m_data.set_size(meta_count); for(meta_index=0;meta_index<meta_count;meta_index++) { meta_entry & entry = m_data[meta_index]; t_size value_index,value_count = p_info.meta_enum_value_count(meta_index); entry.m_name = p_info.meta_enum_name(meta_index); entry.m_values.set_size(value_count); for(value_index=0;value_index<value_count;value_index++) entry.m_values[value_index] = p_info.meta_enum_value(meta_index,value_index); } } void file_info_impl_utils::info_storage::copy_from(const file_info & p_info) { t_size n, count; count = p_info.info_get_count(); m_info.set_count(count); for(n=0;n<count;n++) m_info[n].init(p_info.info_enum_name(n),infinite,p_info.info_enum_value(n),infinite); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/file_info_impl.h ================================================ #ifndef _FOOBAR2000_SDK_FILE_INFO_IMPL_H_ #define _FOOBAR2000_SDK_FILE_INFO_IMPL_H_ namespace file_info_impl_utils { struct info_entry { void init(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len) { m_name.set_string(p_name,p_name_len); m_value.set_string(p_value,p_value_len); } inline const char * get_name() const {return m_name;} inline const char * get_value() const {return m_value;} pfc::string_simple m_name,m_value; }; typedef pfc::array_t<info_entry,pfc::alloc_fast> info_entry_array; } namespace pfc { template<> class traits_t<file_info_impl_utils::info_entry> : public traits_t<pfc::string_simple> {}; }; namespace file_info_impl_utils { class info_storage { public: t_size add_item(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); void remove_mask(const bit_array & p_mask); inline t_size get_count() const {return m_info.get_count();} inline const char * get_name(t_size p_index) const {return m_info[p_index].get_name();} inline const char * get_value(t_size p_index) const {return m_info[p_index].get_value();} void copy_from(const file_info & p_info); private: info_entry_array m_info; }; } namespace file_info_impl_utils { typedef pfc::array_hybrid_t<pfc::string_simple,1,pfc::alloc_fast > meta_value_array; struct meta_entry { meta_entry() {} meta_entry(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len); void remove_values(const bit_array & p_mask); void insert_value(t_size p_value_index,const char * p_value,t_size p_value_length); void modify_value(t_size p_value_index,const char * p_value,t_size p_value_length); inline const char * get_name() const {return m_name;} inline const char * get_value(t_size p_index) const {return m_values[p_index];} inline t_size get_value_count() const {return m_values.get_size();} pfc::string_simple m_name; meta_value_array m_values; }; typedef pfc::array_hybrid_t<meta_entry,10, pfc::alloc_fast> meta_entry_array; } namespace pfc { template<> class traits_t<file_info_impl_utils::meta_entry> : public pfc::traits_combined<pfc::string_simple,file_info_impl_utils::meta_value_array> {}; } namespace file_info_impl_utils { class meta_storage { public: t_size add_entry(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); void insert_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length); void modify_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length); void remove_values(t_size p_index,const bit_array & p_mask); void remove_mask(const bit_array & p_mask); void copy_from(const file_info & p_info); inline void reorder(const t_size * p_order); inline t_size get_count() const {return m_data.get_size();} inline const char * get_name(t_size p_index) const {assert(p_index < m_data.get_size()); return m_data[p_index].get_name();} inline const char * get_value(t_size p_index,t_size p_value_index) const {assert(p_index < m_data.get_size()); return m_data[p_index].get_value(p_value_index);} inline t_size get_value_count(t_size p_index) const {assert(p_index < m_data.get_size()); return m_data[p_index].get_value_count();} private: meta_entry_array m_data; }; } //! Implements file_info. class file_info_impl : public file_info { public: file_info_impl(const file_info_impl & p_source); file_info_impl(const file_info & p_source); file_info_impl(); ~file_info_impl(); double get_length() const; void set_length(double p_length); void copy_meta(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass void copy_info(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass t_size meta_get_count() const; const char* meta_enum_name(t_size p_index) const; t_size meta_enum_value_count(t_size p_index) const; const char* meta_enum_value(t_size p_index,t_size p_value_number) const; t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length); void meta_remove_mask(const bit_array & p_mask); void meta_reorder(const t_size * p_order); void meta_remove_values(t_size p_index,const bit_array & p_mask); void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length); t_size info_get_count() const; const char* info_enum_name(t_size p_index) const; const char* info_enum_value(t_size p_index) const; t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); void info_remove_mask(const bit_array & p_mask); const file_info_impl & operator=(const file_info_impl & p_source); replaygain_info get_replaygain() const; void set_replaygain(const replaygain_info & p_info); protected: t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length); private: file_info_impl_utils::meta_storage m_meta; file_info_impl_utils::info_storage m_info; double m_length; replaygain_info m_replaygain; }; typedef file_info_impl file_info_i;//for compatibility #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/file_info_merge.cpp ================================================ #include "foobar2000.h" static t_size merge_tags_calc_rating_by_index(const file_info & p_info,t_size p_index) { t_size n,m = p_info.meta_enum_value_count(p_index); t_size ret = 0; for(n=0;n<m;n++) ret += strlen(p_info.meta_enum_value(p_index,n)) + 10;//yes, strlen on utf8 data, plus a slight bump to prefer multivalue over singlevalue w/ separator return ret; } static t_size merge_tags_calc_rating(const file_info & p_info,const char * p_field) { t_size field_index = p_info.meta_find(p_field); t_size ret = 0; if (field_index != infinite) { return merge_tags_calc_rating_by_index(p_info,field_index); } else { return 0; } } static void merge_tags_copy_info(const char * field,const file_info * from,file_info * to) { const char * val = from->info_get(field); if (val) to->info_set(field,val); } namespace { struct meta_merge_entry { meta_merge_entry() : m_rating(0) {} t_size m_rating; pfc::array_t<const char *> m_data; }; class meta_merge_map_enumerator { public: meta_merge_map_enumerator(file_info & p_out) : m_out(p_out) { m_out.meta_remove_all(); } void operator() (const char * p_name, const meta_merge_entry & p_entry) { if (p_entry.m_data.get_size() > 0) { t_size index = m_out.__meta_add_unsafe(p_name,p_entry.m_data[0]); for(t_size walk = 1; walk < p_entry.m_data.get_size(); ++walk) { m_out.meta_add_value(index,p_entry.m_data[walk]); } } } private: file_info & m_out; }; } static void merge_meta(file_info & p_out,const pfc::list_base_const_t<const file_info*> & p_in) { pfc::map_t<const char *,meta_merge_entry,pfc::comparator_stricmp_ascii> map; for(t_size in_walk = 0; in_walk < p_in.get_count(); in_walk++) { const file_info & in = * p_in[in_walk]; for(t_size meta_walk = 0, meta_count = in.meta_get_count(); meta_walk < meta_count; meta_walk++ ) { meta_merge_entry & entry = map.find_or_add(in.meta_enum_name(meta_walk)); t_size rating = merge_tags_calc_rating_by_index(in,meta_walk); if (rating > entry.m_rating) { entry.m_rating = rating; const t_size value_count = in.meta_enum_value_count(meta_walk); entry.m_data.set_size(value_count); for(t_size value_walk = 0; value_walk < value_count; value_walk++ ) { entry.m_data[value_walk] = in.meta_enum_value(meta_walk,value_walk); } } } } map.enumerate(meta_merge_map_enumerator(p_out)); } void file_info::merge(const pfc::list_base_const_t<const file_info*> & p_in) { t_size in_count = p_in.get_count(); if (in_count == 0) { meta_remove_all(); return; } else if (in_count == 1) { const file_info * info = p_in[0]; copy_meta(*info); set_replaygain(replaygain_info::g_merge(get_replaygain(),info->get_replaygain())); overwrite_info(*info); //copy_info_single_by_name(*info,"tagtype"); return; } merge_meta(*this,p_in); { pfc::string8_fastalloc tagtype; replaygain_info rg = get_replaygain(); t_size in_ptr; for(in_ptr = 0; in_ptr < in_count; in_ptr++ ) { const file_info * info = p_in[in_ptr]; rg = replaygain_info::g_merge(rg, info->get_replaygain()); t_size field_ptr, field_max = info->info_get_count(); for(field_ptr = 0; field_ptr < field_max; field_ptr++ ) { const char * field_name = info->info_enum_name(field_ptr), * field_value = info->info_enum_value(field_ptr); if (*field_value) { if (!stricmp_utf8(field_name,"tagtype")) { if (!tagtype.is_empty()) tagtype += "|"; tagtype += field_value; } } } } if (!tagtype.is_empty()) info_set("tagtype",tagtype); set_replaygain(rg); } } void file_info::overwrite_info(const file_info & p_source) { t_size count = p_source.info_get_count(); for(t_size n=0;n<count;n++) { info_set(p_source.info_enum_name(n),p_source.info_enum_value(n)); } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/file_operation_callback.cpp ================================================ #include "foobar2000.h" static void g_on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_items) { static_api_ptr_t<library_manager>()->on_files_deleted_sorted(p_items); static_api_ptr_t<playlist_manager>()->on_files_deleted_sorted(p_items); service_ptr_t<file_operation_callback> ptr; service_enum_t<file_operation_callback> e; while(e.next(ptr)) { ptr->on_files_deleted_sorted(p_items); } } static void g_on_files_moved_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) { static_api_ptr_t<playlist_manager>()->on_files_moved_sorted(p_from,p_to); static_api_ptr_t<playlist_manager>()->on_files_deleted_sorted(p_from); service_ptr_t<file_operation_callback> ptr; service_enum_t<file_operation_callback> e; while(e.next(ptr)) { ptr->on_files_moved_sorted(p_from,p_to); } } static void g_on_files_copied_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) { service_ptr_t<file_operation_callback> ptr; service_enum_t<file_operation_callback> e; while(e.next(ptr)) { ptr->on_files_copied_sorted(p_from,p_to); } } void file_operation_callback::g_on_files_deleted(const pfc::list_base_const_t<const char *> & p_items) { core_api::ensure_main_thread(); t_size count = p_items.get_count(); if (count > 0) { if (count == 1) g_on_files_deleted_sorted(p_items); else { pfc::array_t<t_size> order; order.set_size(count); order_helper::g_fill(order); p_items.sort_get_permutation_t(metadb::path_compare,order.get_ptr()); g_on_files_deleted_sorted(pfc::list_permutation_t<const char*>(p_items,order.get_ptr(),count)); } } } void file_operation_callback::g_on_files_moved(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) { core_api::ensure_main_thread(); pfc::dynamic_assert(p_from.get_count() == p_to.get_count()); t_size count = p_from.get_count(); if (count > 0) { if (count == 1) g_on_files_moved_sorted(p_from,p_to); else { pfc::array_t<t_size> order; order.set_size(count); order_helper::g_fill(order); p_from.sort_get_permutation_t(metadb::path_compare,order.get_ptr()); g_on_files_moved_sorted(pfc::list_permutation_t<const char*>(p_from,order.get_ptr(),count),pfc::list_permutation_t<const char*>(p_to,order.get_ptr(),count)); } } } void file_operation_callback::g_on_files_copied(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) { if (core_api::assert_main_thread()) { assert(p_from.get_count() == p_to.get_count()); t_size count = p_from.get_count(); if (count > 0) { if (count == 1) g_on_files_copied_sorted(p_from,p_to); else { pfc::array_t<t_size> order; order.set_size(count); order_helper::g_fill(order); p_from.sort_get_permutation_t(metadb::path_compare,order.get_ptr()); g_on_files_copied_sorted(pfc::list_permutation_t<const char*>(p_from,order.get_ptr(),count),pfc::list_permutation_t<const char*>(p_to,order.get_ptr(),count)); } } } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/file_operation_callback.h ================================================ #ifndef _FILE_OPERATION_CALLBACK_H_ #define _FILE_OPERATION_CALLBACK_H_ //! Interface to notify component system about files being deleted or moved. Operates in app's main thread only. class file_operation_callback : public service_base { public: //! p_items is a metadb::path_compare sorted list of files that have been deleted. virtual void on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_items) = 0; //! p_from is a metadb::path_compare sorted list of files that have been moved, p_to is a list of corresponding target locations. virtual void on_files_moved_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) = 0; //! p_from is a metadb::path_compare sorted list of files that have been copied, p_to is a list of corresponding target locations. virtual void on_files_copied_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) = 0; static void g_on_files_deleted(const pfc::list_base_const_t<const char *> & p_items); static void g_on_files_moved(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to); static void g_on_files_copied(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(file_operation_callback); }; #endif //_FILE_OPERATION_CALLBACK_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/filesystem.cpp ================================================ #include "foobar2000.h" void unpacker::g_open(service_ptr_t<file> & p_out,const service_ptr_t<file> & p,abort_callback & p_abort) { service_enum_t<unpacker> e; service_ptr_t<unpacker> ptr; if (e.first(ptr)) do { p->reopen(p_abort); try { ptr->open(p_out,p,p_abort); return; } catch(exception_io_data const &) {} } while(e.next(ptr)); throw exception_io_data(); } void file::seek_ex(t_sfilesize p_position, file::t_seek_mode p_mode, abort_callback &p_abort) { switch(p_mode) { case seek_from_beginning: seek(p_position,p_abort); break; case seek_from_current: seek(p_position + get_position(p_abort),p_abort); break; case seek_from_eof: seek(p_position + get_size_ex(p_abort),p_abort); break; default: throw exception_io_data(); } } t_filesize file::g_transfer(stream_reader * p_src,stream_writer * p_dst,t_filesize p_bytes,abort_callback & p_abort) { enum {BUFSIZE = 1024*1024*8}; pfc::array_t<t_uint8> temp; temp.set_size((t_size)pfc::min_t<t_filesize>(BUFSIZE,p_bytes)); void* ptr = temp.get_ptr(); t_filesize done = 0; while(done<p_bytes) { p_abort.check_e(); t_size delta = (t_size)pfc::min_t<t_filesize>(BUFSIZE,p_bytes-done); delta = p_src->read(ptr,delta,p_abort); if (delta<=0) break; p_dst->write(ptr,delta,p_abort); done += delta; } return done; } void file::g_transfer_object(stream_reader * p_src,stream_writer * p_dst,t_filesize p_bytes,abort_callback & p_abort) { if (g_transfer(p_src,p_dst,p_bytes,p_abort) != p_bytes) throw exception_io_data_truncation(); } void filesystem::g_get_canonical_path(const char * path,pfc::string_base & out) { TRACK_CALL_TEXT("filesystem::g_get_canonical_path"); service_enum_t<filesystem> e; service_ptr_t<filesystem> ptr; if (e.first(ptr)) do { if (ptr->get_canonical_path(path,out)) return; } while(e.next(ptr)); //no one wants to process this, lets copy over out.set_string(path); } void filesystem::g_get_display_path(const char * path,pfc::string_base & out) { TRACK_CALL_TEXT("filesystem::g_get_display_path"); service_ptr_t<filesystem> ptr; if (!g_get_interface(ptr,path)) { //noone wants to process this, lets copy over out.set_string(path); } else { if (!ptr->get_display_path(path,out)) out.set_string(path); } } bool filesystem::g_get_interface(service_ptr_t<filesystem> & p_out,const char * path) { service_enum_t<filesystem> e; service_ptr_t<filesystem> ptr; if (e.first(ptr)) do { if (ptr->is_our_path(path)) { p_out = ptr; return true; } } while(e.next(ptr)); return false; } void filesystem::g_open(service_ptr_t<file> & p_out,const char * path,t_open_mode mode,abort_callback & p_abort) { TRACK_CALL_TEXT("filesystem::g_open"); service_ptr_t<filesystem> fs; if (!g_get_interface(fs,path)) throw exception_io_no_handler_for_path(); fs->open(p_out,path,mode,p_abort); } void filesystem::g_open_timeout(service_ptr_t<file> & p_out,const char * p_path,t_open_mode p_mode,double p_timeout,abort_callback & p_abort) { pfc::lores_timer timer; timer.start(); for(;;) { try { g_open(p_out,p_path,p_mode,p_abort); break; } catch(exception_io_sharing_violation) { if (timer.query() > p_timeout) throw; p_abort.sleep(0.01); } } } bool filesystem::g_exists(const char * p_path,abort_callback & p_abort) { t_filestats stats; bool dummy; try { g_get_stats(p_path,stats,dummy,p_abort); } catch(exception_io_not_found) {return false;} return true; } bool filesystem::g_exists_writeable(const char * p_path,abort_callback & p_abort) { t_filestats stats; bool writeable; try { g_get_stats(p_path,stats,writeable,p_abort); } catch(exception_io_not_found) {return false;} return writeable; } void filesystem::g_remove(const char * p_path,abort_callback & p_abort) { service_ptr_t<filesystem> fs; if (!g_get_interface(fs,p_path)) throw exception_io_no_handler_for_path(); fs->remove(p_path,p_abort); } void filesystem::g_remove_timeout(const char * p_path,double p_timeout,abort_callback & p_abort) { pfc::lores_timer timer; timer.start(); for(;;) { try { g_remove(p_path,p_abort); break; } catch(exception_io_sharing_violation) { if (timer.query() > p_timeout) throw; p_abort.sleep(0.01); } } } void filesystem::g_move_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort) { pfc::lores_timer timer; timer.start(); for(;;) { try { g_move(p_src,p_dst,p_abort); break; } catch(exception_io_sharing_violation) { if (timer.query() > p_timeout) throw; p_abort.sleep(0.01); } } } void filesystem::g_copy_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort) { pfc::lores_timer timer; timer.start(); for(;;) { try { g_copy(p_src,p_dst,p_abort); break; } catch(exception_io_sharing_violation) { if (timer.query() > p_timeout) throw; p_abort.sleep(0.01); } } } void filesystem::g_create_directory(const char * p_path,abort_callback & p_abort) { service_ptr_t<filesystem> fs; if (!g_get_interface(fs,p_path)) throw exception_io_no_handler_for_path(); fs->create_directory(p_path,p_abort); } void filesystem::g_move(const char * src,const char * dst,abort_callback & p_abort) { service_enum_t<filesystem> e; service_ptr_t<filesystem> ptr; if (e.first(ptr)) do { if (ptr->is_our_path(src) && ptr->is_our_path(dst)) { ptr->move(src,dst,p_abort); return; } } while(e.next(ptr)); throw exception_io_no_handler_for_path(); } void filesystem::g_list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort) { service_ptr_t<filesystem> ptr; if (!g_get_interface(ptr,p_path)) throw exception_io_no_handler_for_path(); ptr->list_directory(p_path,p_out,p_abort); } static void path_pack_string(pfc::string_base & out,const char * src) { out.add_char('|'); out << strlen(src); out.add_char('|'); out << src; out.add_char('|'); } static int path_unpack_string(pfc::string8 & out,const char * src) { int ptr=0; if (src[ptr++]!='|') return -1; int len = atoi(src+ptr); if (len<=0) return -1; while(src[ptr]!=0 && src[ptr]!='|') ptr++; if (src[ptr]!='|') return -1; ptr++; int start = ptr; while(ptr-start<len) { if (src[ptr]==0) return -1; ptr++; } if (src[ptr]!='|') return -1; out.add_string(&src[start],len); ptr++; return ptr; } void filesystem::g_open_precache(service_ptr_t<file> & p_out,const char * p_path,abort_callback & p_abort) { service_ptr_t<filesystem> fs; if (!g_get_interface(fs,p_path)) throw exception_io_no_handler_for_path(); if (fs->is_remote(p_path)) throw exception_io_object_is_remote(); fs->open(p_out,p_path,open_mode_read,p_abort); } bool filesystem::g_is_remote(const char * p_path) { service_ptr_t<filesystem> fs; if (g_get_interface(fs,p_path)) return fs->is_remote(p_path); else throw exception_io_no_handler_for_path(); } bool filesystem::g_is_remote_safe(const char * p_path) { service_ptr_t<filesystem> fs; if (g_get_interface(fs,p_path)) return fs->is_remote(p_path); else return false; } bool filesystem::g_is_remote_or_unrecognized(const char * p_path) { service_ptr_t<filesystem> fs; if (g_get_interface(fs,p_path)) return fs->is_remote(p_path); else return true; } bool filesystem::g_relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out) { bool rv = false; service_ptr_t<filesystem> fs; if (g_get_interface(fs,file_path)) rv = fs->relative_path_create(file_path,playlist_path,out); return rv; } bool filesystem::g_relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out) { service_enum_t<filesystem> e; service_ptr_t<filesystem> ptr; if (e.first(ptr)) do { if (ptr->relative_path_parse(relative_path,playlist_path,out)) return true; } while(e.next(ptr)); return false; } bool archive_impl::get_canonical_path(const char * path,pfc::string_base & out) { if (is_our_path(path)) { pfc::string8 archive,file,archive_canonical; if (g_parse_unpack_path(path,archive,file)) { g_get_canonical_path(archive,archive_canonical); make_unpack_path(out,archive_canonical,file); return true; } else return false; } else return false; } bool archive_impl::is_our_path(const char * path) { if (strncmp(path,"unpack://",9)) return false; const char * type = get_archive_type(); path += 9; while(*type) { if (*type!=*path) return false; type++; path++; } if (*path!='|') return false; return true; } bool archive_impl::get_display_path(const char * path,pfc::string_base & out) { pfc::string8 archive,file; if (g_parse_unpack_path(path,archive,file)) { g_get_display_path(archive,out); out.add_string("|"); out.add_string(file); return true; } else return false; } void archive_impl::open(service_ptr_t<file> & p_out,const char * path,t_open_mode mode, abort_callback & p_abort) { if (mode != open_mode_read) throw exception_io_denied(); pfc::string8 archive,file; if (!g_parse_unpack_path(path,archive,file)) throw exception_io_not_found(); open_archive(p_out,archive,file,p_abort); } void archive_impl::remove(const char * path,abort_callback & p_abort) { throw exception_io_denied(); } void archive_impl::move(const char * src,const char * dst,abort_callback & p_abort) { throw exception_io_denied(); } bool archive_impl::is_remote(const char * src) { pfc::string8 archive,file; if (g_parse_unpack_path(src,archive,file)) return g_is_remote(archive); else throw exception_io_not_found(); } bool archive_impl::relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out) { pfc::string8 archive,file; if (g_parse_unpack_path(file_path,archive,file)) { pfc::string8 archive_rel; if (g_relative_path_create(archive,playlist_path,archive_rel)) { pfc::string8 out_path; make_unpack_path(out_path,archive_rel,file); out.set_string(out_path); return true; } } return false; } bool archive_impl::relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out) { if (!is_our_path(relative_path)) return false; pfc::string8 archive_rel,file; if (g_parse_unpack_path(relative_path,archive_rel,file)) { pfc::string8 archive; if (g_relative_path_parse(archive_rel,playlist_path,archive)) { pfc::string8 out_path; make_unpack_path(out_path,archive,file); out.set_string(out_path); return true; } } return false; } bool archive_impl::g_parse_unpack_path(const char * path,pfc::string8 & archive,pfc::string8 & file) { path = strchr(path,'|'); if (!path) return false; int delta = path_unpack_string(archive,path); if (delta<0) return false; path += delta; file = path; return true; } void archive_impl::g_make_unpack_path(pfc::string_base & path,const char * archive,const char * file,const char * name) { path = "unpack://"; path += name; path_pack_string(path,archive); path += file; } void archive_impl::make_unpack_path(pfc::string_base & path,const char * archive,const char * file) {g_make_unpack_path(path,archive,file,get_archive_type());} FILE * filesystem::streamio_open(const char * path,const char * flags) { FILE * ret = 0; pfc::string8 temp; g_get_canonical_path(path,temp); if (!strncmp(temp,"file://",7)) { ret = _wfopen(pfc::stringcvt::string_wide_from_utf8(path+7),pfc::stringcvt::string_wide_from_utf8(flags)); } return ret; } namespace { class directory_callback_isempty : public directory_callback { bool m_isempty; public: directory_callback_isempty() : m_isempty(true) {} bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) { m_isempty = false; return false; } bool isempty() {return m_isempty;} }; class directory_callback_dummy : public directory_callback { public: bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) {return false;} }; } bool filesystem::g_is_empty_directory(const char * path,abort_callback & p_abort) { directory_callback_isempty callback; try { g_list_directory(path,callback,p_abort); } catch(exception_io const &) {return false;} return callback.isempty(); } bool filesystem::g_is_valid_directory(const char * path,abort_callback & p_abort) { try { g_list_directory(path,directory_callback_dummy(),p_abort); return true; } catch(exception_io const &) {return false;} } bool directory_callback_impl::on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) { p_abort.check_e(); if (is_subdirectory) { if (m_recur) { try { owner->list_directory(url,*this,p_abort); } catch(exception_io const &) {} } } else { m_data.add_item(pfc::rcnew_t<t_entry>(url,p_stats)); } return true; } namespace { class directory_callback_impl_copy : public directory_callback { public: directory_callback_impl_copy(const char * p_target) { m_target = p_target; m_target.fix_dir_separator('\\'); } bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) { const char * fn = url + pfc::scan_filename(url); t_size truncat = m_target.length(); m_target += fn; if (is_subdirectory) { try { filesystem::g_create_directory(m_target,p_abort); } catch(exception_io_already_exists) {} m_target += "\\"; owner->list_directory(url,*this,p_abort); } else { filesystem::g_copy(url,m_target,p_abort); } m_target.truncate(truncat); return true; } private: pfc::string8_fastalloc m_target; // t_io_result m_status; }; } void filesystem::g_copy_directory(const char * src,const char * dst,abort_callback & p_abort) { //UNTESTED filesystem::g_list_directory(src,directory_callback_impl_copy(dst),p_abort); } void filesystem::g_copy(const char * src,const char * dst,abort_callback & p_abort) { service_ptr_t<file> r_src,r_dst; t_filesize size; g_open(r_src,src,open_mode_read,p_abort); size = r_src->get_size_ex(p_abort); g_open(r_dst,dst,open_mode_write_new,p_abort); if (size > 0) { try { file::g_transfer_object(r_src,r_dst,size,p_abort); } catch(std::exception) { r_dst.release(); try {g_remove(dst,abort_callback_impl());} catch(std::exception) {} throw; } } } void stream_reader::read_object(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { if (read(p_buffer,p_bytes,p_abort) != p_bytes) throw exception_io_data_truncation(); } t_filestats file::get_stats(abort_callback & p_abort) { t_filestats temp; temp.m_size = get_size(p_abort); temp.m_timestamp = get_timestamp(p_abort); return temp; } t_filesize stream_reader::skip(t_filesize p_bytes,abort_callback & p_abort) { t_uint8 temp[256]; t_filesize todo = p_bytes, done = 0; while(todo > 0) { t_size delta,deltadone; delta = sizeof(temp); if (delta > todo) delta = (t_size) todo; deltadone = read(temp,delta,p_abort); done += deltadone; todo -= deltadone; if (deltadone < delta) break; } return done; } void stream_reader::skip_object(t_filesize p_bytes,abort_callback & p_abort) { if (skip(p_bytes,p_abort) != p_bytes) throw exception_io_data_truncation(); } void filesystem::g_open_write_new(service_ptr_t<file> & p_out,const char * p_path,abort_callback & p_abort) { g_open(p_out,p_path,open_mode_write_new,p_abort); } void file::g_transfer_file(const service_ptr_t<file> & p_from,const service_ptr_t<file> & p_to,abort_callback & p_abort) { t_filesize length = p_from->get_size_ex(p_abort); p_from->seek(0,p_abort); p_to->seek(0,p_abort); p_to->set_eof(p_abort); if (length > 0) { g_transfer_object(p_from,p_to,length,p_abort); } } void filesystem::g_open_temp(service_ptr_t<file> & p_out,abort_callback & p_abort) { g_open(p_out,"tempfile://",open_mode_write_new,p_abort); } void filesystem::g_open_tempmem(service_ptr_t<file> & p_out,abort_callback & p_abort) { g_open(p_out,"tempmem://",open_mode_write_new,p_abort); } void archive_impl::list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort) { throw exception_io_not_found(); } void archive_impl::create_directory(const char * path,abort_callback &) { throw exception_io_denied(); } void filesystem::g_get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) { TRACK_CALL_TEXT("filesystem::g_get_stats"); service_ptr_t<filesystem> fs; if (!g_get_interface(fs,p_path)) throw exception_io_no_handler_for_path(); return fs->get_stats(p_path,p_stats,p_is_writeable,p_abort); } void archive_impl::get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) { pfc::string8 archive,file; if (g_parse_unpack_path(p_path,archive,file)) { if (g_is_remote(archive)) throw exception_io_object_is_remote(); p_is_writeable = false; p_stats = get_stats_in_archive(archive,file,p_abort); } else throw exception_io_not_found(); } bool file::is_eof(abort_callback & p_abort) { t_filesize position,size; position = get_position(p_abort); size = get_size(p_abort); if (size == filesize_invalid) return false; return position >= size; } t_filetimestamp foobar2000_io::filetimestamp_from_system_timer() { t_filetimestamp ret; GetSystemTimeAsFileTime((FILETIME*)&ret); return ret; } void stream_reader::read_string_ex(pfc::string_base & p_out,t_size p_bytes,abort_callback & p_abort) { char * ptr = p_out.lock_buffer(p_bytes); try { read_object(ptr,p_bytes,p_abort); } catch(...) { p_out.unlock_buffer(); throw; } p_out.unlock_buffer(); } void stream_reader::read_string(pfc::string_base & p_out,abort_callback & p_abort) { t_uint32 length; read_lendian_t(length,p_abort); read_string_ex(p_out,length,p_abort); } void stream_reader::read_string_raw(pfc::string_base & p_out,abort_callback & p_abort) { enum {delta = 256}; char buffer[delta]; p_out.reset(); for(;;) { t_size delta_done; delta_done = read(buffer,delta,p_abort); p_out.add_string(buffer,delta_done); if (delta_done < delta) break; } } void stream_writer::write_string(const char * p_string,abort_callback & p_abort) { t_uint32 len = pfc::downcast_guarded<t_uint32>(strlen(p_string)); write_lendian_t(len,p_abort); write_object(p_string,len,p_abort); } void stream_writer::write_string_raw(const char * p_string,abort_callback & p_abort) { write_object(p_string,strlen(p_string),p_abort); } void file::truncate(t_uint64 p_position,abort_callback & p_abort) { if (p_position < get_size(p_abort)) resize(p_position,p_abort); } format_filetimestamp::format_filetimestamp(t_filetimestamp p_timestamp) { #ifdef _WIN32 SYSTEMTIME st; FILETIME ft; if (FileTimeToLocalFileTime((FILETIME*)&p_timestamp,&ft)) { if (FileTimeToSystemTime(&ft,&st)) { m_buffer << pfc::format_uint(st.wYear,4) << "-" << pfc::format_uint(st.wMonth,2) << "-" << pfc::format_uint(st.wDay,2) << " " << pfc::format_uint(st.wHour,2) << ":" << pfc::format_uint(st.wMinute,2) << ":" << pfc::format_uint(st.wSecond,2); } else m_buffer << "<invalid timestamp>"; } else m_buffer << "<invalid timestamp>"; #else #error portme #endif } #ifdef _WIN32 namespace { //rare/weird win32 errors that didn't make it to the main API PFC_DECLARE_EXCEPTION(exception_io_device_not_ready, exception_io,"Device not ready"); PFC_DECLARE_EXCEPTION(exception_io_invalid_drive, exception_io_not_found,"Drive not found"); PFC_DECLARE_EXCEPTION(exception_io_win32, exception_io,"Generic win32 I/O error"); PFC_DECLARE_EXCEPTION(exception_io_buffer_overflow, exception_io,"The file name is too long"); class exception_io_win32_ex : public exception_io_win32 { public: exception_io_win32_ex(DWORD p_code) : m_msg(pfc::string_formatter() << "I/O error (win32 #" << (t_uint32)p_code << ")") {} exception_io_win32_ex(const exception_io_win32_ex & p_other) {*this = p_other;} const char * what() const throw() {return m_msg;} private: pfc::string8 m_msg; }; } void foobar2000_io::exception_io_from_win32(DWORD p_code) { switch(p_code) { case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: throw exception_io_already_exists(); case ERROR_NETWORK_ACCESS_DENIED: case ERROR_ACCESS_DENIED: throw exception_io_denied(); case ERROR_WRITE_PROTECT: throw exception_io_write_protected(); case ERROR_BUSY: case ERROR_PATH_BUSY: case ERROR_SHARING_VIOLATION: case ERROR_LOCK_VIOLATION: throw exception_io_sharing_violation(); case ERROR_HANDLE_DISK_FULL: case ERROR_DISK_FULL: throw exception_io_device_full(); case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: throw exception_io_not_found(); case ERROR_BROKEN_PIPE: case ERROR_NO_DATA: throw exception_io_no_data(); case ERROR_NETWORK_UNREACHABLE: case ERROR_NETNAME_DELETED: throw exception_io_network_not_reachable(); case ERROR_NOT_READY: throw exception_io_device_not_ready(); case ERROR_INVALID_DRIVE: throw exception_io_invalid_drive(); case ERROR_CRC: case ERROR_FILE_CORRUPT: case ERROR_DISK_CORRUPT: throw exception_io_file_corrupted(); case ERROR_BUFFER_OVERFLOW: throw exception_io_buffer_overflow(); case ERROR_DISK_CHANGE: throw exception_io_disk_change(); default: throw exception_io_win32_ex(p_code); } } #endif t_filesize file::get_size_ex(abort_callback & p_abort) { t_filesize temp = get_size(p_abort); if (temp == filesize_invalid) throw exception_io_no_length(); return temp; } void file::ensure_local() { if (is_remote()) throw exception_io_object_is_remote(); } void file::ensure_seekable() { if (!can_seek()) throw exception_io_object_not_seekable(); } bool filesystem::g_is_recognized_path(const char * p_path) { return g_get_interface(service_ptr_t<filesystem>(),p_path); } t_filesize file::get_remaining(abort_callback & p_abort) { t_filesize length = get_size_ex(p_abort); t_filesize position = get_position(p_abort); pfc::dynamic_assert(position <= length); return length - position; } t_filesize file::g_transfer(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort) { return g_transfer(pfc::safe_cast<stream_reader*>(p_src.get_ptr()),pfc::safe_cast<stream_writer*>(p_dst.get_ptr()),p_bytes,p_abort); } void file::g_transfer_object(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort) { if (p_bytes > 1024) /* don't bother on small objects */ { t_filesize oldsize = p_dst->get_size(p_abort); if (oldsize != filesize_invalid) { t_filesize newpos = p_dst->get_position(p_abort) + p_bytes; if (newpos > oldsize) p_dst->resize(newpos ,p_abort); } } g_transfer_object(pfc::safe_cast<stream_reader*>(p_src.get_ptr()),pfc::safe_cast<stream_writer*>(p_dst.get_ptr()),p_bytes,p_abort); } void foobar2000_io::generate_temp_location_for_file(pfc::string_base & p_out, const char * p_origpath,const char * p_extension,const char * p_magic) { hasher_md5_result hash; { static_api_ptr_t<hasher_md5> hasher; hasher_md5_state state; hasher->initialize(state); hasher->process(state,p_origpath,strlen(p_origpath)); hasher->process(state,p_extension,strlen(p_extension)); hasher->process(state,p_magic,strlen(p_magic)); hash = hasher->get_result(state); } p_out = p_origpath; p_out.truncate(p_out.scan_filename()); p_out += "temp-"; p_out += pfc::format_hexdump(hash.m_data,sizeof(hash.m_data),""); p_out += "."; p_out += p_extension; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/filesystem.h ================================================ #ifndef _FOOBAR2000_SDK_FILESYSTEM_H_ #define _FOOBAR2000_SDK_FILESYSTEM_H_ class playlist_loader_callback; class file_info; //! Contains various I/O related structures and interfaces. namespace foobar2000_io { //! Type used for file size related variables. typedef t_uint64 t_filesize; //! Type used for file size related variables when signed value is needed. typedef t_int64 t_sfilesize; //! Type used for file timestamp related variables. 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601; 0 for invalid/unknown time. typedef t_uint64 t_filetimestamp; //! Invalid/unknown file timestamp constant. Also see: t_filetimestamp. const t_filetimestamp filetimestamp_invalid = 0; //! Invalid/unknown file size constant. Also see: t_filesize. const t_filesize filesize_invalid = (t_filesize)(~0); //! Generic I/O error. Root class for I/O failure exception. See relevant default message for description of each derived exception class. PFC_DECLARE_EXCEPTION(exception_io, pfc::exception,"I/O error"); //! Object not found. PFC_DECLARE_EXCEPTION(exception_io_not_found, exception_io,"Object not found"); //! Access denied. PFC_DECLARE_EXCEPTION(exception_io_denied, exception_io,"Access denied"); //! Unsupported format or corrupted file (unexpected data encountered). PFC_DECLARE_EXCEPTION(exception_io_data, exception_io,"Unsupported format or corrupted file"); //! Unsupported format or corrupted file (truncation encountered). PFC_DECLARE_EXCEPTION(exception_io_data_truncation, exception_io_data,"Unsupported format or corrupted file"); //! Unsupported format (a subclass of "unsupported format or corrupted file" exception). PFC_DECLARE_EXCEPTION(exception_io_unsupported_format, exception_io_data,"Unsupported file format"); //! Object is remote, while specific operation is supported only for local objects. PFC_DECLARE_EXCEPTION(exception_io_object_is_remote, exception_io,"This operation is not supported on remote objects"); //! Sharing violation. PFC_DECLARE_EXCEPTION(exception_io_sharing_violation, exception_io,"Sharing violation"); //! Device full. PFC_DECLARE_EXCEPTION(exception_io_device_full, exception_io,"Device full"); //! Attempt to seek outside valid range. PFC_DECLARE_EXCEPTION(exception_io_seek_out_of_range, exception_io,"Seek offset out of range"); //! This operation requires a seekable object. PFC_DECLARE_EXCEPTION(exception_io_object_not_seekable, exception_io,"Object is not seekable"); //! This operation requires an object with known length. PFC_DECLARE_EXCEPTION(exception_io_no_length, exception_io,"Length of object is unknown"); //! Invalid path. PFC_DECLARE_EXCEPTION(exception_io_no_handler_for_path, exception_io,"Invalid path"); //! Object already exists. PFC_DECLARE_EXCEPTION(exception_io_already_exists, exception_io,"Object already exists"); //! Pipe error. PFC_DECLARE_EXCEPTION(exception_io_no_data, exception_io,"The process receiving or sending data has terminated"); //! Network not reachable. PFC_DECLARE_EXCEPTION(exception_io_network_not_reachable,exception_io,"Network not reachable"); //! Media is write protected. PFC_DECLARE_EXCEPTION(exception_io_write_protected, exception_io_denied,"The media is write protected"); //! File is corrupted. This indicates filesystem call failure, not actual invalid data being read by the app. PFC_DECLARE_EXCEPTION(exception_io_file_corrupted, exception_io,"The file is corrupted"); //! The disc required for requested operation is not available. PFC_DECLARE_EXCEPTION(exception_io_disk_change, exception_io,"Disc not available"); //! Stores file stats (size and timestamp). struct t_filestats { //! Size of the file. t_filesize m_size; //! Time of last file modification. t_filetimestamp m_timestamp; inline bool operator==(const t_filestats & param) const {return m_size == param.m_size && m_timestamp == param.m_timestamp;} inline bool operator!=(const t_filestats & param) const {return m_size != param.m_size || m_timestamp != param.m_timestamp;} }; //! Invalid/unknown file stats constant. See: t_filestats. static const t_filestats filestats_invalid = {filesize_invalid,filetimestamp_invalid}; #ifdef _WIN32 void exception_io_from_win32(DWORD p_code); #endif //! Generic interface to read data from a nonseekable stream. Also see: stream_writer, file. \n //! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled. class NOVTABLE stream_reader { public: //! Attempts to reads specified number of bytes from the stream. //! @param p_buffer Receives data being read. Must have at least p_bytes bytes of space allocated. //! @param p_bytes Number of bytes to read. //! @param p_abort abort_callback object signaling user aborting the operation. //! @returns Number of bytes actually read. May be less than requested when EOF was reached. virtual t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0; //! Reads specified number of bytes from the stream. If requested amount of bytes can't be read (e.g. EOF), throws exception_io_data_truncation. //! @param p_buffer Receives data being read. Must have at least p_bytes bytes of space allocated. //! @param p_bytes Number of bytes to read. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void read_object(void * p_buffer,t_size p_bytes,abort_callback & p_abort); //! Attempts to skip specified number of bytes in the stream. //! @param p_bytes Number of bytes to skip. //! @param p_abort abort_callback object signaling user aborting the operation. //! @returns Number of bytes actually skipped, May be less than requested when EOF was reached. virtual t_filesize skip(t_filesize p_bytes,abort_callback & p_abort); //! Skips specified number of bytes in the stream. If requested amount of bytes can't be skipped (e.g. EOF), throws exception_io_data_truncation. //! @param p_bytes Number of bytes to skip. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void skip_object(t_filesize p_bytes,abort_callback & p_abort); //! Helper template built around read_object. Reads single raw object from the stream. //! @param p_object Receives object read from the stream on success. //! @param p_abort abort_callback object signaling user aborting the operation. template<typename T> inline void read_object_t(T& p_object,abort_callback & p_abort) {pfc::assert_raw_type<T>(); read_object(&p_object,sizeof(p_object),p_abort);} //! Helper template built around read_object. Reads single raw object from the stream; corrects byte order assuming stream uses little endian order. //! @param p_object Receives object read from the stream on success. //! @param p_abort abort_callback object signaling user aborting the operation. template<typename T> inline void read_lendian_t(T& p_object,abort_callback & p_abort) {read_object_t(p_object,p_abort); byte_order::order_le_to_native_t(p_object);} //! Helper template built around read_object. Reads single raw object from the stream; corrects byte order assuming stream uses big endian order. //! @param p_object Receives object read from the stream on success. //! @param p_abort abort_callback object signaling user aborting the operation. template<typename T> inline void read_bendian_t(T& p_object,abort_callback & p_abort) {read_object_t(p_object,p_abort); byte_order::order_be_to_native_t(p_object);} //! Helper function; reads string (with 32-bit header indicating length in bytes followed by UTF-8 encoded data without null terminator). void read_string(pfc::string_base & p_out,abort_callback & p_abort); //! Helper function; alternate way of storing strings; assumes string takes space up to end of stream. void read_string_raw(pfc::string_base & p_out,abort_callback & p_abort); //! Helper function; reads string of specified length from the stream. void read_string_ex(pfc::string_base & p_out,t_size p_bytes,abort_callback & p_abort); protected: stream_reader() {} ~stream_reader() {} }; //! Generic interface to write data to a nonseekable stream. Also see: stream_reader, file. \n //! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled. class NOVTABLE stream_writer { public: //! Writes specified number of bytes from specified buffer to the stream. //! @param p_buffer Buffer with data to write. Must contain at least p_bytes bytes. //! @param p_bytes Number of bytes to write. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0; //! Helper. Same as write(), provided for consistency. inline void write_object(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {write(p_buffer,p_bytes,p_abort);} //! Helper template. Writes single raw object to the stream. //! @param p_object Object to write. //! @param p_abort abort_callback object signaling user aborting the operation. template<typename T> inline void write_object_t(const T & p_object, abort_callback & p_abort) {pfc::assert_raw_type<T>(); write_object(&p_object,sizeof(p_object),p_abort);} //! Helper template. Writes single raw object to the stream; corrects byte order assuming stream uses little endian order. //! @param p_object Object to write. //! @param p_abort abort_callback object signaling user aborting the operation. template<typename T> inline void write_lendian_t(const T & p_object, abort_callback & p_abort) {T temp = p_object; byte_order::order_native_to_le_t(temp); write_object_t(temp,p_abort);} //! Helper template. Writes single raw object to the stream; corrects byte order assuming stream uses big endian order. //! @param p_object Object to write. //! @param p_abort abort_callback object signaling user aborting the operation. template<typename T> inline void write_bendian_t(const T & p_object, abort_callback & p_abort) {T temp = p_object; byte_order::order_native_to_be_t(temp); write_object_t(temp,p_abort);} //! Helper function; writes string (with 32-bit header indicating length in bytes followed by UTF-8 encoded data without null terminator). void write_string(const char * p_string,abort_callback & p_abort); //! Helper function; writes raw string to the stream, with no length info or null terminators. void write_string_raw(const char * p_string,abort_callback & p_abort); protected: stream_writer() {} ~stream_writer() {} }; //! A class providing abstraction for an open file object, with reading/writing/seeking methods. See also: stream_reader, stream_writer (which it inherits read/write methods from). \n //! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled. class NOVTABLE file : public service_base, public stream_reader, public stream_writer { public: //! Seeking mode constants. Note: these are purposedly defined to same values as standard C SEEK_* constants enum t_seek_mode { //! Seek relative to beginning of file (same as seeking to absolute offset). seek_from_beginning = 0, //! Seek relative to current position. seek_from_current = 1, //! Seek relative to end of file. seek_from_eof = 2, }; //! Retrieves size of the file. //! @param p_abort abort_callback object signaling user aborting the operation. //! @returns File size on success; filesize_invalid if unknown (nonseekable stream etc). virtual t_filesize get_size(abort_callback & p_abort) = 0; //! Retrieves read/write cursor position in the file. In case of non-seekable stream, this should return number of bytes read so far since open/reopen call. //! @param p_abort abort_callback object signaling user aborting the operation. //! @returns Read/write cursor position virtual t_filesize get_position(abort_callback & p_abort) = 0; //! Resizes file to specified size in bytes. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void resize(t_filesize p_size,abort_callback & p_abort) = 0; //! Sets read/write cursor position to specific offset. //! @param p_position position to seek to. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void seek(t_filesize p_position,abort_callback & p_abort) = 0; //! Sets read/write cursor position to specific offset; extended form allowing seeking relative to current position or to end of file. //! @param p_position Position to seek to; interpretation of this value depends on p_mode parameter. //! @param p_mode Seeking mode; see t_seek_mode enum values for further description. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void seek_ex(t_sfilesize p_position,t_seek_mode p_mode,abort_callback & p_abort); //! Returns whether the file is seekable or not. If can_seek() returns false, all seek() or seek_ex() calls will fail; reopen() is still usable on nonseekable streams. virtual bool can_seek() = 0; //! Retrieves mime type of the file. //! @param p_out Receives content type string on success. virtual bool get_content_type(pfc::string_base & p_out) = 0; //! Hint, returns whether the file is already fully buffered into memory. virtual bool is_in_memory() {return false;} //! Optional, called by owner thread before sleeping. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void on_idle(abort_callback & p_abort) {} //! Retrieves last modification time of the file. //! @param p_abort abort_callback object signaling user aborting the operation. //! @returns Last modification time o fthe file; filetimestamp_invalid if N/A. virtual t_filetimestamp get_timestamp(abort_callback & p_abort) {return filetimestamp_invalid;} //! Resets non-seekable stream, or seeks to zero on seekable file. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void reopen(abort_callback & p_abort) = 0; //! Indicates whether the file is a remote resource and non-sequential access may be slowed down by lag. This is typically returns to true on non-seekable sources but may also return true on seekable sources indicating that seeking is supported but will be relatively slow. virtual bool is_remote() = 0; //! Retrieves file stats structure. Usese get_size() and get_timestamp(). t_filestats get_stats(abort_callback & p_abort); //! Returns whether read/write cursor position is at the end of file. bool is_eof(abort_callback & p_abort); //! Truncates file to specified size (while preserving read/write cursor position if possible); uses set_eof(). void truncate(t_filesize p_position,abort_callback & p_abort); //! Truncates the file at current read/write cursor position. void set_eof(abort_callback & p_abort) {resize(get_position(p_abort),p_abort);} //! Helper; retrieves size of the file. If size is not available (get_size() returns filesize_invalid), throws exception_io_no_length. t_filesize get_size_ex(abort_callback & p_abort); //! Helper; retrieves amount of bytes between read/write cursor position and end of file. Fails when length can't be determined. t_filesize get_remaining(abort_callback & p_abort); //! Helper; throws exception_io_object_not_seekable if file is not seekable. void ensure_seekable(); //! Helper; throws exception_io_object_is_remote if the file is remote. void ensure_local(); //! Helper; transfers specified number of bytes between streams. //! @returns number of bytes actually transferred. May be less than requested if e.g. EOF is reached. static t_filesize g_transfer(stream_reader * src,stream_writer * dst,t_filesize bytes,abort_callback & p_abort); //! Helper; transfers specified number of bytes between streams. Throws exception if requested number of bytes could not be read (EOF). static void g_transfer_object(stream_reader * src,stream_writer * dst,t_filesize bytes,abort_callback & p_abort); //! Helper; transfers entire file content from one file to another, erasing previous content. static void g_transfer_file(const service_ptr_t<file> & p_from,const service_ptr_t<file> & p_to,abort_callback & p_abort); //! Helper; improved performance over g_transfer on streams (avoids disk fragmentation when transferring large blocks). static t_filesize g_transfer(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort); //! Helper; improved performance over g_transfer_file on streams (avoids disk fragmentation when transferring large blocks). static void g_transfer_object(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort); FB2K_MAKE_SERVICE_INTERFACE(file,service_base); }; //! Special hack for shoutcast metadata nonsense handling. Documentme. class file_dynamicinfo : public file { public: //! Retrieves "static" info that doesn't change in the middle of stream, such as station names etc. Returns true on success; false when static info is not available. virtual bool get_static_info(class file_info & p_out) = 0; //! Returns whether dynamic info is available on this stream or not. virtual bool is_dynamic_info_enabled()=0; //! Retrieves dynamic stream info (e.g. online stream track titles). Returns true on success, false when info has not changed since last call. virtual bool get_dynamic_info(class file_info & p_out) = 0; FB2K_MAKE_SERVICE_INTERFACE(file_dynamicinfo,file); }; //! Implementation helper - contains dummy implementations of methods that modify the file template<typename t_base> class file_readonly_t : public t_base { public: void resize(t_filesize p_size,abort_callback & p_abort) {throw exception_io_denied();} void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {throw exception_io_denied();} }; typedef file_readonly_t<file> file_readonly; class filesystem; class NOVTABLE directory_callback { public: //! @returns true to continue enumeration, false to abort. virtual bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats)=0; }; //! Entrypoint service for all filesystem operations.\n //! Implementation: standard implementations for local filesystem etc are provided by core.\n //! Instantiation: use static helper functions rather than calling filesystem interface methods directly, e.g. filesystem::g_open() to open a file. class NOVTABLE filesystem : public service_base { public: //! Enumeration specifying how to open a file. See: filesystem::open(), filesystem::g_open(). enum t_open_mode { //! Opens an existing file for reading; if the file does not exist, the operation will fail. open_mode_read, //! Opens an existing file for writing; if the file does not exist, the operation will fail. open_mode_write_existing, //! Opens a new file for writing; if the file exists, its contents will be wiped. open_mode_write_new, }; virtual bool get_canonical_path(const char * p_path,pfc::string_base & p_out)=0; virtual bool is_our_path(const char * p_path)=0; virtual bool get_display_path(const char * p_path,pfc::string_base & p_out)=0; virtual void open(service_ptr_t<file> & p_out,const char * p_path, t_open_mode p_mode,abort_callback & p_abort)=0; virtual void remove(const char * p_path,abort_callback & p_abort)=0; virtual void move(const char * p_src,const char * p_dst,abort_callback & p_abort)=0; //! Queries whether a file at specified path belonging to this filesystem is a remove object or not. virtual bool is_remote(const char * p_src) = 0; //! Retrieves stats of a file at specified path. virtual void get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) = 0; virtual bool relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out) {return false;} virtual bool relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out) {return false;} //! Creates a directory. virtual void create_directory(const char * p_path,abort_callback & p_abort) = 0; virtual void list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort)=0; //! Hint; returns whether this filesystem supports mime types. virtual bool supports_content_types() = 0; static void g_get_canonical_path(const char * path,pfc::string_base & out); static void g_get_display_path(const char * path,pfc::string_base & out); static bool g_get_interface(service_ptr_t<filesystem> & p_out,const char * path);//path is AFTER get_canonical_path static bool g_is_remote(const char * p_path);//path is AFTER get_canonical_path static bool g_is_remote_safe(const char * p_path);//path is AFTER get_canonical_path static bool g_is_remote_or_unrecognized(const char * p_path); static bool g_is_recognized_path(const char * p_path); //! Opens file at specified path, with specified access privileges. static void g_open(service_ptr_t<file> & p_out,const char * p_path,t_open_mode p_mode,abort_callback & p_abort); //! Attempts to open file at specified path; if the operation fails with sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time. static void g_open_timeout(service_ptr_t<file> & p_out,const char * p_path,t_open_mode p_mode,double p_timeout,abort_callback & p_abort); static void g_open_write_new(service_ptr_t<file> & p_out,const char * p_path,abort_callback & p_abort); static void g_open_read(service_ptr_t<file> & p_out,const char * path,abort_callback & p_abort) {return g_open(p_out,path,open_mode_read,p_abort);} static void g_open_precache(service_ptr_t<file> & p_out,const char * path,abort_callback & p_abort);//open only for precaching data (eg. will fail on http etc) static bool g_exists(const char * p_path,abort_callback & p_abort); static bool g_exists_writeable(const char * p_path,abort_callback & p_abort); //! Removes file at specified path. static void g_remove(const char * p_path,abort_callback & p_abort); //! Attempts to remove file at specified path; if the operation fails with a sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time. static void g_remove_timeout(const char * p_path,double p_timeout,abort_callback & p_abort); //! Moves file from one path to another. static void g_move(const char * p_src,const char * p_dst,abort_callback & p_abort); //! Attempts to move file from one path to another; if the operation fails with a sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time. static void g_move_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort); static void g_copy(const char * p_src,const char * p_dst,abort_callback & p_abort);//needs canonical path static void g_copy_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort);//needs canonical path static void g_copy_directory(const char * p_src,const char * p_dst,abort_callback & p_abort);//needs canonical path static void g_get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort); static bool g_relative_path_create(const char * p_file_path,const char * p_playlist_path,pfc::string_base & out); static bool g_relative_path_parse(const char * p_relative_path,const char * p_playlist_path,pfc::string_base & out); static void g_create_directory(const char * p_path,abort_callback & p_abort); //! If for some bloody reason you ever need stream io compatibility, use this, INSTEAD of calling fopen() on the path string you've got; will only work with file:// (and not with http://, unpack:// or whatever) static FILE * streamio_open(const char * p_path,const char * p_flags); static void g_open_temp(service_ptr_t<file> & p_out,abort_callback & p_abort); static void g_open_tempmem(service_ptr_t<file> & p_out,abort_callback & p_abort); static void g_list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort);// path must be canonical static bool g_is_valid_directory(const char * path,abort_callback & p_abort); static bool g_is_empty_directory(const char * path,abort_callback & p_abort); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(filesystem); }; class directory_callback_impl : public directory_callback { struct t_entry { pfc::string_simple m_path; t_filestats m_stats; t_entry(const char * p_path, const t_filestats & p_stats) : m_path(p_path), m_stats(p_stats) {} }; pfc::list_t<pfc::rcptr_t<t_entry> > m_data; bool m_recur; static int sortfunc(const pfc::rcptr_const_t<t_entry> & p1, const pfc::rcptr_const_t<t_entry> & p2) {return stricmp_utf8(p1->m_path,p2->m_path);} public: bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats); directory_callback_impl(bool p_recur) : m_recur(p_recur) {} t_size get_count() {return m_data.get_count();} const char * operator[](t_size n) const {return m_data[n]->m_path;} const char * get_item(t_size n) const {return m_data[n]->m_path;} const t_filestats & get_item_stats(t_size n) const {return m_data[n]->m_stats;} void sort() {m_data.sort_t(sortfunc);} }; class archive; class NOVTABLE archive_callback : public abort_callback { public: virtual bool on_entry(archive * owner,const char * url,const t_filestats & p_stats,const service_ptr_t<file> & p_reader) = 0; }; //! Interface for archive reader services. When implementing, derive from archive_impl rather than from deriving from archive directly. class NOVTABLE archive : public filesystem { public: virtual void archive_list(const char * p_path,const service_ptr_t<file> & p_reader,archive_callback & p_callback,bool p_want_readers) = 0; FB2K_MAKE_SERVICE_INTERFACE(archive,filesystem); }; //! Root class for archive implementations. Derive from this instead of from archive directly. class NOVTABLE archive_impl : public archive { private: //do not override these bool get_canonical_path(const char * path,pfc::string_base & out); bool is_our_path(const char * path); bool get_display_path(const char * path,pfc::string_base & out); void remove(const char * path,abort_callback & p_abort); void move(const char * src,const char * dst,abort_callback & p_abort); bool is_remote(const char * src); bool relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out); bool relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out); void open(service_ptr_t<file> & p_out,const char * path, t_open_mode mode,abort_callback & p_abort); void create_directory(const char * path,abort_callback &); void list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort); void get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort); protected: //override these virtual const char * get_archive_type()=0;//eg. "zip", must be lowercase virtual t_filestats get_stats_in_archive(const char * p_archive,const char * p_file,abort_callback & p_abort) = 0; virtual void open_archive(service_ptr_t<file> & p_out,const char * archive,const char * file, abort_callback & p_abort)=0;//opens for reading public: //override these virtual void archive_list(const char * path,const service_ptr_t<file> & p_reader,archive_callback & p_out,bool p_want_readers)=0; //playlist_loader_callback ONLY for on_progress calls static bool g_parse_unpack_path(const char * path,pfc::string8 & archive,pfc::string8 & file); static void g_make_unpack_path(pfc::string_base & path,const char * archive,const char * file,const char * name); void make_unpack_path(pfc::string_base & path,const char * archive,const char * file); }; template<typename T> class archive_factory_t : public service_factory_single_t<T> {}; t_filetimestamp filetimestamp_from_system_timer(); //! Warning: this formats according to system timezone settings, created strings should be used for display only, never for storage. class format_filetimestamp { public: format_filetimestamp(t_filetimestamp p_timestamp); operator const char*() const {return m_buffer;} const char * get_ptr() const {return m_buffer;} private: pfc::string_fixed_t<32> m_buffer; }; void generate_temp_location_for_file(pfc::string_base & p_out, const char * p_origpath,const char * p_extension,const char * p_magic); } using namespace foobar2000_io; #include "filesystem_helper.h" #endif//_FOOBAR2000_SDK_FILESYSTEM_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/filesystem_helper.cpp ================================================ #include "foobar2000.h" void stream_writer_chunk::write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { t_size remaining = p_bytes, written = 0; while(remaining > 0) { t_size delta = sizeof(m_buffer) - m_buffer_state; if (delta > remaining) delta = remaining; memcpy(m_buffer,(const t_uint8*)p_buffer + written,delta); written += delta; remaining -= delta; if (m_buffer_state == sizeof(m_buffer)) { m_writer->write_lendian_t((t_uint8)m_buffer_state,p_abort); m_writer->write_object(m_buffer,m_buffer_state,p_abort); m_buffer_state = 0; } } } void stream_writer_chunk::flush(abort_callback & p_abort) { m_writer->write_lendian_t((t_uint8)m_buffer_state,p_abort); if (m_buffer_state > 0) { m_writer->write_object(m_buffer,m_buffer_state,p_abort); m_buffer_state = 0; } } /* stream_writer * m_writer; unsigned m_buffer_state; unsigned char m_buffer[255]; */ t_size stream_reader_chunk::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { t_size todo = p_bytes, done = 0; while(todo > 0) { if (m_buffer_size == m_buffer_state) { if (m_eof) break; t_uint8 temp; m_reader->read_lendian_t(temp,p_abort); m_buffer_size = temp; if (temp != sizeof(m_buffer)) m_eof = true; m_buffer_state = 0; if (m_buffer_size>0) { m_reader->read_object(m_buffer,m_buffer_size,p_abort); } } t_size delta = m_buffer_size - m_buffer_state; if (delta > todo) delta = todo; if (delta > 0) { memcpy((unsigned char*)p_buffer + done,m_buffer + m_buffer_state,delta); todo -= delta; done += delta; m_buffer_state += delta; } } return done; } void stream_reader_chunk::flush(abort_callback & p_abort) { while(!m_eof) { p_abort.check_e(); t_uint8 temp; m_reader->read_lendian_t(temp,p_abort); m_buffer_size = temp; if (temp != sizeof(m_buffer)) m_eof = true; m_buffer_state = 0; if (m_buffer_size>0) { m_reader->skip_object(m_buffer_size,p_abort); } } } /* stream_reader * m_reader; unsigned m_buffer_state, m_buffer_size; bool m_eof; unsigned char m_buffer[255]; */ void stream_reader_chunk::g_skip(stream_reader * p_stream,abort_callback & p_abort) { stream_reader_chunk(p_stream).flush(p_abort); } t_size reader_membuffer_base::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { p_abort.check_e(); t_size max = get_buffer_size(); if (max < m_offset) throw pfc::exception_bug_check(); max -= m_offset; t_size delta = p_bytes; if (delta > max) delta = max; memcpy(p_buffer,(char*)get_buffer() + m_offset,delta); m_offset += delta; return delta; } void reader_membuffer_base::seek(t_filesize position,abort_callback & p_abort) { p_abort.check_e(); t_filesize max = get_buffer_size(); if (position == filesize_invalid || position > max) throw exception_io_seek_out_of_range(); m_offset = (t_size)position; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/filesystem_helper.h ================================================ #ifndef _FILESYSTEM_HELPER_H_ #define _FILESYSTEM_HELPER_H_ //helper class file_path_canonical { public: file_path_canonical(const char * src) {filesystem::g_get_canonical_path(src,m_data);} operator const char * () const {return m_data.get_ptr();} const char * get_ptr() const {return m_data.get_ptr();} t_size get_length() const {return m_data.get_length();} private: pfc::string8 m_data; }; class file_path_display { public: file_path_display(const char * src) {filesystem::g_get_display_path(src,m_data);} operator const char * () const {return m_data.get_ptr();} const char * get_ptr() const {return m_data.get_ptr();} t_size get_length() const {return m_data.get_length();} private: pfc::string8 m_data; }; class NOVTABLE reader_membuffer_base : public file_readonly { public: reader_membuffer_base() : m_offset(0) {} t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort); void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {throw exception_io_sharing_violation();} t_filesize get_size(abort_callback & p_abort) {return get_buffer_size();} t_filesize get_position(abort_callback & p_abort) {return m_offset;} void seek(t_filesize position,abort_callback & p_abort); void reopen(abort_callback & p_abort) {seek(0,p_abort);} bool can_seek() {return true;} bool is_in_memory() {return true;} protected: virtual const void * get_buffer() = 0; virtual t_size get_buffer_size() = 0; virtual t_filetimestamp get_timestamp(abort_callback & p_abort) = 0; virtual bool get_content_type(pfc::string_base &) {return false;} inline void seek_internal(t_size p_offset) {if (p_offset > get_buffer_size()) throw exception_io_seek_out_of_range(); m_offset = p_offset;} private: t_size m_offset; }; class reader_membuffer_mirror : public reader_membuffer_base { public: t_filetimestamp get_timestamp(abort_callback & p_abort) {return m_timestamp;} bool is_remote() {return m_remote;} //! Returns false when the object could not be mirrored (too big) or did not need mirroring. static bool g_create(service_ptr_t<file> & p_out,const service_ptr_t<file> & p_src,abort_callback & p_abort) { service_ptr_t<reader_membuffer_mirror> ptr = new service_impl_t<reader_membuffer_mirror>(); if (!ptr->init(p_src,p_abort)) return false; p_out = ptr.get_ptr(); return true; } private: const void * get_buffer() {return m_buffer.get_ptr();} t_size get_buffer_size() {return m_buffer.get_size();} bool init(const service_ptr_t<file> & p_src,abort_callback & p_abort) { if (p_src->is_in_memory()) return false;//already buffered m_remote = p_src->is_remote(); t_size size = pfc::downcast_guarded<t_size>(p_src->get_size(p_abort)); m_buffer.set_size(size); p_src->reopen(p_abort); p_src->read_object(m_buffer.get_ptr(),size,p_abort); m_timestamp = p_src->get_timestamp(p_abort); return true; } t_filetimestamp m_timestamp; pfc::array_t<char> m_buffer; bool m_remote; }; class reader_limited : public file_readonly { service_ptr_t<file> r; t_filesize begin; t_filesize end; public: reader_limited() {begin=0;end=0;} reader_limited(const service_ptr_t<file> & p_r,t_filesize p_begin,t_filesize p_end,abort_callback & p_abort) { r = p_r; begin = p_begin; end = p_end; r->seek(begin,p_abort); } void init(const service_ptr_t<file> & p_r,t_filesize p_begin,t_filesize p_end,abort_callback & p_abort) { r = p_r; begin = p_begin; end = p_end; r->seek(begin,p_abort); } t_filetimestamp get_timestamp(abort_callback & p_abort) {return r->get_timestamp(p_abort);} t_size read(void *p_buffer, t_size p_bytes,abort_callback & p_abort) { t_filesize pos; pos = r->get_position(p_abort); if (p_bytes > end - pos) p_bytes = (t_size)(end - pos); return r->read(p_buffer,p_bytes,p_abort); } t_filesize get_size(abort_callback & p_abort) {return end-begin;} t_filesize get_position(abort_callback & p_abort) { return r->get_position(p_abort) - begin; } void seek(t_filesize position,abort_callback & p_abort) { r->seek(position+begin,p_abort); } bool can_seek() {return r->can_seek();} bool is_remote() {return r->is_remote();} bool get_content_type(pfc::string_base &) {return false;} void reopen(abort_callback & p_abort) {seek(0,p_abort);} }; class stream_reader_memblock_ref : public stream_reader { public: stream_reader_memblock_ref(const void * p_data,t_size p_data_size) : m_data((const unsigned char*)p_data), m_data_size(p_data_size), m_pointer(0) {} t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { t_size remaining = m_data_size - m_pointer; t_size toread = p_bytes; if (toread > remaining) toread = remaining; if (toread > 0) { memcpy(p_buffer,m_data+m_pointer,toread); m_pointer += toread; } return toread; } private: const unsigned char * m_data; t_size m_data_size,m_pointer; }; template<class t_storage> class stream_writer_buffer_append_ref_t : public stream_writer { public: stream_writer_buffer_append_ref_t(t_storage & p_output) : m_output(p_output) {} void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { pfc::static_assert< sizeof(m_output[0]) == 1>(); t_size base = m_output.get_size(); if (base + p_bytes < base) throw std::bad_alloc(); m_output.set_size(base + p_bytes); memcpy( (t_uint8*) m_output.get_ptr() + base, p_buffer, p_bytes ); } private: t_storage & m_output; }; class stream_reader_limited_ref : public stream_reader { public: stream_reader_limited_ref(stream_reader * p_reader,t_size p_limit) : m_reader(p_reader), m_remaining(p_limit) {} t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { if (p_bytes > m_remaining) p_bytes = m_remaining; t_size done = m_reader->read(p_buffer,p_bytes,p_abort); m_remaining -= done; return done; } inline t_size get_remaining() const {return m_remaining;} void flush_remaining(abort_callback & p_abort) { if (m_remaining > 0) return skip_object(m_remaining,p_abort); } private: stream_reader * m_reader; t_size m_remaining; }; class stream_writer_chunk_dwordheader : public stream_writer { public: stream_writer_chunk_dwordheader(const service_ptr_t<file> & p_writer) : m_writer(p_writer) {} void initialize(abort_callback & p_abort) { m_headerposition = m_writer->get_position(p_abort); m_written = 0; m_writer->write_lendian_t((t_uint32)0,p_abort); } void finalize(abort_callback & p_abort) { t_filesize end_offset; end_offset = m_writer->get_position(p_abort); m_writer->seek(m_headerposition,p_abort); m_writer->write_lendian_t(pfc::downcast_guarded<t_uint32>(m_written),p_abort); m_writer->seek(end_offset,p_abort); } void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { m_writer->write(p_buffer,p_bytes,p_abort); m_written += p_bytes; } private: service_ptr_t<file> m_writer; t_filesize m_headerposition; t_filesize m_written; }; class stream_writer_chunk : public stream_writer { public: stream_writer_chunk(stream_writer * p_writer) : m_writer(p_writer), m_buffer_state(0) {} void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort); void flush(abort_callback & p_abort);//must be called after writing before object is destroyed private: stream_writer * m_writer; unsigned m_buffer_state; unsigned char m_buffer[255]; }; class stream_reader_chunk : public stream_reader { public: stream_reader_chunk(stream_reader * p_reader) : m_reader(p_reader), m_buffer_state(0), m_buffer_size(0), m_eof(false) {} t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort); void flush(abort_callback & p_abort);//must be called after reading before object is destroyed static void g_skip(stream_reader * p_stream,abort_callback & p_abort); private: stream_reader * m_reader; t_size m_buffer_state, m_buffer_size; bool m_eof; unsigned char m_buffer[255]; }; #endif//_FILESYSTEM_HELPER_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/foobar2000.h ================================================ #ifndef _FOOBAR2000_H_ #define _FOOBAR2000_H_ #ifndef UNICODE #error Only UNICODE environment supported. #endif #include "../../pfc/pfc.h" #include "shared.h" #ifndef NOTHROW #ifdef _MSC_VER #define NOTHROW __declspec(nothrow) #else #define NOTHROW #endif #endif #define FB2KAPI /*NOTHROW*/ typedef const char * pcchar; #include "core_api.h" #include "service.h" #include "completion_notify.h" #include "abort_callback.h" #include "audio_chunk.h" #include "componentversion.h" #include "preferences_page.h" #include "coreversion.h" #include "filesystem.h" #include "cfg_var.h" #include "mem_block_container.h" #include "audio_postprocessor.h" #include "playable_location.h" #include "file_info.h" #include "file_info_impl.h" #include "metadb_handle.h" #include "metadb.h" #include "console.h" #include "dsp.h" #include "dsp_manager.h" #include "initquit.h" #include "input.h" #include "input_impl.h" #include "menu.h" #include "contextmenu.h" #include "contextmenu_manager.h" #include "menu_helpers.h" #include "modeless_dialog.h" #include "playback_control.h" #include "play_callback.h" #include "playlist.h" #include "playlist_loader.h" #include "replaygain.h" #include "resampler.h" #include "tag_processor.h" #include "titleformat.h" #include "titleformat_config.h" #include "ui.h" #include "unpack.h" #include "vis.h" #include "packet_decoder.h" #include "commandline.h" #include "genrand.h" #include "file_operation_callback.h" #include "library_manager.h" #include "config_io_callback.h" #include "popup_message.h" #include "app_close_blocker.h" #include "config_object.h" #include "config_object_impl.h" #include "threaded_process.h" #include "hasher_md5.h" #include "message_loop.h" #include "input_file_type.h" #include "masstagger_action.h" #include "chapterizer.h" #include "link_resolver.h" #include "main_thread_callback.h" #include "advconfig.h" #include "info_lookup_handler.h" #include "track_property.h" #endif //_FOOBAR2000_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/genrand.h ================================================ #ifndef _SDK_GENRAND_H_ #define _SDK_GENRAND_H_ //! PRNG service. Implemented by the core, do not reimplement. Use g_create() helper function to instantiate. class NOVTABLE genrand_service : public service_base { public: //! Seeds the PRNG with specified value. virtual void seed(unsigned val) = 0; //! Returns random value N, where 0 <= N < range. virtual unsigned genrand(unsigned range)=0; static service_ptr_t<genrand_service> g_create() {return standard_api_create_t<genrand_service>();} FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(genrand_service); }; #endif //_SDK_GENRAND_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/guids.cpp ================================================ #include "foobar2000.h" // {4B897EC8-A2F9-478d-A95E-1A2110A40078} FOOGUIDDECL const GUID hasher_md5::class_guid = { 0x4b897ec8, 0xa2f9, 0x478d, { 0xa9, 0x5e, 0x1a, 0x21, 0x10, 0xa4, 0x0, 0x78 } }; // {74497D81-6158-48ba-9657-386A5520D0FF} FOOGUIDDECL const GUID config_io_callback::class_guid = { 0x74497d81, 0x6158, 0x48ba, { 0x96, 0x57, 0x38, 0x6a, 0x55, 0x20, 0xd0, 0xff } }; // {10BB3EBD-DDF7-4975-A3CC-23084829453E} FOOGUIDDECL const GUID componentversion::class_guid = { 0x10bb3ebd, 0xddf7, 0x4975, { 0xa3, 0xcc, 0x23, 0x8, 0x48, 0x29, 0x45, 0x3e } }; // {7255E8D0-3FCF-4781-B93B-D06CB88DFAFA} FOOGUIDDECL const GUID preferences_page::class_guid = { 0x7255e8d0, 0x3fcf, 0x4781, { 0xb9, 0x3b, 0xd0, 0x6c, 0xb8, 0x8d, 0xfa, 0xfa } }; // {8146A883-F146-401b-BAF6-4FB2E306F2EB} FOOGUIDDECL const GUID preferences_branch::class_guid = { 0x8146a883, 0xf146, 0x401b, { 0xba, 0xf6, 0x4f, 0xb2, 0xe3, 0x6, 0xf2, 0xeb } }; // {90DF5270-65BB-4dba-BAF5-86BE9E59DC20} FOOGUIDDECL const GUID console_receiver::class_guid = { 0x90df5270, 0x65bb, 0x4dba, { 0xba, 0xf5, 0x86, 0xbe, 0x9e, 0x59, 0xdc, 0x20 } }; // {0C36A649-9EA0-4f48-B229-0CB2AA9AB6F4} FOOGUIDDECL const GUID core_version_info::class_guid = { 0xc36a649, 0x9ea0, 0x4f48, { 0xb2, 0x29, 0xc, 0xb2, 0xaa, 0x9a, 0xb6, 0xf4 } }; // {61C4E6E4-C31E-4c89-A759-BF0949DFC528} FOOGUIDDECL const GUID audio_postprocessor::class_guid = { 0x61c4e6e4, 0xc31e, 0x4c89, { 0xa7, 0x59, 0xbf, 0x9, 0x49, 0xdf, 0xc5, 0x28 } }; // {EE65D408-70D6-40f6-940D-D9F537F1BEF1} FOOGUIDDECL const GUID dsp_config_manager::class_guid = { 0xee65d408, 0x70d6, 0x40f6, { 0x94, 0xd, 0xd9, 0xf5, 0x37, 0xf1, 0xbe, 0xf1 } }; // {F8F03D26-C2DA-47ed-8748-1DBACBCEA508} FOOGUIDDECL const GUID dsp_config_callback::class_guid = { 0xf8f03d26, 0xc2da, 0x47ed, { 0x87, 0x48, 0x1d, 0xba, 0xcb, 0xce, 0xa5, 0x8 } }; // {0D048731-8AA8-4704-8AD6-189E24D48C88} FOOGUIDDECL const GUID dsp_entry::class_guid = { 0xd048731, 0x8aa8, 0x4704, { 0x8a, 0xd6, 0x18, 0x9e, 0x24, 0xd4, 0x8c, 0x88 } }; // {2A42AFEA-940B-455b-AEFF-CFDACAF52AFF} FOOGUIDDECL const GUID dsp::class_guid = { 0x2a42afea, 0x940b, 0x455b, { 0xae, 0xff, 0xcf, 0xda, 0xca, 0xf5, 0x2a, 0xff } }; // {113773C4-B387-4b48-8BDF-AB58BC6CE538} FOOGUIDDECL const GUID initquit::class_guid = { 0x113773c4, 0xb387, 0x4b48, { 0x8b, 0xdf, 0xab, 0x58, 0xbc, 0x6c, 0xe5, 0x38 } }; // {4560C877-C41A-4c71-BB75-A7A5BAFC18D8} FOOGUIDDECL const GUID metadb_display_hook::class_guid = { 0x4560c877, 0xc41a, 0x4c71, { 0xbb, 0x75, 0xa7, 0xa5, 0xba, 0xfc, 0x18, 0xd8 } }; // {609D48C8-C6A6-4784-8BBD-FDD32BF0740E} FOOGUIDDECL const GUID metadb::class_guid = { 0x609d48c8, 0xc6a6, 0x4784, { 0x8b, 0xbd, 0xfd, 0xd3, 0x2b, 0xf0, 0x74, 0xe } }; // {D5286BB4-FDED-47ef-A623-4C3FF056DEC1} FOOGUIDDECL const GUID metadb_io_callback::class_guid = { 0xd5286bb4, 0xfded, 0x47ef, { 0xa6, 0x23, 0x4c, 0x3f, 0xf0, 0x56, 0xde, 0xc1 } }; // {1C0802F7-CF24-49ef-B914-8B9866F19779} FOOGUIDDECL const GUID contextmenu_item::class_guid = { 0x1c0802f7, 0xcf24, 0x49ef, { 0xb9, 0x14, 0x8b, 0x98, 0x66, 0xf1, 0x97, 0x79 } }; // {98B00B13-8C0E-49ff-B17C-5E537D3AE4B7} FOOGUIDDECL const GUID visualisation_manager::class_guid = { 0x98b00b13, 0x8c0e, 0x49ff, { 0xb1, 0x7c, 0x5e, 0x53, 0x7d, 0x3a, 0xe4, 0xb7 } }; // {4A4B1DD8-82FF-4071-A6E2-BD043F4C251C} FOOGUIDDECL const GUID visualisation_stream::class_guid = { 0x4a4b1dd8, 0x82ff, 0x4071, { 0xa6, 0xe2, 0xbd, 0x4, 0x3f, 0x4c, 0x25, 0x1c } }; // {015A6C0E-1571-49bd-A367-30B4BD889C34} FOOGUIDDECL const GUID packet_decoder::class_guid = { 0x15a6c0e, 0x1571, 0x49bd, { 0xa3, 0x67, 0x30, 0xb4, 0xbd, 0x88, 0x9c, 0x34 } }; // {D815032D-AFEB-46c6-8AA3-6FD530A4CE67} FOOGUIDDECL const GUID packet_decoder_streamparse::class_guid = { 0xd815032d, 0xafeb, 0x46c6, { 0x8a, 0xa3, 0x6f, 0xd5, 0x30, 0xa4, 0xce, 0x67 } }; // {53006A71-C03C-4c38-822F-9A34A5655095} FOOGUIDDECL const GUID packet_decoder_entry::class_guid = { 0x53006a71, 0xc03c, 0x4c38, { 0x82, 0x2f, 0x9a, 0x34, 0xa5, 0x65, 0x50, 0x95 } }; // {D3BD5F53-A6D6-4346-991F-CF14DFAD2B3A} FOOGUIDDECL const GUID contextmenu_manager::class_guid = { 0xd3bd5f53, 0xa6d6, 0x4346, { 0x99, 0x1f, 0xcf, 0x14, 0xdf, 0xad, 0x2b, 0x3a } }; // {640E006E-2934-4d6c-8327-4FA9F341ECF2} FOOGUIDDECL const GUID input_file_type::class_guid = { 0x640e006e, 0x2934, 0x4d6c, { 0x83, 0x27, 0x4f, 0xa9, 0xf3, 0x41, 0xec, 0xf2 } }; // {2DC57FF7-476D-42f5-A05A-60499896134A} FOOGUIDDECL const GUID ui_control::class_guid = { 0x2dc57ff7, 0x476d, 0x42f5, { 0xa0, 0x5a, 0x60, 0x49, 0x98, 0x96, 0x13, 0x4a } }; // {392B88DE-50FC-43b0-9F03-2D79B071CAF6} FOOGUIDDECL const GUID ui_status_text_override::class_guid = { 0x392b88de, 0x50fc, 0x43b0, { 0x9f, 0x3, 0x2d, 0x79, 0xb0, 0x71, 0xca, 0xf6 } }; // {52BD7A17-540C-4a97-B812-72BC84EC4FF5} FOOGUIDDECL const GUID ui_drop_item_callback::class_guid = { 0x52bd7a17, 0x540c, 0x4a97, { 0xb8, 0x12, 0x72, 0xbc, 0x84, 0xec, 0x4f, 0xf5 } }; // {550B3A19-42A4-4c0f-91F2-90550189CBFF} FOOGUIDDECL const GUID commandline_handler::class_guid = { 0x550b3a19, 0x42a4, 0x4c0f, { 0x91, 0xf2, 0x90, 0x55, 0x1, 0x89, 0xcb, 0xff } }; // {C71B99BD-12C5-48fe-A9C0-469F6FEA88BF} FOOGUIDDECL const GUID modeless_dialog_manager::class_guid = { 0xc71b99bd, 0x12c5, 0x48fe, { 0xa9, 0xc0, 0x46, 0x9f, 0x6f, 0xea, 0x88, 0xbf } }; // {78BCBFA1-DFB9-487f-AB16-CD82BF90CCF7} FOOGUIDDECL const GUID play_callback_manager::class_guid = { 0x78bcbfa1, 0xdfb9, 0x487f, { 0xab, 0x16, 0xcd, 0x82, 0xbf, 0x90, 0xcc, 0xf7 } }; // {8E4EED7A-C6B8-49c7-99FE-97E04AAA63A8} FOOGUIDDECL const GUID play_callback_static::class_guid = { 0x8e4eed7a, 0xc6b8, 0x49c7, { 0x99, 0xfe, 0x97, 0xe0, 0x4a, 0xaa, 0x63, 0xa8 } }; // {BF803668-2977-4c71-B9AB-5C77C338C970} FOOGUIDDECL const GUID playback_control::class_guid = { 0xbf803668, 0x2977, 0x4c71, { 0xb9, 0xab, 0x5c, 0x77, 0xc3, 0x38, 0xc9, 0x70 } }; // {242D9341-211A-4637-A69F-F6684B52F9D6} FOOGUIDDECL const GUID playlist_callback_static::class_guid = { 0x242d9341, 0x211a, 0x4637, { 0xa6, 0x9f, 0xf6, 0x68, 0x4b, 0x52, 0xf9, 0xd6 } }; // {95E9F11B-4C99-4d0a-AB9F-367196B10925} FOOGUIDDECL const GUID playlist_callback_single_static::class_guid = { 0x95e9f11b, 0x4c99, 0x4d0a, { 0xab, 0x9f, 0x36, 0x71, 0x96, 0xb1, 0x9, 0x25 } }; // {88D7EDB1-A850-42a4-BBAB-49E955F4B81F} FOOGUIDDECL const GUID playlist_lock::class_guid = { 0x88d7edb1, 0xa850, 0x42a4, { 0xbb, 0xab, 0x49, 0xe9, 0x55, 0xf4, 0xb8, 0x1f } }; // {D2E5F92B-3424-4822-AE60-8663E6D26EAB} FOOGUIDDECL const GUID playlist_loader::class_guid = { 0xd2e5f92b, 0x3424, 0x4822, { 0xae, 0x60, 0x86, 0x63, 0xe6, 0xd2, 0x6e, 0xab } }; // {2FBCE1E5-902E-49e0-B9CF-CE0FBA765348} FOOGUIDDECL const GUID filesystem::class_guid = { 0x2fbce1e5, 0x902e, 0x49e0, { 0xb9, 0xcf, 0xce, 0xf, 0xba, 0x76, 0x53, 0x48 } }; // {9098AF12-61A3-4caa-8AA9-BB95C2EF8346} FOOGUIDDECL const GUID unpacker::class_guid = { 0x9098af12, 0x61a3, 0x4caa, { 0x8a, 0xa9, 0xbb, 0x95, 0xc2, 0xef, 0x83, 0x46 } }; // {EC707440-FA3E-4d12-9876-FC369F04D4A4} FOOGUIDDECL const GUID archive::class_guid = { 0xec707440, 0xfa3e, 0x4d12, { 0x98, 0x76, 0xfc, 0x36, 0x9f, 0x4, 0xd4, 0xa4 } }; // {B2F9FC40-3E55-4b23-A2C9-22BAAD8795B1} FOOGUIDDECL const GUID file::class_guid = { 0xb2f9fc40, 0x3e55, 0x4b23, { 0xa2, 0xc9, 0x22, 0xba, 0xad, 0x87, 0x95, 0xb1 } }; // {6374340F-82D4-4471-A24B-A754B1398285} FOOGUIDDECL const GUID file_dynamicinfo::class_guid = { 0x6374340f, 0x82d4, 0x4471, { 0xa2, 0x4b, 0xa7, 0x54, 0xb1, 0x39, 0x82, 0x85 } }; // {A00CB77D-ED72-4031-806B-4E45AF995241} FOOGUIDDECL const GUID replaygain_manager::class_guid = { 0xa00cb77d, 0xed72, 0x4031, { 0x80, 0x6b, 0x4e, 0x45, 0xaf, 0x99, 0x52, 0x41 } }; // {3FEED4FC-A400-4a30-8E73-F0ECD114D7E8} FOOGUIDDECL const GUID resampler_entry::class_guid = { 0x3feed4fc, 0xa400, 0x4a30, { 0x8e, 0x73, 0xf0, 0xec, 0xd1, 0x14, 0xd7, 0xe8 } }; // {3F7674AB-044C-4796-8801-6C443C244D88} FOOGUIDDECL const GUID titleformat_compiler::class_guid = { 0x3f7674ab, 0x44c, 0x4796, { 0x88, 0x1, 0x6c, 0x44, 0x3c, 0x24, 0x4d, 0x88 } }; // {1ADD4DC4-B278-4a0c-A105-2629F4B312F4} FOOGUIDDECL const GUID user_interface::class_guid = { 0x1add4dc4, 0xb278, 0x4a0c, { 0xa1, 0x5, 0x26, 0x29, 0xf4, 0xb3, 0x12, 0xf4 } }; // {994C0D0E-319E-45f3-92FC-518616E73ADC} FOOGUIDDECL const GUID contextmenu_item::caller_now_playing = { 0x994c0d0e, 0x319e, 0x45f3, { 0x92, 0xfc, 0x51, 0x86, 0x16, 0xe7, 0x3a, 0xdc } }; // {47502BA1-816D-4a3e-ADE5-A7A9860A67DB} FOOGUIDDECL const GUID contextmenu_item::caller_playlist = { 0x47502ba1, 0x816d, 0x4a3e, { 0xad, 0xe5, 0xa7, 0xa9, 0x86, 0xa, 0x67, 0xdb } }; // {00000000-0000-0000-0000-000000000000} FOOGUIDDECL const GUID contextmenu_item::caller_undefined = { 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }; // {FABEE3E9-8901-4df4-A2D7-B9898D86C39B} FOOGUIDDECL const GUID contextmenu_item::caller_keyboard_shortcut_list = { 0xfabee3e9, 0x8901, 0x4df4, { 0xa2, 0xd7, 0xb9, 0x89, 0x8d, 0x86, 0xc3, 0x9b } }; // {95DE5842-30F5-4f72-B40C-191663782F80} FOOGUIDDECL const GUID keyboard_shortcut_manager::class_guid = { 0x95de5842, 0x30f5, 0x4f72, { 0xb4, 0xc, 0x19, 0x16, 0x63, 0x78, 0x2f, 0x80 } }; // {30F95BEB-FDF4-4a75-B597-60CAF93B39C4} FOOGUIDDECL const GUID packet_decoder::owner_MP4 = { 0x30f95beb, 0xfdf4, 0x4a75, { 0xb5, 0x97, 0x60, 0xca, 0xf9, 0x3b, 0x39, 0xc4 } }; // {8F450CB3-A083-4b83-8D85-ADCE5EA6D57F} FOOGUIDDECL const GUID packet_decoder::owner_MP4_ALAC = { 0x8f450cb3, 0xa083, 0x4b83, { 0x8d, 0x85, 0xad, 0xce, 0x5e, 0xa6, 0xd5, 0x7f } }; // {40017871-50A9-48b6-BF60-BD181A227F9B} FOOGUIDDECL const GUID packet_decoder::owner_MP4_AMR = { 0x40017871, 0x50a9, 0x48b6, { 0xbf, 0x60, 0xbd, 0x18, 0x1a, 0x22, 0x7f, 0x9b } }; // {2E729EA0-6BEB-4392-BF24-75C69B60166D} FOOGUIDDECL const GUID packet_decoder::owner_MP4_AMR_WB = { 0x2e729ea0, 0x6beb, 0x4392, { 0xbf, 0x24, 0x75, 0xc6, 0x9b, 0x60, 0x16, 0x6d } }; // {AF5B7CB0-A08E-404a-A3C0-5C5EA1A8A05C} FOOGUIDDECL const GUID packet_decoder::owner_ADTS = { 0xaf5b7cb0, 0xa08e, 0x404a, { 0xa3, 0xc0, 0x5c, 0x5e, 0xa1, 0xa8, 0xa0, 0x5c } }; // {F72D2EAE-835C-4dfb-97C6-624343EFAFB0} FOOGUIDDECL const GUID packet_decoder::owner_ADIF = { 0xf72d2eae, 0x835c, 0x4dfb, { 0x97, 0xc6, 0x62, 0x43, 0x43, 0xef, 0xaf, 0xb0 } }; // {5C2DE804-EAEE-4b8e-8C14-9207A2549BBE} FOOGUIDDECL const GUID packet_decoder::owner_matroska = { 0x5c2de804, 0xeaee, 0x4b8e, { 0x8c, 0x14, 0x92, 0x7, 0xa2, 0x54, 0x9b, 0xbe } }; // {7B741A69-1AC7-440d-A01D-88536DD4DE1C} FOOGUIDDECL const GUID packet_decoder::owner_MP3 = { 0x7b741a69, 0x1ac7, 0x440d, { 0xa0, 0x1d, 0x88, 0x53, 0x6d, 0xd4, 0xde, 0x1c } }; // {17B300A0-3110-4400-A434-C18FBEEABA81} FOOGUIDDECL const GUID packet_decoder::owner_MP2 = { 0x17b300a0, 0x3110, 0x4400, { 0xa4, 0x34, 0xc1, 0x8f, 0xbe, 0xea, 0xba, 0x81 } }; // {1C068E5E-DD65-4639-BF85-78B297C8FFAC} FOOGUIDDECL const GUID packet_decoder::owner_MP1 = { 0x1c068e5e, 0xdd65, 0x4639, { 0xbf, 0x85, 0x78, 0xb2, 0x97, 0xc8, 0xff, 0xac } }; // {BC73F9FC-0107-480e-BF0E-BE58AF7AF328} FOOGUIDDECL const GUID packet_decoder::property_samplerate = { 0xbc73f9fc, 0x107, 0x480e, { 0xbf, 0xe, 0xbe, 0x58, 0xaf, 0x7a, 0xf3, 0x28 } }; // {E5D19AAD-931B-48ac-AA6E-95E2C23BEC37} FOOGUIDDECL const GUID packet_decoder::property_bitspersample = { 0xe5d19aad, 0x931b, 0x48ac, { 0xaa, 0x6e, 0x95, 0xe2, 0xc2, 0x3b, 0xec, 0x37 } }; // {1AFA1145-E774-4c26-91D6-3F5DD61E260E} FOOGUIDDECL const GUID packet_decoder::property_channels = { 0x1afa1145, 0xe774, 0x4c26, { 0x91, 0xd6, 0x3f, 0x5d, 0xd6, 0x1e, 0x26, 0xe } }; // {29C556DA-065A-4d24-8A11-0F9DBC05A817} FOOGUIDDECL const GUID packet_decoder::property_byteorder = { 0x29c556da, 0x65a, 0x4d24, { 0x8a, 0x11, 0xf, 0x9d, 0xbc, 0x5, 0xa8, 0x17 } }; // {0759C32F-E78E-4479-B0C0-B653DFA014D8} FOOGUIDDECL const GUID packet_decoder::property_signed = { 0x759c32f, 0xe78e, 0x4479, { 0xb0, 0xc0, 0xb6, 0x53, 0xdf, 0xa0, 0x14, 0xd8 } }; // {BB31669E-0C30-4c5f-AAF0-20CD49D46058} FOOGUIDDECL const GUID packet_decoder::property_channelmask = { 0xbb31669e, 0xc30, 0x4c5f, { 0xaa, 0xf0, 0x20, 0xcd, 0x49, 0xd4, 0x60, 0x58 } }; // {6F441057-1D18-4a58-9AC4-8F409CDA7DFD} FOOGUIDDECL const GUID standard_commands::guid_context_file_properties = { 0x6f441057, 0x1d18, 0x4a58, { 0x9a, 0xc4, 0x8f, 0x40, 0x9c, 0xda, 0x7d, 0xfd } }; // {EFC1E9C8-EEEF-427a-8F42-E5781605846D} FOOGUIDDECL const GUID standard_commands::guid_context_file_open_directory = { 0xefc1e9c8, 0xeeef, 0x427a, { 0x8f, 0x42, 0xe5, 0x78, 0x16, 0x5, 0x84, 0x6d } }; // {FFE18008-BCA2-4b29-AB88-8816B492C434} FOOGUIDDECL const GUID standard_commands::guid_context_copy_names = { 0xffe18008, 0xbca2, 0x4b29, { 0xab, 0x88, 0x88, 0x16, 0xb4, 0x92, 0xc4, 0x34 } }; // {44B8F02B-5408-4361-8240-18DEC881B95E} FOOGUIDDECL const GUID standard_commands::guid_context_send_to_playlist = { 0x44b8f02b, 0x5408, 0x4361, { 0x82, 0x40, 0x18, 0xde, 0xc8, 0x81, 0xb9, 0x5e } }; // {8C3BA2CB-BC4D-4752-8282-C6F9AED75A78} FOOGUIDDECL const GUID standard_commands::guid_context_reload_info = { 0x8c3ba2cb, 0xbc4d, 0x4752, { 0x82, 0x82, 0xc6, 0xf9, 0xae, 0xd7, 0x5a, 0x78 } }; // {BD045EA4-E5E9-4206-8FF9-12AD9F5DCDE1} FOOGUIDDECL const GUID standard_commands::guid_context_reload_info_if_changed = { 0xbd045ea4, 0xe5e9, 0x4206, { 0x8f, 0xf9, 0x12, 0xad, 0x9f, 0x5d, 0xcd, 0xe1 } }; // {684D9FBB-4383-44a2-9789-7EE1376209C6} FOOGUIDDECL const GUID standard_commands::guid_context_rewrite_info = { 0x684d9fbb, 0x4383, 0x44a2, { 0x97, 0x89, 0x7e, 0xe1, 0x37, 0x62, 0x9, 0xc6 } }; // {860179B7-962F-4340-ACAD-0DDAF060A6B8} FOOGUIDDECL const GUID standard_commands::guid_context_remove_tags = { 0x860179b7, 0x962f, 0x4340, { 0xac, 0xad, 0xd, 0xda, 0xf0, 0x60, 0xa6, 0xb8 } }; // {4DD1E1AD-F481-480c-BC3E-DD9C878EAFC3} FOOGUIDDECL const GUID standard_commands::guid_context_remove_from_database = { 0x4dd1e1ad, 0xf481, 0x480c, { 0xbc, 0x3e, 0xdd, 0x9c, 0x87, 0x8e, 0xaf, 0xc3 } }; // {A7E7ECB7-1943-4907-83B3-60E92353C7FA} FOOGUIDDECL const GUID standard_commands::guid_context_convert_run = { 0xa7e7ecb7, 0x1943, 0x4907, { 0x83, 0xb3, 0x60, 0xe9, 0x23, 0x53, 0xc7, 0xfa } }; // {BED4FB6E-C4F2-40dd-B528-1DE1450DDFE9} FOOGUIDDECL const GUID standard_commands::guid_context_convert_run_withcue = { 0xbed4fb6e, 0xc4f2, 0x40dd, { 0xb5, 0x28, 0x1d, 0xe1, 0x45, 0xd, 0xdf, 0xe9 } }; // {A58AE6EA-A34F-4879-B25C-F31F2CBC4DA9} FOOGUIDDECL const GUID standard_commands::guid_context_convert_run_singlefile = { 0xa58ae6ea, 0xa34f, 0x4879, { 0xb2, 0x5c, 0xf3, 0x1f, 0x2c, 0xbc, 0x4d, 0xa9 } }; // {A8EFA42D-76CB-42bc-8803-70516567B13D} FOOGUIDDECL const GUID standard_commands::guid_context_write_cd = { 0xa8efa42d, 0x76cb, 0x42bc, { 0x88, 0x3, 0x70, 0x51, 0x65, 0x67, 0xb1, 0x3d } }; // {487DAED1-02FB-4336-A813-5E90317AB041} FOOGUIDDECL const GUID standard_commands::guid_context_rg_scan_track = { 0x487daed1, 0x2fb, 0x4336, { 0xa8, 0x13, 0x5e, 0x90, 0x31, 0x7a, 0xb0, 0x41 } }; // {3850F34C-F619-4296-B8A0-26C617B1D16C} FOOGUIDDECL const GUID standard_commands::guid_context_rg_scan_album = { 0x3850f34c, 0xf619, 0x4296, { 0xb8, 0xa0, 0x26, 0xc6, 0x17, 0xb1, 0xd1, 0x6c } }; // {6A2DBA02-260C-436f-8F41-0190A4298266} FOOGUIDDECL const GUID standard_commands::guid_context_rg_scan_album_multi = { 0x6a2dba02, 0x260c, 0x436f, { 0x8f, 0x41, 0x1, 0x90, 0xa4, 0x29, 0x82, 0x66 } }; // {54C82A92-5824-4381-8D1B-79FBB1E2ABB8} FOOGUIDDECL const GUID standard_commands::guid_context_rg_remove = { 0x54c82a92, 0x5824, 0x4381, { 0x8d, 0x1b, 0x79, 0xfb, 0xb1, 0xe2, 0xab, 0xb8 } }; // {69EAA594-13D9-4237-9BD7-11A39FDD1454} FOOGUIDDECL const GUID standard_commands::guid_context_save_playlist = { 0x69eaa594, 0x13d9, 0x4237, { 0x9b, 0xd7, 0x11, 0xa3, 0x9f, 0xdd, 0x14, 0x54 } }; // {2BEB6836-C657-40ef-BB6E-D5B222AB89CE} FOOGUIDDECL const GUID standard_commands::guid_context_masstag_edit = { 0x2beb6836, 0xc657, 0x40ef, { 0xbb, 0x6e, 0xd5, 0xb2, 0x22, 0xab, 0x89, 0xce } }; // {A579FF07-5D0B-48ed-A071-B6FCE4385AA9} FOOGUIDDECL const GUID standard_commands::guid_context_masstag_rename = { 0xa579ff07, 0x5d0b, 0x48ed, { 0xa0, 0x71, 0xb6, 0xfc, 0xe4, 0x38, 0x5a, 0xa9 } }; // {77CFBCD0-98DC-4015-B327-D7142C664806} FOOGUIDDECL const GUID standard_commands::guid_main_always_on_top = { 0x77cfbcd0, 0x98dc, 0x4015, { 0xb3, 0x27, 0xd7, 0x14, 0x2c, 0x66, 0x48, 0x6 } }; // {11213A01-9F36-4e69-A1BB-7A72F418DE3A} FOOGUIDDECL const GUID standard_commands::guid_main_preferences = { 0x11213a01, 0x9f36, 0x4e69, { 0xa1, 0xbb, 0x7a, 0x72, 0xf4, 0x18, 0xde, 0x3a } }; // {EDA23441-5D38-4499-A22C-FE0CE0A987D9} FOOGUIDDECL const GUID standard_commands::guid_main_about = { 0xeda23441, 0x5d38, 0x4499, { 0xa2, 0x2c, 0xfe, 0xc, 0xe0, 0xa9, 0x87, 0xd9 } }; // {6D38C73A-15D8-472c-8E68-6F946B82ECB4} FOOGUIDDECL const GUID standard_commands::guid_main_exit = { 0x6d38c73a, 0x15d8, 0x472c, { 0x8e, 0x68, 0x6f, 0x94, 0x6b, 0x82, 0xec, 0xb4 } }; // {EF9B60FE-CB03-4c40-A8FD-3F1821020B37} FOOGUIDDECL const GUID standard_commands::guid_main_restart = { 0xef9b60fe, 0xcb03, 0x4c40, { 0xa8, 0xfd, 0x3f, 0x18, 0x21, 0x2, 0xb, 0x37 } }; // {90500F09-F16F-415e-A047-AC5045C95FFE} FOOGUIDDECL const GUID standard_commands::guid_main_activate = { 0x90500f09, 0xf16f, 0x415e, { 0xa0, 0x47, 0xac, 0x50, 0x45, 0xc9, 0x5f, 0xfe } }; // {13C17E8D-0D6F-41a4-B24A-59371043E925} FOOGUIDDECL const GUID standard_commands::guid_main_hide = { 0x13c17e8d, 0xd6f, 0x41a4, { 0xb2, 0x4a, 0x59, 0x37, 0x10, 0x43, 0xe9, 0x25 } }; // {D9473FB2-BF11-4be0-972F-0E43F166A118} FOOGUIDDECL const GUID standard_commands::guid_main_activate_or_hide = { 0xd9473fb2, 0xbf11, 0x4be0, { 0x97, 0x2f, 0xe, 0x43, 0xf1, 0x66, 0xa1, 0x18 } }; // {91E349DA-8800-42e5-BC8C-F3A92577AE84} FOOGUIDDECL const GUID standard_commands::guid_main_titleformat_help = { 0x91e349da, 0x8800, 0x42e5, { 0xbc, 0x8c, 0xf3, 0xa9, 0x25, 0x77, 0xae, 0x84 } }; // {FBCFE01C-6C57-4e6a-A9F1-62334640DC91} FOOGUIDDECL const GUID standard_commands::guid_main_playback_follows_cursor= { 0xfbcfe01c, 0x6c57, 0x4e6a, { 0xa9, 0xf1, 0x62, 0x33, 0x46, 0x40, 0xdc, 0x91 } }; // {0E1C730A-1EA9-41cc-9C30-25700ABDD9FA} FOOGUIDDECL const GUID standard_commands::guid_main_cursor_follows_playback= { 0xe1c730a, 0x1ea9, 0x41cc, { 0x9c, 0x30, 0x25, 0x70, 0xa, 0xbd, 0xd9, 0xfa } }; // {E58895A0-A2F0-45b6-8799-1440E4DB7284} FOOGUIDDECL const GUID standard_commands::guid_main_next = { 0xe58895a0, 0xa2f0, 0x45b6, { 0x87, 0x99, 0x14, 0x40, 0xe4, 0xdb, 0x72, 0x84 } }; // {059C4566-4708-4ebf-8139-6A8EA5A9DFC8} FOOGUIDDECL const GUID standard_commands::guid_main_previous = { 0x59c4566, 0x4708, 0x4ebf, { 0x81, 0x39, 0x6a, 0x8e, 0xa5, 0xa9, 0xdf, 0xc8 } }; // {18B1278A-F1BB-4b48-BC3D-6EC9EF80AD19} FOOGUIDDECL const GUID standard_commands::guid_main_next_or_random = { 0x18b1278a, 0xf1bb, 0x4b48, { 0xbc, 0x3d, 0x6e, 0xc9, 0xef, 0x80, 0xad, 0x19 } }; // {42BDA765-30A8-40fa-BFA4-6A4E2F2B2CE9} FOOGUIDDECL const GUID standard_commands::guid_main_random = { 0x42bda765, 0x30a8, 0x40fa, { 0xbf, 0xa4, 0x6a, 0x4e, 0x2f, 0x2b, 0x2c, 0xe9 } }; // {FCEF5262-7FA5-452e-A527-C14E0CB582DE} FOOGUIDDECL const GUID standard_commands::guid_main_pause = { 0xfcef5262, 0x7fa5, 0x452e, { 0xa5, 0x27, 0xc1, 0x4e, 0xc, 0xb5, 0x82, 0xde } }; // {D3F83B15-D4AF-4586-8BD0-4EA415E31FE1} FOOGUIDDECL const GUID standard_commands::guid_main_play = { 0xd3f83b15, 0xd4af, 0x4586, { 0x8b, 0xd0, 0x4e, 0xa4, 0x15, 0xe3, 0x1f, 0xe1 } }; // {8DEBC44E-EDA2-48d4-8696-31FC29D1F383} FOOGUIDDECL const GUID standard_commands::guid_main_play_or_pause = { 0x8debc44e, 0xeda2, 0x48d4, { 0x86, 0x96, 0x31, 0xfc, 0x29, 0xd1, 0xf3, 0x83 } }; // {2DF17F25-80B9-4a43-B21D-620458FDDE1E} FOOGUIDDECL const GUID standard_commands::guid_main_rg_set_album = { 0x2df17f25, 0x80b9, 0x4a43, { 0xb2, 0x1d, 0x62, 0x4, 0x58, 0xfd, 0xde, 0x1e } }; // {C26F1955-5753-4836-887F-84A563DD6DD9} FOOGUIDDECL const GUID standard_commands::guid_main_rg_set_track = { 0xc26f1955, 0x5753, 0x4836, { 0x88, 0x7f, 0x84, 0xa5, 0x63, 0xdd, 0x6d, 0xd9 } }; // {8D2D808E-6AA2-455b-A2F1-CDB019328140} FOOGUIDDECL const GUID standard_commands::guid_main_rg_disable = { 0x8d2d808e, 0x6aa2, 0x455b, { 0xa2, 0xf1, 0xcd, 0xb0, 0x19, 0x32, 0x81, 0x40 } }; // {C3378028-165F-4374-966C-2FA2E0FCD3A8} FOOGUIDDECL const GUID standard_commands::guid_main_stop = { 0xc3378028, 0x165f, 0x4374, { 0x96, 0x6c, 0x2f, 0xa2, 0xe0, 0xfc, 0xd3, 0xa8 } }; // {EE057982-22F9-4862-A986-859E463316FB} FOOGUIDDECL const GUID standard_commands::guid_main_stop_after_current = { 0xee057982, 0x22f9, 0x4862, { 0xa9, 0x86, 0x85, 0x9e, 0x46, 0x33, 0x16, 0xfb } }; // {11DC6526-73C4-44f0-91B1-DE5C2D26B0C7} FOOGUIDDECL const GUID standard_commands::guid_main_volume_down = { 0x11dc6526, 0x73c4, 0x44f0, { 0x91, 0xb1, 0xde, 0x5c, 0x2d, 0x26, 0xb0, 0xc7 } }; // {A313E630-A04A-4ae8-B5B4-0A944AC964FF} FOOGUIDDECL const GUID standard_commands::guid_main_volume_up = { 0xa313e630, 0xa04a, 0x4ae8, { 0xb5, 0xb4, 0xa, 0x94, 0x4a, 0xc9, 0x64, 0xff } }; // {4A36285B-B4AF-46ed-A1AA-6333057F485B} FOOGUIDDECL const GUID standard_commands::guid_main_volume_mute = { 0x4a36285b, 0xb4af, 0x46ed, { 0xa1, 0xaa, 0x63, 0x33, 0x5, 0x7f, 0x48, 0x5b } }; // {2DC43C22-CA09-4ef9-A61E-7A0C1DAAE21E} FOOGUIDDECL const GUID standard_commands::guid_main_add_directory = { 0x2dc43c22, 0xca09, 0x4ef9, { 0xa6, 0x1e, 0x7a, 0xc, 0x1d, 0xaa, 0xe2, 0x1e } }; // {FC89C278-4475-4853-96C9-F7F05E8CC837} FOOGUIDDECL const GUID standard_commands::guid_main_add_files = { 0xfc89c278, 0x4475, 0x4853, { 0x96, 0xc9, 0xf7, 0xf0, 0x5e, 0x8c, 0xc8, 0x37 } }; // {9CA39D38-AC9B-4cca-B0CE-C0F62D188114} FOOGUIDDECL const GUID standard_commands::guid_main_add_location = { 0x9ca39d38, 0xac9b, 0x4cca, { 0xb0, 0xce, 0xc0, 0xf6, 0x2d, 0x18, 0x81, 0x14 } }; // {73D6E69D-0DC9-4d5c-A7EE-FF4BE3896B08} FOOGUIDDECL const GUID standard_commands::guid_main_add_playlist = { 0x73d6e69d, 0xdc9, 0x4d5c, { 0xa7, 0xee, 0xff, 0x4b, 0xe3, 0x89, 0x6b, 0x8 } }; // {55559142-7AEA-4c20-9B72-1D48E970A274} FOOGUIDDECL const GUID standard_commands::guid_main_clear_playlist = { 0x55559142, 0x7aea, 0x4c20, { 0x9b, 0x72, 0x1d, 0x48, 0xe9, 0x70, 0xa2, 0x74 } }; // {BF72488F-36AC-46b3-A36D-193E60C79BC5} FOOGUIDDECL const GUID standard_commands::guid_main_create_playlist = { 0xbf72488f, 0x36ac, 0x46b3, { 0xa3, 0x6d, 0x19, 0x3e, 0x60, 0xc7, 0x9b, 0xc5 } }; // {59E99BEE-42A3-4526-B06D-56C0C49F0BC1} FOOGUIDDECL const GUID standard_commands::guid_main_highlight_playing = { 0x59e99bee, 0x42a3, 0x4526, { 0xb0, 0x6d, 0x56, 0xc0, 0xc4, 0x9f, 0xb, 0xc1 } }; // {D94393D4-9DBB-4e5c-BE8C-BE9CA80E214D} FOOGUIDDECL const GUID standard_commands::guid_main_load_playlist = { 0xd94393d4, 0x9dbb, 0x4e5c, { 0xbe, 0x8c, 0xbe, 0x9c, 0xa8, 0xe, 0x21, 0x4d } }; // {EE1308C5-EBD2-48f1-959D-2627069C2E0F} FOOGUIDDECL const GUID standard_commands::guid_main_next_playlist = { 0xee1308c5, 0xebd2, 0x48f1, { 0x95, 0x9d, 0x26, 0x27, 0x6, 0x9c, 0x2e, 0xf } }; // {486ECDA3-7BA2-49e9-BB44-4DB9DF9320C7} FOOGUIDDECL const GUID standard_commands::guid_main_previous_playlist = { 0x486ecda3, 0x7ba2, 0x49e9, { 0xbb, 0x44, 0x4d, 0xb9, 0xdf, 0x93, 0x20, 0xc7 } }; // {69C778AA-B836-40a0-89CD-7A2E50C102CB} FOOGUIDDECL const GUID standard_commands::guid_main_open = { 0x69c778aa, 0xb836, 0x40a0, { 0x89, 0xcd, 0x7a, 0x2e, 0x50, 0xc1, 0x2, 0xcb } }; // {EB7FB5A4-5904-4d2c-B66C-D882A3B15277} FOOGUIDDECL const GUID standard_commands::guid_main_remove_playlist = { 0xeb7fb5a4, 0x5904, 0x4d2c, { 0xb6, 0x6c, 0xd8, 0x82, 0xa3, 0xb1, 0x52, 0x77 } }; // {C297BADB-8098-45a9-A5E8-B53A0D780CE3} FOOGUIDDECL const GUID standard_commands::guid_main_remove_dead_entries = { 0xc297badb, 0x8098, 0x45a9, { 0xa5, 0xe8, 0xb5, 0x3a, 0xd, 0x78, 0xc, 0xe3 } }; // {D08C2921-7750-4979-98F9-3A513A31FF96} FOOGUIDDECL const GUID standard_commands::guid_main_remove_duplicates = { 0xd08c2921, 0x7750, 0x4979, { 0x98, 0xf9, 0x3a, 0x51, 0x3a, 0x31, 0xff, 0x96 } }; // {D3A25E47-BA98-4e6b-95AD-A7502919EB75} FOOGUIDDECL const GUID standard_commands::guid_main_rename_playlist = { 0xd3a25e47, 0xba98, 0x4e6b, { 0x95, 0xad, 0xa7, 0x50, 0x29, 0x19, 0xeb, 0x75 } }; // {0FDCFC65-9B39-445a-AA88-4D245F217480} FOOGUIDDECL const GUID standard_commands::guid_main_save_all_playlists = { 0xfdcfc65, 0x9b39, 0x445a, { 0xaa, 0x88, 0x4d, 0x24, 0x5f, 0x21, 0x74, 0x80 } }; // {370B720B-4CF7-465b-908C-2D2ADD027900} FOOGUIDDECL const GUID standard_commands::guid_main_save_playlist = { 0x370b720b, 0x4cf7, 0x465b, { 0x90, 0x8c, 0x2d, 0x2a, 0xdd, 0x2, 0x79, 0x0 } }; // {A6CFC2A8-56B3-4d12-88E7-0237961AC47E} FOOGUIDDECL const GUID standard_commands::guid_main_playlist_search = { 0xa6cfc2a8, 0x56b3, 0x4d12, { 0x88, 0xe7, 0x2, 0x37, 0x96, 0x1a, 0xc4, 0x7e } }; // {383D4E8D-7E30-4fb8-B5DD-8C975D89E58E} FOOGUIDDECL const GUID standard_commands::guid_main_playlist_sel_crop = { 0x383d4e8d, 0x7e30, 0x4fb8, { 0xb5, 0xdd, 0x8c, 0x97, 0x5d, 0x89, 0xe5, 0x8e } }; // {E0EEA319-E282-4e6c-8B82-4DFD42A1D4ED} FOOGUIDDECL const GUID standard_commands::guid_main_playlist_sel_remove = { 0xe0eea319, 0xe282, 0x4e6c, { 0x8b, 0x82, 0x4d, 0xfd, 0x42, 0xa1, 0xd4, 0xed } }; // {F0845920-7F6D-40ac-B2EB-3D00C2C8260B} FOOGUIDDECL const GUID standard_commands::guid_main_playlist_sel_invert = { 0xf0845920, 0x7f6d, 0x40ac, { 0xb2, 0xeb, 0x3d, 0x0, 0xc2, 0xc8, 0x26, 0xb } }; // {29910B33-79E9-40da-992B-5A4AA4281F78} FOOGUIDDECL const GUID standard_commands::guid_main_playlist_undo = { 0x29910b33, 0x79e9, 0x40da, { 0x99, 0x2b, 0x5a, 0x4a, 0xa4, 0x28, 0x1f, 0x78 } }; // {7A9D9450-A8BF-4a88-B44F-DCD83A49DD7A} FOOGUIDDECL const GUID standard_commands::guid_main_playlist_redo = { 0x7a9d9450, 0xa8bf, 0x4a88, { 0xb4, 0x4f, 0xdc, 0xd8, 0x3a, 0x49, 0xdd, 0x7a } }; // {02D89A8A-5F7D-41c3-A215-6731D8621036} FOOGUIDDECL const GUID standard_commands::guid_main_show_console = { 0x2d89a8a, 0x5f7d, 0x41c3, { 0xa2, 0x15, 0x67, 0x31, 0xd8, 0x62, 0x10, 0x36 } }; // {E6970E91-33BE-4288-AC01-4B02F07B5D38} FOOGUIDDECL const GUID standard_commands::guid_main_play_cd = { 0xe6970e91, 0x33be, 0x4288, { 0xac, 0x1, 0x4b, 0x2, 0xf0, 0x7b, 0x5d, 0x38 } }; // {1073AB1D-38ED-4957-8B06-38BC878C1F40} FOOGUIDDECL const GUID standard_commands::guid_main_restart_resetconfig = { 0x1073ab1d, 0x38ed, 0x4957, { 0x8b, 0x6, 0x38, 0xbc, 0x87, 0x8c, 0x1f, 0x40 } }; // {FDC8A1C2-93EF-4415-8C20-60B6517F0B5F} FOOGUIDDECL const GUID standard_commands::guid_main_record = { 0xfdc8a1c2, 0x93ef, 0x4415, { 0x8c, 0x20, 0x60, 0xb6, 0x51, 0x7f, 0xb, 0x5f } }; // {45EB37D2-3CD9-4f0a-9B20-8EAE649D7A9F} FOOGUIDDECL const GUID standard_commands::guid_main_playlist_moveback = { 0x45eb37d2, 0x3cd9, 0x4f0a, { 0x9b, 0x20, 0x8e, 0xae, 0x64, 0x9d, 0x7a, 0x9f } }; // {02298938-596A-41f7-A398-19766A06E6EB} FOOGUIDDECL const GUID standard_commands::guid_main_playlist_moveforward = { 0x2298938, 0x596a, 0x41f7, { 0xa3, 0x98, 0x19, 0x76, 0x6a, 0x6, 0xe6, 0xeb } }; // {E910ACC6-A81A-4a20-9F62-19015BCD1013} FOOGUIDDECL const GUID standard_commands::guid_main_saveconfig = { 0xe910acc6, 0xa81a, 0x4a20, { 0x9f, 0x62, 0x19, 0x1, 0x5b, 0xcd, 0x10, 0x13 } }; // {03445FCC-71D9-4e01-AE02-A557D30B4CD7} FOOGUIDDECL const GUID standard_commands::guid_main_playlist_select_all = { 0x3445fcc, 0x71d9, 0x4e01, { 0xae, 0x2, 0xa5, 0x57, 0xd3, 0xb, 0x4c, 0xd7 } }; // {919FA72B-1DF9-49ee-A8F1-D8BA1DEAA7E9} FOOGUIDDECL const GUID standard_commands::guid_main_show_now_playing = { 0x919fa72b, 0x1df9, 0x49ee, { 0xa8, 0xf1, 0xd8, 0xba, 0x1d, 0xea, 0xa7, 0xe9 } }; // {D8D51855-D79D-49b8-97A8-065785FA2426} FOOGUIDDECL const GUID playlist_manager::class_guid= { 0xd8d51855, 0xd79d, 0x49b8, { 0x97, 0xa8, 0x6, 0x57, 0x85, 0xfa, 0x24, 0x26 } }; // {C303A0F1-8E88-4539-87E5-9E455B7D548C} FOOGUIDDECL const GUID genrand_service::class_guid= { 0xc303a0f1, 0x8e88, 0x4539, { 0x87, 0xe5, 0x9e, 0x45, 0x5b, 0x7d, 0x54, 0x8c } }; // {EB8FA333-F365-46ad-8621-345671567BAA} FOOGUIDDECL const GUID playlist_incoming_item_filter::class_guid= { 0xeb8fa333, 0xf365, 0x46ad, { 0x86, 0x21, 0x34, 0x56, 0x71, 0x56, 0x7b, 0xaa } }; // {FEBD85B5-C12D-45b5-B55D-0D3F432B0C6B} FOOGUIDDECL const GUID library_manager::class_guid= { 0xfebd85b5, 0xc12d, 0x45b5, { 0xb5, 0x5d, 0xd, 0x3f, 0x43, 0x2b, 0xc, 0x6b } }; // {9A08B50F-E8C6-40c0-88C3-7530CF2C3115} FOOGUIDDECL const GUID library_callback::class_guid= { 0x9a08b50f, 0xe8c6, 0x40c0, { 0x88, 0xc3, 0x75, 0x30, 0xcf, 0x2c, 0x31, 0x15 } }; // {D5CAC75A-3023-4dce-8B0D-B502BD056A7C} FOOGUIDDECL const GUID popup_message::class_guid= { 0xd5cac75a, 0x3023, 0x4dce, { 0x8b, 0xd, 0xb5, 0x2, 0xbd, 0x5, 0x6a, 0x7c } }; // {13F9CB11-0EAE-4417-AF0C-8894DEB65E8B} FOOGUIDDECL const GUID app_close_blocker::class_guid= { 0x13f9cb11, 0xeae, 0x4417, { 0xaf, 0xc, 0x88, 0x94, 0xde, 0xb6, 0x5e, 0x8b } }; // {5570A2D2-8F9E-48a7-AFAC-DAC00A3C1636} FOOGUIDDECL const GUID file_operation_callback::class_guid= { 0x5570a2d2, 0x8f9e, 0x48a7, { 0xaf, 0xac, 0xda, 0xc0, 0xa, 0x3c, 0x16, 0x36 } }; // {942F5EFE-D68B-4bde-BA17-C442A8B2A445} FOOGUIDDECL const GUID titleformat_config::class_guid= { 0x942f5efe, 0xd68b, 0x4bde, { 0xba, 0x17, 0xc4, 0x42, 0xa8, 0xb2, 0xa4, 0x45 } }; // {9F33EDF0-B8F0-4d11-B27B-4FC1D3B3489F} FOOGUIDDECL const GUID titleformat_config_callback::class_guid= { 0x9f33edf0, 0xb8f0, 0x4d11, { 0xb2, 0x7b, 0x4f, 0xc1, 0xd3, 0xb3, 0x48, 0x9f } }; // {B905715B-4B26-4e0e-B3B8-FF4A232A4258} FOOGUIDDECL const GUID titleformat_config::config_playlist= { 0xb905715b, 0x4b26, 0x4e0e, { 0xb3, 0xb8, 0xff, 0x4a, 0x23, 0x2a, 0x42, 0x58 } }; // {A2051E09-A1EA-471b-B146-7375413105D2} FOOGUIDDECL const GUID titleformat_config::config_copy= { 0xa2051e09, 0xa1ea, 0x471b, { 0xb1, 0x46, 0x73, 0x75, 0x41, 0x31, 0x5, 0xd2 } }; // {70B56942-3C37-427b-8D58-3A2DF3D835BD} FOOGUIDDECL const GUID titleformat_config::config_statusbar= { 0x70b56942, 0x3c37, 0x427b, { 0x8d, 0x58, 0x3a, 0x2d, 0xf3, 0xd8, 0x35, 0xbd } }; // {8E728600-7BBD-4a5c-A001-40A450427EB6} FOOGUIDDECL const GUID titleformat_config::config_systray= { 0x8e728600, 0x7bbd, 0x4a5c, { 0xa0, 0x1, 0x40, 0xa4, 0x50, 0x42, 0x7e, 0xb6 } }; // {D02F2D01-BED3-46e8-B664-8B1F07E78F69} FOOGUIDDECL const GUID titleformat_config::config_windowtitle= { 0xd02f2d01, 0xbed3, 0x46e8, { 0xb6, 0x64, 0x8b, 0x1f, 0x7, 0xe7, 0x8f, 0x69 } }; // {340099D1-7BEC-4ac6-92A4-77FF01507891} FOOGUIDDECL const GUID config_object::class_guid= { 0x340099d1, 0x7bec, 0x4ac6, { 0x92, 0xa4, 0x77, 0xff, 0x1, 0x50, 0x78, 0x91 } }; // {1BA04122-DE9A-4a90-9FC3-A6A7EC8F9626} FOOGUIDDECL const GUID config_object_notify_manager::class_guid= { 0x1ba04122, 0xde9a, 0x4a90, { 0x9f, 0xc3, 0xa6, 0xa7, 0xec, 0x8f, 0x96, 0x26 } }; // {AD5138E6-CF8F-4482-89B2-B2EAAEDF3B4C} FOOGUIDDECL const GUID standard_config_objects::bool_show_keyboard_shortcuts_in_menus= { 0xad5138e6, 0xcf8f, 0x4482, { 0x89, 0xb2, 0xb2, 0xea, 0xae, 0xdf, 0x3b, 0x4c } }; // {E91F618C-5741-470f-A178-21272C3ECA90} FOOGUIDDECL const GUID standard_config_objects::bool_remember_window_positions= { 0xe91f618c, 0x5741, 0x470f, { 0xa1, 0x78, 0x21, 0x27, 0x2c, 0x3e, 0xca, 0x90 } }; // {5497FAA9-690C-4fb4-8BC0-678CEBCBBDC4} FOOGUIDDECL const GUID standard_config_objects::bool_playback_follows_cursor= { 0x5497faa9, 0x690c, 0x4fb4, { 0x8b, 0xc0, 0x67, 0x8c, 0xeb, 0xcb, 0xbd, 0xc4 } }; // {58D3E2D6-DD6D-48dd-98EB-4EABF0ACFEB8} FOOGUIDDECL const GUID standard_config_objects::bool_cursor_follows_playback= { 0x58d3e2d6, 0xdd6d, 0x48dd, { 0x98, 0xeb, 0x4e, 0xab, 0xf0, 0xac, 0xfe, 0xb8 } }; // {2DF7194D-86FC-4312-98DA-E820DA3E710D} FOOGUIDDECL const GUID standard_config_objects::bool_ui_always_on_top= { 0x2df7194d, 0x86fc, 0x4312, { 0x98, 0xda, 0xe8, 0x20, 0xda, 0x3e, 0x71, 0xd } }; // {B572C86F-4206-40a0-8476-C7B27E74DB2D} FOOGUIDDECL const GUID standard_config_objects::bool_playlist_stop_after_current= { 0xb572c86f, 0x4206, 0x40a0, { 0x84, 0x76, 0xc7, 0xb2, 0x7e, 0x74, 0xdb, 0x2d } }; // {FFDFDA96-699C-4617-A977-22DC2F039B00} FOOGUIDDECL const GUID standard_config_objects::string_gui_last_directory_media= { 0xffdfda96, 0x699c, 0x4617, { 0xa9, 0x77, 0x22, 0xdc, 0x2f, 0x3, 0x9b, 0x0 } }; // {915D3B21-81CB-4b96-9762-1C90FBF371DF} FOOGUIDDECL const GUID standard_config_objects::string_gui_last_directory_playlists= { 0x915d3b21, 0x81cb, 0x4b96, { 0x97, 0x62, 0x1c, 0x90, 0xfb, 0xf3, 0x71, 0xdf } }; // {338B6871-EB1F-4384-BF83-6BFACE5B66BC} FOOGUIDDECL const GUID standard_config_objects::int32_dynamic_bitrate_display_rate= { 0x338b6871, 0xeb1f, 0x4384, { 0xbf, 0x83, 0x6b, 0xfa, 0xce, 0x5b, 0x66, 0xbc } }; // {3E35D949-DCDB-44e1-8013-9D1633C09756} FOOGUIDDECL const GUID config_object_notify::class_guid= { 0x3e35d949, 0xdcdb, 0x44e1, { 0x80, 0x13, 0x9d, 0x16, 0x33, 0xc0, 0x97, 0x56 } }; // {4AC9408E-4D9C-4135-ACB5-2CAB35376FC9} FOOGUIDDECL const GUID titleformat_object::class_guid= { 0x4ac9408e, 0x4d9c, 0x4135, { 0xac, 0xb5, 0x2c, 0xab, 0x35, 0x37, 0x6f, 0xc9 } }; // {25B0D20D-9BA3-4a7b-8D0E-89FAF75F916F} FOOGUIDDECL const GUID tag_processor_id3v2::class_guid= { 0x25b0d20d, 0x9ba3, 0x4a7b, { 0x8d, 0xe, 0x89, 0xfa, 0xf7, 0x5f, 0x91, 0x6f } }; // {AD537D40-499D-4c29-81D4-C0FA496DD58C} FOOGUIDDECL const GUID tag_processor_trailing::class_guid= { 0xad537d40, 0x499d, 0x4c29, { 0x81, 0xd4, 0xc0, 0xfa, 0x49, 0x6d, 0xd5, 0x8c } }; // {160885C6-3AA3-4f60-8718-1240615E6414} FOOGUIDDECL const GUID metadb_handle::class_guid= { 0x160885c6, 0x3aa3, 0x4f60, { 0x87, 0x18, 0x12, 0x40, 0x61, 0x5e, 0x64, 0x14 } }; // {9DBC262F-4795-4292-824B-23A655011A3E} FOOGUIDDECL const GUID threaded_process::class_guid= { 0x9dbc262f, 0x4795, 0x4292, { 0x82, 0x4b, 0x23, 0xa6, 0x55, 0x1, 0x1a, 0x3e } }; // {7B69141B-4271-4070-8998-10CD39249C12} FOOGUIDDECL const GUID threaded_process_callback::class_guid= { 0x7b69141b, 0x4271, 0x4070, { 0x89, 0x98, 0x10, 0xcd, 0x39, 0x24, 0x9c, 0x12 } }; // {64D18B80-5E97-40e4-A30C-A4640C60BCE5} FOOGUIDDECL const GUID metadb_io::class_guid= { 0x64d18b80, 0x5e97, 0x40e4, { 0xa3, 0xc, 0xa4, 0x64, 0xc, 0x60, 0xbc, 0xe5 } }; // {B9218D23-F73D-4b61-A1D9-BFD420CDAC77} FOOGUIDDECL const GUID preferences_page::guid_root= { 0xb9218d23, 0xf73d, 0x4b61, { 0xa1, 0xd9, 0xbf, 0xd4, 0x20, 0xcd, 0xac, 0x77 } }; // {2F0E2232-A5FD-43e4-B6AB-3839B8D1A707} FOOGUIDDECL const GUID preferences_page::guid_hidden= { 0x2f0e2232, 0xa5fd, 0x43e4, { 0xb6, 0xab, 0x38, 0x39, 0xb8, 0xd1, 0xa7, 0x7 } }; // {627C0767-0793-44f8-8087-BE4934311282} FOOGUIDDECL const GUID preferences_page::guid_tools= { 0x627c0767, 0x793, 0x44f8, { 0x80, 0x87, 0xbe, 0x49, 0x34, 0x31, 0x12, 0x82 } }; // {6AAA67B6-732F-4967-899A-18C5F8C70017} FOOGUIDDECL const GUID preferences_page::guid_display= { 0x6aaa67b6, 0x732f, 0x4967, { 0x89, 0x9a, 0x18, 0xc5, 0xf8, 0xc7, 0x0, 0x17 } }; // {67508677-CFCC-4a1c-921C-49B39CC5B986} FOOGUIDDECL const GUID preferences_page::guid_playback= { 0x67508677, 0xcfcc, 0x4a1c, { 0x92, 0x1c, 0x49, 0xb3, 0x9c, 0xc5, 0xb9, 0x86 } }; // {494326C8-88FF-4265-B2B2-E6470BEE13B3} FOOGUIDDECL const GUID preferences_page::guid_visualisations= { 0x494326c8, 0x88ff, 0x4265, { 0xb2, 0xb2, 0xe6, 0x47, 0xb, 0xee, 0x13, 0xb3 } }; // {FC01B529-4BD7-47cd-BAF7-2FB632F0DBB6} FOOGUIDDECL const GUID preferences_page::guid_input= { 0xfc01b529, 0x4bd7, 0x47cd, { 0xba, 0xf7, 0x2f, 0xb6, 0x32, 0xf0, 0xdb, 0xb6 } }; // {2E8E9647-4174-41b2-9EC4-910BE128082E} FOOGUIDDECL const GUID preferences_page::guid_core= { 0x2e8e9647, 0x4174, 0x41b2, { 0x9e, 0xc4, 0x91, 0xb, 0xe1, 0x28, 0x8, 0x2e } }; // {7D0334E5-ADD7-4ff5-9DB8-A618B4824028} FOOGUIDDECL const GUID preferences_page::guid_tag_writing= { 0x7d0334e5, 0xadd7, 0x4ff5, { 0x9d, 0xb8, 0xa6, 0x18, 0xb4, 0x82, 0x40, 0x28 } }; // {2D269FA9-6F78-4cec-9F1F-0A176EFCE77A} FOOGUIDDECL const GUID preferences_page::guid_media_library= { 0x2d269fa9, 0x6f78, 0x4cec, { 0x9f, 0x1f, 0xa, 0x17, 0x6e, 0xfc, 0xe7, 0x7a } }; // {B8C5CEEA-A5F4-4278-AA2D-798E4403001F} FOOGUIDDECL const GUID library_viewer::class_guid= { 0xb8c5ceea, 0xa5f4, 0x4278, { 0xaa, 0x2d, 0x79, 0x8e, 0x44, 0x3, 0x0, 0x1f } }; // {5CD49B5D-D604-4c07-A8FA-FFD8512AFD2B} FOOGUIDDECL const GUID message_loop::class_guid= { 0x5cd49b5d, 0xd604, 0x4c07, { 0xa8, 0xfa, 0xff, 0xd8, 0x51, 0x2a, 0xfd, 0x2b } }; // {932EC96D-4DFD-4ed7-A07C-2A22BE770185} FOOGUIDDECL const GUID chapterizer::class_guid= { 0x932ec96d, 0x4dfd, 0x4ed7, { 0xa0, 0x7c, 0x2a, 0x22, 0xbe, 0x77, 0x1, 0x85 } }; // {7EB442CD-FAD7-4a26-AD7E-16F6FC89207B} FOOGUIDDECL const GUID input_decoder::class_guid = { 0x7eb442cd, 0xfad7, 0x4a26, { 0xad, 0x7e, 0x16, 0xf6, 0xfc, 0x89, 0x20, 0x7b } }; // {8E9BB1D4-A52B-4df6-A929-1AAE4075388A} FOOGUIDDECL const GUID input_info_reader::class_guid = { 0x8e9bb1d4, 0xa52b, 0x4df6, { 0xa9, 0x29, 0x1a, 0xae, 0x40, 0x75, 0x38, 0x8a } }; // {FE40FF66-64C9-4234-B639-028DC8060CF7} FOOGUIDDECL const GUID input_info_writer::class_guid = { 0xfe40ff66, 0x64c9, 0x4234, { 0xb6, 0x39, 0x2, 0x8d, 0xc8, 0x6, 0xc, 0xf7 } }; // {436547FC-C4EF-4322-B59E-E696A25FAB2C} FOOGUIDDECL const GUID input_entry::class_guid = { 0x436547fc, 0xc4ef, 0x4322, { 0xb5, 0x9e, 0xe6, 0x96, 0xa2, 0x5f, 0xab, 0x2c } }; // {3296219B-EBA5-4c32-A193-C9BB174801DA} FOOGUIDDECL const GUID link_resolver::class_guid = { 0x3296219b, 0xeba5, 0x4c32, { 0xa1, 0x93, 0xc9, 0xbb, 0x17, 0x48, 0x1, 0xda } }; // {A49E087E-D064-4b2e-A08D-11264F691FFE} FOOGUIDDECL const GUID playback_queue_callback::class_guid = { 0xa49e087e, 0xd064, 0x4b2e, { 0xa0, 0x8d, 0x11, 0x26, 0x4f, 0x69, 0x1f, 0xfe } }; // {1131D64B-04CB-43ee-95EB-24D18B753248} FOOGUIDDECL const GUID main_thread_callback_manager::class_guid = { 0x1131d64b, 0x4cb, 0x43ee, { 0x95, 0xeb, 0x24, 0xd1, 0x8b, 0x75, 0x32, 0x48 } }; // {87D2C583-7AFB-4e58-B21E-FBD3E6E8F5E7} FOOGUIDDECL const GUID main_thread_callback::class_guid = { 0x87d2c583, 0x7afb, 0x4e58, { 0xb2, 0x1e, 0xfb, 0xd3, 0xe6, 0xe8, 0xf5, 0xe7 } }; // {21656AB9-0255-4f8c-8FB9-1A7748BCE93A} FOOGUIDDECL const GUID mainmenu_group::class_guid = { 0x21656ab9, 0x255, 0x4f8c, { 0x8f, 0xb9, 0x1a, 0x77, 0x48, 0xbc, 0xe9, 0x3a } }; // {2E673A2E-A4EE-419c-94C8-9C838660414C} FOOGUIDDECL const GUID mainmenu_group_popup::class_guid = { 0x2e673a2e, 0xa4ee, 0x419c, { 0x94, 0xc8, 0x9c, 0x83, 0x86, 0x60, 0x41, 0x4c } }; // {35077B8C-6E70-47ba-B9DD-D51500E12F2E} FOOGUIDDECL const GUID mainmenu_commands::class_guid = { 0x35077b8c, 0x6e70, 0x47ba, { 0xb9, 0xdd, 0xd5, 0x15, 0x0, 0xe1, 0x2f, 0x2e } }; // {350B3EA8-6B3E-4346-B6D2-799E98EFC920} FOOGUIDDECL const GUID mainmenu_manager::class_guid = { 0x350b3ea8, 0x6b3e, 0x4346, { 0xb6, 0xd2, 0x79, 0x9e, 0x98, 0xef, 0xc9, 0x20 } }; // {8F487F1F-419F-47a7-8ECF-EC44AF4449A3} FOOGUIDDECL const GUID mainmenu_groups::file = { 0x8f487f1f, 0x419f, 0x47a7, { 0x8e, 0xcf, 0xec, 0x44, 0xaf, 0x44, 0x49, 0xa3 } }; // {F8BE5604-165F-4bb9-B6A9-15E55E0E0D3A} FOOGUIDDECL const GUID mainmenu_groups::view = { 0xf8be5604, 0x165f, 0x4bb9, { 0xb6, 0xa9, 0x15, 0xe5, 0x5e, 0xe, 0xd, 0x3a } }; // {8CDA6B10-0613-4cfd-8730-3B9CBF4C6A39} FOOGUIDDECL const GUID mainmenu_groups::edit = { 0x8cda6b10, 0x613, 0x4cfd, { 0x87, 0x30, 0x3b, 0x9c, 0xbf, 0x4c, 0x6a, 0x39 } }; // {D8A4FC46-5E3D-47aa-97B7-947988228246} FOOGUIDDECL const GUID mainmenu_groups::edit_part1 = { 0xd8a4fc46, 0x5e3d, 0x47aa, { 0x97, 0xb7, 0x94, 0x79, 0x88, 0x22, 0x82, 0x46 } }; // {007682CE-2A99-4b70-8F63-DE765D1C5555} FOOGUIDDECL const GUID mainmenu_groups::edit_part2 = { 0x7682ce, 0x2a99, 0x4b70, { 0x8f, 0x63, 0xde, 0x76, 0x5d, 0x1c, 0x55, 0x55 } }; // {1F572090-D620-4940-85EC-0EFE499FAC03} FOOGUIDDECL const GUID mainmenu_groups::edit_part3 = { 0x1f572090, 0xd620, 0x4940, { 0x85, 0xec, 0xe, 0xfe, 0x49, 0x9f, 0xac, 0x3 } }; // {65FA047A-1599-4b9c-B53D-C3FEB716339D} FOOGUIDDECL const GUID mainmenu_groups::edit_part2_selection = { 0x65fa047a, 0x1599, 0x4b9c, { 0xb5, 0x3d, 0xc3, 0xfe, 0xb7, 0x16, 0x33, 0x9d } }; // {5B8AEF2C-6E1A-420d-B488-3E3A00E39E28} FOOGUIDDECL const GUID mainmenu_groups::edit_part2_sort = { 0x5b8aef2c, 0x6e1a, 0x420d, { 0xb4, 0x88, 0x3e, 0x3a, 0x0, 0xe3, 0x9e, 0x28 } }; // {EE9D6F72-7BC7-4a60-8C28-B96DED252BD3} FOOGUIDDECL const GUID mainmenu_groups::edit_part2_selection_sort = { 0xee9d6f72, 0x7bc7, 0x4a60, { 0x8c, 0x28, 0xb9, 0x6d, 0xed, 0x25, 0x2b, 0xd3 } }; // {53FA5B8A-FCBC-4296-B968-45BAE6888845} FOOGUIDDECL const GUID mainmenu_groups::playback = { 0x53fa5b8a, 0xfcbc, 0x4296, { 0xb9, 0x68, 0x45, 0xba, 0xe6, 0x88, 0x88, 0x45 } }; // {15D22753-9D30-4929-AA14-5124016F7E68} FOOGUIDDECL const GUID mainmenu_groups::library = { 0x15d22753, 0x9d30, 0x4929, { 0xaa, 0x14, 0x51, 0x24, 0x1, 0x6f, 0x7e, 0x68 } }; // {25DC3DB7-996A-4f48-AF53-712032EFA04F} FOOGUIDDECL const GUID mainmenu_groups::help = { 0x25dc3db7, 0x996a, 0x4f48, { 0xaf, 0x53, 0x71, 0x20, 0x32, 0xef, 0xa0, 0x4f } }; // {8EED252D-0A0F-4fc9-9D81-8CF7209A8BF2} FOOGUIDDECL const GUID mainmenu_groups::file_open = { 0x8eed252d, 0xa0f, 0x4fc9, { 0x9d, 0x81, 0x8c, 0xf7, 0x20, 0x9a, 0x8b, 0xf2 } }; // {9E650B8E-C3F3-4495-AF15-6B656060C3B9} FOOGUIDDECL const GUID mainmenu_groups::file_add = { 0x9e650b8e, 0xc3f3, 0x4495, { 0xaf, 0x15, 0x6b, 0x65, 0x60, 0x60, 0xc3, 0xb9 } }; // {9995FAE6-B1C4-457f-A747-5BD120930210} FOOGUIDDECL const GUID mainmenu_groups::file_playlist = { 0x9995fae6, 0xb1c4, 0x457f, { 0xa7, 0x47, 0x5b, 0xd1, 0x20, 0x93, 0x2, 0x10 } }; // {8A324F18-6173-42ec-A640-5E296AD446B3} FOOGUIDDECL const GUID mainmenu_groups::file_etc = { 0x8a324f18, 0x6173, 0x42ec, { 0xa6, 0x40, 0x5e, 0x29, 0x6a, 0xd4, 0x46, 0xb3 } }; // {12F5E247-5A81-4734-8119-8F9BC114FE74} FOOGUIDDECL const GUID mainmenu_groups::playback_controls = { 0x12f5e247, 0x5a81, 0x4734, { 0x81, 0x19, 0x8f, 0x9b, 0xc1, 0x14, 0xfe, 0x74 } }; // {1169B3EB-81B5-4199-8929-7D3EAFD4809F} FOOGUIDDECL const GUID mainmenu_groups::playback_etc = { 0x1169b3eb, 0x81b5, 0x4199, { 0x89, 0x29, 0x7d, 0x3e, 0xaf, 0xd4, 0x80, 0x9f } }; // {29272FEC-21C7-4b00-A9A7-01A8CE675EBF} FOOGUIDDECL const GUID mainmenu_groups::view_visualisations = { 0x29272fec, 0x21c7, 0x4b00, { 0xa9, 0xa7, 0x1, 0xa8, 0xce, 0x67, 0x5e, 0xbf } }; // {2DA055D4-0257-40b2-8281-7967866B485C} FOOGUIDDECL const GUID mainmenu_groups::file_etc_preferences = { 0x2da055d4, 0x257, 0x40b2, { 0x82, 0x81, 0x79, 0x67, 0x86, 0x6b, 0x48, 0x5c } }; // {517BFAE8-A148-4cb2-8CCE-1AD2179FB910} FOOGUIDDECL const GUID mainmenu_groups::file_etc_exit = { 0x517bfae8, 0xa148, 0x4cb2, { 0x8c, 0xce, 0x1a, 0xd2, 0x17, 0x9f, 0xb9, 0x10 } }; // {61190F78-3B3A-4fda-A431-2CF7DA1C96CE} FOOGUIDDECL const GUID mainmenu_groups::view_alwaysontop = { 0x61190f78, 0x3b3a, 0x4fda, { 0xa4, 0x31, 0x2c, 0xf7, 0xda, 0x1c, 0x96, 0xce } }; // {E55F8D65-AE51-47bf-99F9-BB113421CB19} FOOGUIDDECL const GUID mainmenu_groups::help_about = { 0xe55f8d65, 0xae51, 0x47bf, { 0x99, 0xf9, 0xbb, 0x11, 0x34, 0x21, 0xcb, 0x19 } }; // {696DF823-B7AE-4b73-95C8-C1E9A9410726} FOOGUIDDECL const GUID mainmenu_groups::library_refresh= { 0x696df823, 0xb7ae, 0x4b73, { 0x95, 0xc8, 0xc1, 0xe9, 0xa9, 0x41, 0x7, 0x26 } }; // {004BF6ED-2F88-464f-BDC1-278F6E610C2F} FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_1s = { 0x4bf6ed, 0x2f88, 0x464f, { 0xbd, 0xc1, 0x27, 0x8f, 0x6e, 0x61, 0xc, 0x2f } }; // {5B56D124-2ECA-4b0f-9895-2A533B31D29E} FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_5s = { 0x5b56d124, 0x2eca, 0x4b0f, { 0x98, 0x95, 0x2a, 0x53, 0x3b, 0x31, 0xd2, 0x9e } }; // {B582E3CA-D86D-4568-8380-68BC9C93ED0E} FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_10s = { 0xb582e3ca, 0xd86d, 0x4568, { 0x83, 0x80, 0x68, 0xbc, 0x9c, 0x93, 0xed, 0xe } }; // {DE6B96B7-3074-4da9-A260-988E31CEE0F9} FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_30s = { 0xde6b96b7, 0x3074, 0x4da9, { 0xa2, 0x60, 0x98, 0x8e, 0x31, 0xce, 0xe0, 0xf9 } }; // {FEED4AD7-13D2-4846-8833-D91B5F8B4E94} FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_1min = { 0xfeed4ad7, 0x13d2, 0x4846, { 0x88, 0x33, 0xd9, 0x1b, 0x5f, 0x8b, 0x4e, 0x94 } }; // {ECCF4904-03CF-429a-9D99-7A87FA62FD10} FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_2min = { 0xeccf4904, 0x3cf, 0x429a, { 0x9d, 0x99, 0x7a, 0x87, 0xfa, 0x62, 0xfd, 0x10 } }; // {5F0F0AF7-F519-41e6-A8DB-04DF1390E69D} FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_5min = { 0x5f0f0af7, 0xf519, 0x41e6, { 0xa8, 0xdb, 0x4, 0xdf, 0x13, 0x90, 0xe6, 0x9d } }; // {9ABA4292-1B8F-42ac-93AC-34B2A74C6320} FOOGUIDDECL const GUID standard_commands::guid_seek_ahead_10min = { 0x9aba4292, 0x1b8f, 0x42ac, { 0x93, 0xac, 0x34, 0xb2, 0xa7, 0x4c, 0x63, 0x20 } }; // {2F89AB1C-5646-4997-8E3F-92BEE0322A41} FOOGUIDDECL const GUID standard_commands::guid_seek_back_1s = { 0x2f89ab1c, 0x5646, 0x4997, { 0x8e, 0x3f, 0x92, 0xbe, 0xe0, 0x32, 0x2a, 0x41 } }; // {0CE84538-2E21-4482-BFE1-BCEC471467CC} FOOGUIDDECL const GUID standard_commands::guid_seek_back_5s = { 0xce84538, 0x2e21, 0x4482, { 0xbf, 0xe1, 0xbc, 0xec, 0x47, 0x14, 0x67, 0xcc } }; // {9F504218-AF5D-41a8-BCE3-26313FAE65EE} FOOGUIDDECL const GUID standard_commands::guid_seek_back_10s = { 0x9f504218, 0xaf5d, 0x41a8, { 0xbc, 0xe3, 0x26, 0x31, 0x3f, 0xae, 0x65, 0xee } }; // {98239B89-F66E-4160-B650-D9B068C59E63} FOOGUIDDECL const GUID standard_commands::guid_seek_back_30s = { 0x98239b89, 0xf66e, 0x4160, { 0xb6, 0x50, 0xd9, 0xb0, 0x68, 0xc5, 0x9e, 0x63 } }; // {6633226B-9AA9-4810-AFDA-185A281D9FE2} FOOGUIDDECL const GUID standard_commands::guid_seek_back_1min = { 0x6633226b, 0x9aa9, 0x4810, { 0xaf, 0xda, 0x18, 0x5a, 0x28, 0x1d, 0x9f, 0xe2 } }; // {E2F8B8BB-C539-44f3-A300-13185DE52227} FOOGUIDDECL const GUID standard_commands::guid_seek_back_2min = { 0xe2f8b8bb, 0xc539, 0x44f3, { 0xa3, 0x0, 0x13, 0x18, 0x5d, 0xe5, 0x22, 0x27 } }; // {7B41A130-01D2-4a1b-9CAD-6314633C61C4} FOOGUIDDECL const GUID standard_commands::guid_seek_back_5min = { 0x7b41a130, 0x1d2, 0x4a1b, { 0x9c, 0xad, 0x63, 0x14, 0x63, 0x3c, 0x61, 0xc4 } }; // {0B012852-BAF9-4f6b-B947-FAB89AE76B79} FOOGUIDDECL const GUID standard_commands::guid_seek_back_10min = { 0xb012852, 0xbaf9, 0x4f6b, { 0xb9, 0x47, 0xfa, 0xb8, 0x9a, 0xe7, 0x6b, 0x79 } }; // {97215C5E-7228-4237-B52C-A2B5504EF726} FOOGUIDDECL const GUID playback_statistics_collector::class_guid = { 0x97215c5e, 0x7228, 0x4237, { 0xb5, 0x2c, 0xa2, 0xb5, 0x50, 0x4e, 0xf7, 0x26 } }; // {9FB1DA49-AF6F-4fd2-9C73-48A8A109003B} FOOGUIDDECL const GUID dsp_v2::class_guid = { 0x9fb1da49, 0xaf6f, 0x4fd2, { 0x9c, 0x73, 0x48, 0xa8, 0xa1, 0x9, 0x0, 0x3b } }; // {9EC5D45E-10F5-46a7-9546-F3ACEC68B149} FOOGUIDDECL const GUID dsp_entry_v2::class_guid = { 0x9ec5d45e, 0x10f5, 0x46a7, { 0x95, 0x46, 0xf3, 0xac, 0xec, 0x68, 0xb1, 0x49 } }; FOOGUIDDECL const GUID advconfig_entry::guid_root = { 0x34949f34, 0xe655, 0x4f09, { 0xba, 0x50, 0xfa, 0xeb, 0x4d, 0x9b, 0x77, 0x69 } }; FOOGUIDDECL const GUID advconfig_entry::class_guid = { 0x7e84602e, 0xdc49, 0x4047, { 0xaa, 0xee, 0x63, 0x71, 0x8b, 0xbc, 0x5a, 0x1f } }; FOOGUIDDECL const GUID advconfig_branch::class_guid = { 0x6a60b472, 0x91ac, 0x4681, { 0x9d, 0xc2, 0x76, 0xc9, 0x94, 0x0, 0x5b, 0x63 } }; FOOGUIDDECL const GUID advconfig_entry_checkbox::class_guid = { 0x5243aba4, 0x2a3d, 0x4627, { 0x88, 0xef, 0xce, 0xe3, 0x76, 0x1c, 0x7, 0x9c } }; FOOGUIDDECL const GUID advconfig_entry::guid_branch_tagging = { 0xe8fe273f, 0xdd00, 0x476e, { 0xa7, 0x90, 0xe5, 0x9d, 0xf6, 0xb8, 0xf8, 0xd4 } }; FOOGUIDDECL const GUID advconfig_entry::guid_branch_decoding = { 0x904c272b, 0x2317, 0x4c3c, { 0xb2, 0xff, 0xc5, 0xa0, 0x12, 0x5e, 0x2c, 0xc2 } }; FOOGUIDDECL const GUID advconfig_entry_string::class_guid = { 0x185d582d, 0xfbd8, 0x4db3, { 0xbe, 0x23, 0x47, 0xaa, 0xc6, 0x75, 0xfc, 0x11 } }; FOOGUIDDECL const GUID advconfig_entry::guid_branch_tools = { 0x35365484, 0xcc58, 0x4926, { 0x97, 0xe1, 0x5e, 0x63, 0xf3, 0xab, 0xb9, 0xe2 } }; FOOGUIDDECL const GUID advconfig_entry::guid_branch_playback = { 0xc48d430d, 0x112, 0x4922, { 0x97, 0x23, 0x28, 0x38, 0xc7, 0xd9, 0x7d, 0xd7 } }; FOOGUIDDECL const GUID advconfig_entry::guid_branch_display = { 0x6c4bc1c8, 0xbaf4, 0x40c3, { 0x9d, 0xb1, 0x9, 0x50, 0x7f, 0xc, 0xc, 0xb9 } }; FOOGUIDDECL const GUID playback_control_v2::class_guid = { 0x1eb67bda, 0x1345, 0x49ae, { 0x8e, 0x89, 0x50, 0x5, 0xd9, 0x1, 0x62, 0x89 } }; FOOGUIDDECL const GUID preferences_page_v2::class_guid = { 0xce4ebc9e, 0xab20, 0x46f9, { 0x92, 0x5f, 0x88, 0x3b, 0x8, 0x4f, 0x5, 0x69 } }; FOOGUIDDECL const GUID preferences_branch_v2::class_guid = { 0x167ebeb9, 0x8334, 0x4b21, { 0xaf, 0x58, 0xa7, 0x40, 0xa5, 0xd5, 0xb6, 0x66 } }; FOOGUIDDECL const GUID advconfig_entry_enum::class_guid = { 0xb1451540, 0x98ec, 0x4d36, { 0x9f, 0x19, 0xe3, 0x10, 0xfb, 0xa7, 0xab, 0x5a } }; FOOGUIDDECL const GUID info_lookup_handler::class_guid = { 0x4fcfdab7, 0x55b5, 0x47d6, { 0xb1, 0x9d, 0xa4, 0xdc, 0x9f, 0xd7, 0x69, 0x4c } }; FOOGUIDDECL const GUID completion_notify::class_guid = { 0xdf26d586, 0xf7ec, 0x40c3, { 0x9f, 0xe8, 0x2e, 0xa0, 0x72, 0x5d, 0x76, 0xc0 } }; FOOGUIDDECL const GUID metadb_io_v2::class_guid = { 0x265c4ece, 0xffb2, 0x4299, { 0x91, 0xb5, 0x6c, 0xa6, 0x60, 0x3d, 0xa1, 0x53 } }; FOOGUIDDECL const GUID file_info_filter::class_guid = { 0x45d0b800, 0xde83, 0x4a77, { 0xad, 0x34, 0x3f, 0x84, 0x2d, 0x40, 0xe7, 0x95 } }; FOOGUIDDECL const GUID metadb_hint_list::class_guid = { 0x719dc072, 0x8d4d, 0x4aa6, { 0xa6, 0xf3, 0x90, 0x73, 0x7, 0xe5, 0xbc, 0xee } }; FOOGUIDDECL const GUID playlist_incoming_item_filter_v2::class_guid = { 0xf335802b, 0xa522, 0x434d, { 0xbe, 0x89, 0xe8, 0x6d, 0x59, 0x56, 0x16, 0x48 } }; FOOGUIDDECL const GUID process_locations_notify::class_guid = { 0x7d7eddac, 0xf93e, 0x4707, { 0x96, 0x9b, 0xcf, 0x44, 0xa9, 0xe1, 0xfb, 0x78 } }; FOOGUIDDECL const GUID library_manager_v2::class_guid = { 0x900eae79, 0x68d0, 0x4900, { 0xa4, 0xd8, 0x18, 0x20, 0x5, 0xae, 0x33, 0x7e } }; FOOGUIDDECL const GUID packet_decoder::owner_Ogg = { 0x8fa27457, 0xa021, 0x43b9, { 0xad, 0x20, 0xa7, 0x96, 0xdb, 0x94, 0x7c, 0xd1 } }; FOOGUIDDECL const GUID packet_decoder::property_ogg_header = { 0xbeb22c78, 0xeb49, 0x4f9e, { 0x9e, 0x37, 0x57, 0xd8, 0x87, 0x40, 0xfb, 0x4c } }; FOOGUIDDECL const GUID packet_decoder::property_ogg_query_sample_rate = { 0x6226bf73, 0x1c37, 0x4aa1, { 0xae, 0xb1, 0x18, 0x8b, 0x4a, 0xf3, 0xae, 0xf7 } }; FOOGUIDDECL const GUID packet_decoder::property_ogg_packet = { 0xade740be, 0x8e2e, 0x4d0b, { 0xa4, 0x0, 0x3f, 0x6e, 0x8a, 0xf7, 0x71, 0xe4 } }; FOOGUIDDECL const GUID track_property_provider::class_guid = { 0xefcdd365, 0x81fc, 0x4c82, { 0x96, 0x1c, 0xa2, 0xb5, 0x76, 0x1a, 0xf8, 0xb7 } }; FOOGUIDDECL const GUID library_manager_v3::class_guid = { 0x65e34e4d, 0xa9ab, 0x4c02, { 0x9e, 0x8a, 0xe5, 0xf3, 0xb9, 0x55, 0x8c, 0xbe } }; FOOGUIDDECL const GUID core_version_info_v2::class_guid = { 0x273704d7, 0x99ba, 0x4b58, { 0xa0, 0x60, 0x82, 0xab, 0x1, 0xb4, 0x92, 0x25 } }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/hasher_md5.cpp ================================================ #include "foobar2000.h" GUID hasher_md5::guid_from_result(const hasher_md5_result & param) { assert(sizeof(GUID) == sizeof(hasher_md5_result)); GUID temp = * reinterpret_cast<const GUID*>(¶m); byte_order::order_le_to_native_t(temp); return temp; } hasher_md5_result hasher_md5::process_single(const void * p_buffer,t_size p_bytes) { hasher_md5_state state; initialize(state); process(state,p_buffer,p_bytes); return get_result(state); } GUID hasher_md5::process_single_guid(const void * p_buffer,t_size p_bytes) { return guid_from_result(process_single(p_buffer,p_bytes)); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/hasher_md5.h ================================================ struct hasher_md5_state { char m_data[128]; }; struct hasher_md5_result { char m_data[16]; }; inline bool operator==(const hasher_md5_result & p_item1,const hasher_md5_result & p_item2) {return memcmp(&p_item1,&p_item2,sizeof(hasher_md5_result)) == 0;} inline bool operator!=(const hasher_md5_result & p_item1,const hasher_md5_result & p_item2) {return memcmp(&p_item1,&p_item2,sizeof(hasher_md5_result)) != 0;} namespace pfc { template<> class traits_t<hasher_md5_state> : public traits_rawobject {}; template<> class traits_t<hasher_md5_result> : public traits_rawobject {}; } class NOVTABLE hasher_md5 : public service_base { public: virtual void initialize(hasher_md5_state & p_state) = 0; virtual void process(hasher_md5_state & p_state,const void * p_buffer,t_size p_bytes) = 0; virtual hasher_md5_result get_result(const hasher_md5_state & p_state) = 0; static GUID guid_from_result(const hasher_md5_result & param); hasher_md5_result process_single(const void * p_buffer,t_size p_bytes); GUID process_single_guid(const void * p_buffer,t_size p_bytes); GUID get_result_guid(const hasher_md5_state & p_state) {return guid_from_result(get_result(p_state));} //! Helper void process_string(hasher_md5_state & p_state,const char * p_string,t_size p_length = infinite) {return process(p_state,p_string,pfc::strlen_max(p_string,p_length));} FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(hasher_md5); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/info_lookup_handler.h ================================================ //! Service used to access various external (online) track info lookup services, such as freedb, to update file tags with info retrieved from those services. class NOVTABLE info_lookup_handler : public service_base { public: enum { flag_album_lookup = 1 << 0, flag_track_lookup = 1 << 1, }; //! Retrieves human-readable name of the lookup handler to display in user interface. virtual void get_name(pfc::string_base & p_out) = 0; //! Returns one or more of flag_track_lookup, and flag_album_lookup. virtual t_uint32 get_flags() = 0; virtual HICON get_icon(int p_width, int p_height) = 0; //! Performs a lookup. Creates a modeless dialog and returns immediately. //! @param p_items Items to look up. //! @param p_notify Callback to notify caller when the operation has completed. Call on_completion with status code 0 to signal failure/abort, or with code 1 to signal success / new infos in metadb. //! @param p_parent Parent window for the lookup dialog. Caller will typically disable the window while lookup is in progress and enable it back when completion is signaled. virtual void lookup(const pfc::list_base_const_t<metadb_handle_ptr> & p_items,completion_notify_ptr p_notify,HWND p_parent) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(info_lookup_handler); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/initquit.h ================================================ #ifndef _INITQUIT_H_ #define _INITQUIT_H_ #include "service.h" //init/quit callback, on_init is called after main window has been created, on_quit is called before main window is destroyed class NOVTABLE initquit : public service_base { public: virtual void on_init() {} virtual void on_quit() {} FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(initquit); }; template<typename T> class initquit_factory_t : public service_factory_single_t<T> {}; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/input.cpp ================================================ #include "foobar2000.h" bool input_entry::g_find_service_by_path(service_ptr_t<input_entry> & p_out,const char * p_path) { service_ptr_t<input_entry> ptr; service_enum_t<input_entry> e; pfc::string_extension ext(p_path); while(e.next(ptr)) { if (ptr->is_our_path(p_path,ext)) { p_out = ptr; return true; } } return false; } bool input_entry::g_find_service_by_content_type(service_ptr_t<input_entry> & p_out,const char * p_content_type) { service_ptr_t<input_entry> ptr; service_enum_t<input_entry> e; while(e.next(ptr)) { if (ptr->is_our_content_type(p_content_type)) { p_out = ptr; return true; } } return false; } static void prepare_for_open(service_ptr_t<input_entry> & p_service,service_ptr_t<file> & p_file,const char * p_path,filesystem::t_open_mode p_open_mode,abort_callback & p_abort,bool p_from_redirect) { if (p_file.is_empty()) { service_ptr_t<filesystem> fs; if (filesystem::g_get_interface(fs,p_path)) { if (fs->supports_content_types()) { fs->open(p_file,p_path,p_open_mode,p_abort); } } } if (p_file.is_valid()) { pfc::string8 content_type; if (p_file->get_content_type(content_type)) { if (input_entry::g_find_service_by_content_type(p_service,content_type)) return; } } if (input_entry::g_find_service_by_path(p_service,p_path)) { if (p_from_redirect && p_service->is_redirect()) throw exception_io_unsupported_format(); return; } throw exception_io_unsupported_format(); } namespace { bool g_find_inputs_by_content_type(pfc::list_base_t<service_ptr_t<input_entry> > & p_out,const char * p_content_type,bool p_from_redirect) { service_enum_t<input_entry> e; service_ptr_t<input_entry> ptr; bool ret = false; while(e.next(ptr)) { if (!(p_from_redirect && ptr->is_redirect())) { if (ptr->is_our_content_type(p_content_type)) {p_out.add_item(ptr); ret = true;} } } return ret; } bool g_find_inputs_by_path(pfc::list_base_t<service_ptr_t<input_entry> > & p_out,const char * p_path,bool p_from_redirect) { service_enum_t<input_entry> e; service_ptr_t<input_entry> ptr; pfc::string_extension extension(p_path); bool ret = false; while(e.next(ptr)) { if (!(p_from_redirect && ptr->is_redirect())) { if (ptr->is_our_path(p_path,extension)) {p_out.add_item(ptr); ret = true;} } } return ret; } template<typename t_service> void g_open_from_list(service_ptr_t<t_service> & p_instance,pfc::list_base_const_t<service_ptr_t<input_entry> > const & p_list,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) { const t_size count = p_list.get_count(); if (count == 1) { p_list[0]->open(p_instance,p_filehint,p_path,p_abort); } else { bool got_bad_data = false, got_bad_data_multi = false; bool done = false; pfc::string8 bad_data_message; for(t_size n=0;n<count && !done;n++) { try { p_list[n]->open(p_instance,p_filehint,p_path,p_abort); done = true; } catch(exception_io_unsupported_format) { //do nothing, skip over } catch(exception_io_data const & e) { if (!got_bad_data) bad_data_message = e.what(); else got_bad_data_multi = true; got_bad_data = true; } } if (!done) { if (got_bad_data_multi) throw exception_io_data(); else if (got_bad_data) throw exception_io_data(bad_data_message); else throw exception_io_unsupported_format(); } } } template<typename t_service> bool needs_write_access() {return false;} template<> bool needs_write_access<input_info_writer>() {return true;} template<typename t_service> void g_open_t(service_ptr_t<t_service> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) { service_ptr_t<file> l_file = p_filehint; if (l_file.is_empty()) { service_ptr_t<filesystem> fs; if (filesystem::g_get_interface(fs,p_path)) { if (fs->supports_content_types()) { fs->open(l_file,p_path,needs_write_access<t_service>() ? filesystem::open_mode_write_existing : filesystem::open_mode_read,p_abort); } } } if (l_file.is_valid()) { pfc::string8 content_type; if (l_file->get_content_type(content_type)) { pfc::list_hybrid_t<service_ptr_t<input_entry>,4> list; if (g_find_inputs_by_content_type(list,content_type,p_from_redirect)) { g_open_from_list(p_instance,list,l_file,p_path,p_abort); return; } } } { pfc::list_hybrid_t<service_ptr_t<input_entry>,4> list; if (g_find_inputs_by_path(list,p_path,p_from_redirect)) { g_open_from_list(p_instance,list,l_file,p_path,p_abort); return; } } throw exception_io_unsupported_format(); } }; void input_entry::g_open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) { #if 1 g_open_t(p_instance,p_filehint,p_path,p_abort,p_from_redirect); #else service_ptr_t<file> filehint = p_filehint; service_ptr_t<input_entry> entry; prepare_for_open(entry,filehint,p_path,filesystem::open_mode_read,p_abort,p_from_redirect); entry->open_for_decoding(p_instance,filehint,p_path,p_abort); #endif } void input_entry::g_open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) { #if 1 g_open_t(p_instance,p_filehint,p_path,p_abort,p_from_redirect); #else service_ptr_t<file> filehint = p_filehint; service_ptr_t<input_entry> entry; prepare_for_open(entry,filehint,p_path,filesystem::open_mode_read,p_abort,p_from_redirect); entry->open_for_info_read(p_instance,filehint,p_path,p_abort); #endif } void input_entry::g_open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) { #if 1 g_open_t(p_instance,p_filehint,p_path,p_abort,p_from_redirect); #else service_ptr_t<file> filehint = p_filehint; service_ptr_t<input_entry> entry; prepare_for_open(entry,filehint,p_path,filesystem::open_mode_write_existing,p_abort,p_from_redirect); entry->open_for_info_write(p_instance,filehint,p_path,p_abort); #endif } void input_entry::g_open_for_info_write_timeout(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,double p_timeout,bool p_from_redirect) { pfc::lores_timer timer; timer.start(); for(;;) { try { g_open_for_info_write(p_instance,p_filehint,p_path,p_abort,p_from_redirect); break; } catch(exception_io_sharing_violation) { if (timer.query() > p_timeout) throw; p_abort.sleep(0.01); } } } bool input_entry::g_is_supported_path(const char * p_path) { service_ptr_t<input_entry> ptr; service_enum_t<input_entry> e; pfc::string_extension ext(p_path); while(e.next(ptr)) { if (ptr->is_our_path(p_path,ext)) return true; } return false; } void input_open_file_helper(service_ptr_t<file> & p_file,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) { if (p_file.is_empty()) { switch(p_reason) { default: throw pfc::exception_bug_check(); case input_open_info_read: case input_open_decode: filesystem::g_open(p_file,p_path,filesystem::open_mode_read,p_abort); break; case input_open_info_write: filesystem::g_open(p_file,p_path,filesystem::open_mode_write_existing,p_abort); break; } } else { p_file->reopen(p_abort); } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/input.h ================================================ enum { input_flag_no_seeking = 1 << 0, input_flag_no_looping = 1 << 1, input_flag_playback = 1 << 2, input_flag_testing_integrity = 1 << 3, input_flag_allow_inaccurate_seeking = 1 << 4, input_flag_simpledecode = input_flag_no_seeking|input_flag_no_looping, }; //! Class providing interface for retrieval of information (metadata, duration, replaygain, other tech infos) from files. Also see: file_info. \n //! Instantiating: see input_entry.\n //! Implementing: see input_impl. class input_info_reader : public service_base { public: //! Retrieves count of subsongs in the file. 1 for non-multisubsong-enabled inputs. //! Note: multi-subsong handling is disabled for remote files (see: filesystem::is_remote) for performance reasons. Remote files are always assumed to be single-subsong, with null index. virtual t_uint32 get_subsong_count() = 0; //! Retrieves identifier of specified subsong; this identifier is meant to be used in playable_location as well as a parameter for input_info_reader::get_info(). //! @param p_index Index of subsong to query. Must be >=0 and < get_subsong_count(). virtual t_uint32 get_subsong(t_uint32 p_index) = 0; //! Retrieves information about specified subsong. //! @param p_subsong Identifier of the subsong to query. See: input_info_reader::get_subsong(), playable_location. //! @param p_info file_info object to fill. Must be empty on entry. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) = 0; //! Retrieves file stats. Equivalent to calling get_stats() on file object. virtual t_filestats get_file_stats(abort_callback & p_abort) = 0; FB2K_MAKE_SERVICE_INTERFACE(input_info_reader,service_base); }; //! Class providing interface for retrieval of PCM audio data from files.\n //! Instantiating: see input_entry.\n //! Implementing: see input_impl. class input_decoder : public input_info_reader { public: //! Prepares to decode specified subsong; resets playback position to the beginning of specified subsong. This must be called first, before any other input_decoder methods (other than those inherited from input_info_reader). \n //! It is legal to set initialize() more than once, with same or different subsong, to play either the same subsong again or another subsong from same file without full reopen.\n //! Warning: this interface inherits from input_info_reader, it is legal to call any input_info_reader methods even during decoding! Call order is not defined, other than initialize() requirement before calling other input_decoder methods.\n //! @param p_subsong Subsong to decode. Should always be 0 for non-multi-subsong-enabled inputs. //! @param p_flags Specifies additional hints for decoding process. It can be null, or a combination of one or more following constants: \n //! input_flag_no_seeking - Indicates that seek() will never be called. Can be used to avoid building potentially expensive seektables when only sequential reading is needed.\n //! input_flag_no_looping - Certain input implementations can be configured to utilize looping info from file formats they process and keep playing single file forever, or keep repeating it specified number of times. This flag indicates that such features should be disabled, for e.g. ReplayGain scan or conversion.\n //! input_flag_playback - Indicates that decoding process will be used for realtime playback rather than conversion. This can be used to reconfigure features that are relevant only for conversion and take a lot of resources, such as very slow secure CDDA reading. \n //! input_flag_testing_integrity - Indicates that we're testing integrity of the file. Any recoverable problems where decoding would normally continue should cause decoder to fail with exception_io_data. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) = 0; //! Reads/decodes one chunk of audio data. Use false return value to signal end of file (no more data to return). Before calling run(), decoding must be initialized by initialize() call. //! @param p_chunk audio_chunk object receiving decoded data. Contents are valid only the method returns true. //! @param p_abort abort_callback object signaling user aborting the operation. //! @returns true on success (new data decoded), false on EOF. virtual bool run(audio_chunk & p_chunk,abort_callback & p_abort) = 0; //! Seeks to specified time offset. Before seeking or other decoding calls, decoding must be initialized with initialize() call. //! @param p_seconds Time to seek to, in seconds. If p_seconds exceeds length of the object being decoded, succeed, and then return false from next run() call. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void seek(double p_seconds,abort_callback & p_abort) = 0; //! Queries whether resource being read/decoded is seekable. If p_value is set to false, all seek() calls will fail. Before calling can_seek() or other decoding calls, decoding must be initialized with initialize() call. virtual bool can_seek() = 0; //! This function is used to signal dynamic VBR bitrate, etc. Called after each run() (or not called at all if caller doesn't care about dynamic info). //! @param p_out Initially contains currently displayed info (either last get_dynamic_info result or current cached info), use this object to return changed info. //! @param p_timestamp_delta Indicates when returned info should be displayed (in seconds, relative to first sample of last decoded chunk), initially set to 0. //! @returns false to keep old info, or true to indicate that changes have been made to p_info and those should be displayed. virtual bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) = 0; //! This function is used to signal dynamic live stream song titles etc. Called after each run() (or not called at all if caller doesn't care about dynamic info). The difference between this and get_dynamic_info() is frequency and relevance of dynamic info changes - get_dynamic_info_track() returns new info only on track change in the stream, returning new titles etc. //! @param p_out Initially contains currently displayed info (either last get_dynamic_info_track result or current cached info), use this object to return changed info. //! @param p_timestamp_delta Indicates when returned info should be displayed (in seconds, relative to first sample of last decoded chunk), initially set to 0. //! @returns false to keep old info, or true to indicate that changes have been made to p_info and those should be displayed. virtual bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) = 0; //! Called from playback thread before sleeping. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void on_idle(abort_callback & p_abort) = 0; FB2K_MAKE_SERVICE_INTERFACE(input_decoder,input_info_reader); }; //! Class providing interface for writing metadata and replaygain info to files. Also see: file_info. \n //! Instantiating: see input_entry.\n //! Implementing: see input_impl. class input_info_writer : public input_info_reader { public: //! Tells the service to update file tags with new info for specified subsong. //! @param p_subsong Subsong to update. Should be always 0 for non-multisubsong-enabled inputs. //! @param p_info New info to write. Sometimes not all contents of p_info can be written. Caller will typically read info back after successful write, so e.g. tech infos that change with retag are properly maintained. //! @param p_abort abort_callback object signaling user aborting the operation. WARNING: abort_callback object is provided for consistency; if writing tags actually gets aborted, user will be likely left with corrupted file. Anything calling this should make sure that aborting is either impossible, or gives appropriate warning to the user first. virtual void set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) = 0; //! Commits pending updates. In case of multisubsong inputs, set_info should queue the update and perform actual file access in commit(). Otherwise, actual writing can be done in set_info() and then Commit() can just do nothing and always succeed. //! @param p_abort abort_callback object signaling user aborting the operation. WARNING: abort_callback object is provided for consistency; if writing tags actually gets aborted, user will be likely left with corrupted file. Anything calling this should make sure that aborting is either impossible, or gives appropriate warning to the user first. virtual void commit(abort_callback & p_abort) = 0; FB2K_MAKE_SERVICE_INTERFACE(input_info_writer,input_info_reader); }; class input_entry : public service_base { public: //! Determines whether specified content type can be handled by this input. //! @param p_type Content type string to test. virtual bool is_our_content_type(const char * p_type)=0; //! Determines whether specified file type can be handled by this input. This must not use any kind of file access; the result should be only based on file path / extension. //! @param p_full_path Full URL of file being tested. //! @param p_extension Extension of file being tested, provided by caller for performance reasons. virtual bool is_our_path(const char * p_full_path,const char * p_extension)=0; //! Opens specified resource for decoding. //! @param p_instance Receives new input_decoder instance if successful. //! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). //! @param p_path URL of resource being opened. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) = 0; //! Opens specified file for reading info. //! @param p_instance Receives new input_info_reader instance if successful. //! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). //! @param p_path URL of resource being opened. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) = 0; //! Opens specified file for writing info. //! @param p_instance Receives new input_info_writer instance if successful. //! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). //! @param p_path URL of resource being opened. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) = 0; //! Reserved for future use. Do nothing and return until specifications are finalized. virtual void get_extended_data(service_ptr_t<file> p_filehint,const playable_location & p_location,const GUID & p_guid,mem_block_container & p_out,abort_callback & p_abort) = 0; enum { //! Indicates that this service implements some kind of redirector that opens another input for decoding, used to avoid circular call possibility. flag_redirect = 1, //! Indicates that multi-CPU optimizations should not be used. flag_parallel_reads_slow = 2, }; //! See flag_* enums. virtual unsigned get_flags() = 0; inline bool is_redirect() {return (get_flags() & flag_redirect) != 0;} inline bool are_parallel_reads_slow() {return (get_flags() & flag_parallel_reads_slow) != 0;} static bool g_find_service_by_path(service_ptr_t<input_entry> & p_out,const char * p_path); static bool g_find_service_by_content_type(service_ptr_t<input_entry> & p_out,const char * p_content_type); static void g_open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false); static void g_open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false); static void g_open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false); static void g_open_for_info_write_timeout(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,double p_timeout,bool p_from_redirect = false); static bool g_is_supported_path(const char * p_path); void open(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_decoding(p_instance,p_filehint,p_path,p_abort);} void open(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_info_read(p_instance,p_filehint,p_path,p_abort);} void open(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_info_write(p_instance,p_filehint,p_path,p_abort);} FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_entry); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/input_file_type.cpp ================================================ #include "foobar2000.h" void input_file_type::build_openfile_mask(pfc::string_base & out, bool b_include_playlists) { pfc::string8_fastalloc name,mask,mask_alltypes,out_temp; if (b_include_playlists) { service_enum_t<playlist_loader> e; service_ptr_t<playlist_loader> ptr; if (e.first(ptr)) do { if (!mask.is_empty()) mask += ";"; mask += "*."; mask += ptr->get_extension(); } while(e.next(ptr)); out_temp += "Playlists"; out_temp += "|"; out_temp += mask; out_temp += "|"; if (!mask_alltypes.is_empty()) { if (mask_alltypes[mask_alltypes.length()-1]!=';') mask_alltypes += ";"; } mask_alltypes += mask; } { service_enum_t<input_file_type> e; service_ptr_t<input_file_type> ptr; if (e.first(ptr)) do { unsigned n,m = ptr->get_count(); for(n=0;n<m;n++) { name.reset(); mask.reset(); if (ptr->get_name(n,name) && ptr->get_mask(n,mask)) { if (!strchr(name,'|') && !strchr(mask,'|')) { out_temp += name; out_temp += "|"; out_temp += mask; out_temp += "|"; if (!mask_alltypes.is_empty()) { if (mask_alltypes[mask_alltypes.length()-1]!=';') mask_alltypes += ";"; } mask_alltypes += mask; } } } } while(e.next(ptr)); } out.reset(); out += "All files|*.*|"; if (!mask_alltypes.is_empty()) { out += "All supported types|"; out += mask_alltypes; out += "|"; } out += out_temp; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/input_file_type.h ================================================ class input_file_type : public service_base { public: virtual unsigned get_count()=0; virtual bool get_name(unsigned idx,pfc::string_base & out)=0;//e.g. "MPEG file" virtual bool get_mask(unsigned idx,pfc::string_base & out)=0;//e.g. "*.MP3;*.MP2"; separate with semicolons virtual bool is_associatable(unsigned idx) = 0; static void build_openfile_mask(pfc::string_base & out,bool b_include_playlists=true); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_file_type); }; class input_file_type_impl : public service_impl_single_t<input_file_type> { const char * name, * mask; bool m_associatable; public: input_file_type_impl(const char * p_name, const char * p_mask,bool p_associatable) : name(p_name), mask(p_mask), m_associatable(p_associatable) {} unsigned get_count() {return 1;} bool get_name(unsigned idx,pfc::string_base & out) {if (idx==0) {out = name; return true;} else return false;} bool get_mask(unsigned idx,pfc::string_base & out) {if (idx==0) {out = mask; return true;} else return false;} bool is_associatable(unsigned idx) {return m_associatable;} }; #define DECLARE_FILE_TYPE(NAME,MASK) \ namespace { static input_file_type_impl g_filetype_instance(NAME,MASK,true); \ static service_factory_single_ref_t<input_file_type_impl> g_filetype_service(g_filetype_instance); } //USAGE: DECLARE_FILE_TYPE("Blah file","*.blah;*.bleh"); class input_file_type_factory : private service_factory_single_transparent_t<input_file_type_impl> { public: input_file_type_factory(const char * p_name,const char * p_mask,bool p_associatable) : service_factory_single_transparent_t<input_file_type_impl>(p_name,p_mask,p_associatable) {} }; //usage: static input_file_type_factory mytype("blah type","*.bla;*.meh",true) ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/input_impl.h ================================================ enum t_input_open_reason { input_open_info_read, input_open_decode, input_open_info_write }; //! Helper function for input implementation use; ensures that file is open with relevant access mode. This is typically called from input_impl::open() and such. //! @param p_file File object pointer to process. If passed pointer is non-null, the function does nothing and always succeeds; otherwise it attempts to open the file using filesystem API methods. //! @param p_path Path to the file. //! @param p_reason Type of input operation requested. See: input_impl::open() parameters. //! @param p_abort abort_callback object signaling user aborting the operation. void input_open_file_helper(service_ptr_t<file> & p_file,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort); //! This is a class that just declares prototypes of functions that each input needs to implement. See input_decoder / input_info_reader / input_info_writer interfaces for full descriptions of member functions. Since input implementation class is instantiated using a template, you don't need to derive from input_impl as virtual functions are not used on implementation class level. Use input_factory_t template to register input class based on input_impl. class input_impl { public: //! Opens specified file for info read / decoding / info write. This is called only once, immediately after object creation, before any other methods, and no other methods are called if open() fails. //! @param p_filehint Optional; passes file object to use for the operation; if set to null, the implementation should handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). Typically, an input implementation that requires file access calls input_open_file_helper() function to ensure that file is open with relevant access mode (read or read/write). //! @param p_path URL of resource being opened. //! @param p_reason Type of operation requested. Possible values are: \n //! - input_open_info_read - info retrieval methods are valid; \n //! - input_open_decode - info retrieval and decoding methods are valid; \n //! - input_open_info_write - info retrieval and retagging methods are valid; \n //! Note that info retrieval methods are valid in all cases, and they may be called at any point of decoding/retagging process. Results of info retrieval methods (other than get_subsong_count() / get_subsong()) between retag_set_info() and retag_commit() are undefined however; those should not be called during that period. //! @param p_abort abort_callback object signaling user aborting the operation. void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort); //! See: input_info_reader::get_subsong_count(). Valid after open() with any reason. unsigned get_subsong_count(); //! See: input_info_reader::get_subsong(). Valid after open() with any reason. t_uint32 get_subsong(unsigned p_index); //! See: input_info_reader::get_info(). Valid after open() with any reason. void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort); //! See: input_info_reader::get_file_stats(). Valid after open() with any reason. t_filestats get_file_stats(abort_callback & p_abort); //! See: input_decoder::initialize(). Valid after open() with input_open_decode reason. void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort); //! See: input_decoder::run(). Valid after decode_initialize(). bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort); //! See: input_decoder::seek(). Valid after decode_initialize(). void decode_seek(double p_seconds,abort_callback & p_abort); //! See: input_decoder::can_seek(). Valid after decode_initialize(). bool decode_can_seek(); //! See: input_decoder::get_dynamic_info(). Valid after decode_initialize(). bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta); //! See: input_decoder::get_dynamic_info_track(). Valid after decode_initialize(). bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta); //! See: input_decoder::on_idle(). Valid after decode_initialize(). void decode_on_idle(abort_callback & p_abort); //! See: input_info_writer::set_info(). Valid after open() with input_open_info_write reason. void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort); //! See: input_info_writer::commit(). Valid after open() with input_open_info_write reason. void retag_commit(abort_callback & p_abort); //! See: input_entry::is_our_content_type(). static bool g_is_our_content_type(const char * p_content_type); //! See: input_entry::is_our_path(). static bool g_is_our_path(const char * p_path,const char * p_extension); protected: input_impl() {} ~input_impl() {} }; //! This is a class that just declares prototypes of functions that each non-multitrack-enabled input needs to implement. See input_decoder / input_info_reader / input_info_writer interfaces for full descriptions of member functions. Since input implementation class is instantiated using a template, you don't need to derive from input_singletrack_impl as virtual functions are not used on implementation class level. Use input_singletrack_factory_t template to register input class based on input_singletrack_impl. class input_singletrack_impl { public: //! Opens specified file for info read / decoding / info write. This is called only once, immediately after object creation, before any other methods, and no other methods are called if open() fails. //! @param p_filehint Optional; passes file object to use for the operation; if set to null, the implementation should handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). Typically, an input implementation that requires file access calls input_open_file_helper() function to ensure that file is open with relevant access mode (read or read/write). //! @param p_path URL of resource being opened. //! @param p_reason Type of operation requested. Possible values are: \n //! - input_open_info_read - info retrieval methods are valid; \n //! - input_open_decode - info retrieval and decoding methods are valid; \n //! - input_open_info_write - info retrieval and retagging methods are valid; \n //! Note that info retrieval methods are valid in all cases, and they may be called at any point of decoding/retagging process. Results of info retrieval methods (other than get_subsong_count() / get_subsong()) between retag_set_info() and retag_commit() are undefined however; those should not be called during that period. //! @param p_abort abort_callback object signaling user aborting the operation. void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort); //! See: input_info_reader::get_info(). Valid after open() with any reason. \n //! Implementation warning: this is typically also called immediately after tag update and should return newly written content then. void get_info(file_info & p_info,abort_callback & p_abort); //! See: input_info_reader::get_file_stats(). Valid after open() with any reason. \n //! Implementation warning: this is typically also called immediately after tag update and should return new values then. t_filestats get_file_stats(abort_callback & p_abort); //! See: input_decoder::initialize(). Valid after open() with input_open_decode reason. void decode_initialize(unsigned p_flags,abort_callback & p_abort); //! See: input_decoder::run(). Valid after decode_initialize(). bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort); //! See: input_decoder::seek(). Valid after decode_initialize(). void decode_seek(double p_seconds,abort_callback & p_abort); //! See: input_decoder::can_seek(). Valid after decode_initialize(). bool decode_can_seek(); //! See: input_decoder::get_dynamic_info(). Valid after decode_initialize(). bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta); //! See: input_decoder::get_dynamic_info_track(). Valid after decode_initialize(). bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta); //! See: input_decoder::on_idle(). Valid after decode_initialize(). void decode_on_idle(abort_callback & p_abort); //! See: input_info_writer::set_info(). Note that input_info_writer::commit() call isn't forwarded because it's useless in case of non-multitrack-enabled inputs. Valid after open() with input_open_info_write reason. void retag(const file_info & p_info,abort_callback & p_abort); //! See: input_entry::is_our_content_type(). static bool g_is_our_content_type(const char * p_content_type); //! See: input_entry::is_our_path(). static bool g_is_our_path(const char * p_path,const char * p_extension); protected: input_singletrack_impl() {} ~input_singletrack_impl() {} }; //! Misc helper, documentme. class input_singletrack_file_impl { public: void open(service_ptr_t<file> p_file,t_input_open_reason p_reason,abort_callback & p_abort); void get_info(file_info & p_info,abort_callback & p_abort); t_filestats get_file_stats(abort_callback & p_abort); void decode_initialize(unsigned p_flags,abort_callback & p_abort); bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort); void decode_seek(double p_seconds,abort_callback & p_abort); bool decode_can_seek(); bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta); bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta); void decode_on_idle(abort_callback & p_abort); void retag(file_info & p_info,abort_callback & p_abort); static bool g_is_our_content_type(const char * p_content_type); static bool g_is_our_path(const char * p_path,const char * p_extension); protected: input_singletrack_file_impl() {} ~input_singletrack_file_impl() {} }; //! Used internally by standard input_entry implementation; do not use directly. Translates input_decoder / input_info_reader / input_info_writer calls to input_impl calls. template<class I, class t_base> class input_impl_interface_wrapper_t : public t_base { public: void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) { m_instance.open(p_filehint,p_path,p_reason,p_abort); } // input_info_reader methods t_uint32 get_subsong_count() { return m_instance.get_subsong_count(); } t_uint32 get_subsong(t_uint32 p_index) { return m_instance.get_subsong(p_index); } void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) { m_instance.get_info(p_subsong,p_info,p_abort); } t_filestats get_file_stats(abort_callback & p_abort) { return m_instance.get_file_stats(p_abort); } // input_decoder methods void initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) { m_instance.decode_initialize(p_subsong,p_flags,p_abort); } bool run(audio_chunk & p_chunk,abort_callback & p_abort) { return m_instance.decode_run(p_chunk,p_abort); } void seek(double p_seconds,abort_callback & p_abort) { m_instance.decode_seek(p_seconds,p_abort); } bool can_seek() { return m_instance.decode_can_seek(); } bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) { return m_instance.decode_get_dynamic_info(p_out,p_timestamp_delta); } bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) { return m_instance.decode_get_dynamic_info_track(p_out,p_timestamp_delta); } void on_idle(abort_callback & p_abort) { m_instance.decode_on_idle(p_abort); } // input_info_writer methods void set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) { m_instance.retag_set_info(p_subsong,p_info,p_abort); } void commit(abort_callback & p_abort) { m_instance.retag_commit(p_abort); } private: I m_instance; }; //! Helper used by input_singletrack_factory_t, do not use directly. Translates input_impl calls to input_singletrack_impl calls. template<class I> class input_wrapper_singletrack_t { public: input_wrapper_singletrack_t() {} void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) { m_instance.open(p_filehint,p_path,p_reason,p_abort); } void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) { if (p_subsong != 0) throw exception_io_data(); m_instance.get_info(p_info,p_abort); } t_uint32 get_subsong_count() { return 1; } t_uint32 get_subsong(t_uint32 p_index) { assert(p_index == 0); return 0; } t_filestats get_file_stats(abort_callback & p_abort) { return m_instance.get_file_stats(p_abort); } void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) { if (p_subsong != 0) throw exception_io_data(); m_instance.decode_initialize(p_flags,p_abort); } bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {return m_instance.decode_run(p_chunk,p_abort);} void decode_seek(double p_seconds,abort_callback & p_abort) {m_instance.decode_seek(p_seconds,p_abort);} bool decode_can_seek() {return m_instance.decode_can_seek();} bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {return m_instance.decode_get_dynamic_info(p_out,p_timestamp_delta);} bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {return m_instance.decode_get_dynamic_info_track(p_out,p_timestamp_delta);} void decode_on_idle(abort_callback & p_abort) {m_instance.decode_on_idle(p_abort);} void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) { if (p_subsong != 0) throw exception_io_data(); m_instance.retag(p_info,p_abort); } void retag_commit(abort_callback & p_abort) {} static bool g_is_our_content_type(const char * p_content_type) {return I::g_is_our_content_type(p_content_type);} static bool g_is_our_path(const char * p_path,const char * p_extension) {return I::g_is_our_path(p_path,p_extension);} private: I m_instance; }; //! Helper; standard input_entry implementation. Do not instantiate this directly, use input_factory_t or one of other input_*_factory_t helpers instead. template<class I,unsigned t_flags> class input_entry_impl_t : public input_entry { private: template<class T> void instantiate_t(service_ptr_t<T> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) { service_ptr_t< service_impl_t<input_impl_interface_wrapper_t<I,T> > > temp; temp = new service_impl_t<input_impl_interface_wrapper_t<I,T> >(); temp->open(p_filehint,p_path,p_reason,p_abort); p_instance = temp.get_ptr(); } public: bool is_our_content_type(const char * p_type) {return I::g_is_our_content_type(p_type);} bool is_our_path(const char * p_full_path,const char * p_extension) {return I::g_is_our_path(p_full_path,p_extension);} void open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) { instantiate_t(p_instance,p_filehint,p_path,input_open_decode,p_abort); } void open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) { instantiate_t(p_instance,p_filehint,p_path,input_open_info_read,p_abort); } void open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) { instantiate_t(p_instance,p_filehint,p_path,input_open_info_write,p_abort); } void get_extended_data(service_ptr_t<file> p_filehint,const playable_location & p_location,const GUID & p_guid,mem_block_container & p_out,abort_callback & p_abort) { p_out.reset(); } unsigned get_flags() {return t_flags;} }; //! Stardard input factory. For reference of functions that must be supported by registered class, see input_impl.\n Usage: static input_factory_t<myinputclass> g_myinputclass_factory;\n Note that input classes can't be registered through service_factory_t template directly. template<class T> class input_factory_t : public service_factory_single_t<input_entry_impl_t<T,0> > {}; //! Non-multitrack-enabled input factory (helper) - hides multitrack management functions from input implementation; use this for inputs that handle file types where each physical file can contain only one user-visible playable track. For reference of functions that must be supported by registered class, see input_singletrack_impl.\n Usage: static input_singletrack_factory_t<myinputclass> g_myinputclass_factory;\n Note that input classes can't be registered through service_factory_t template directly.template<class T> template<class T> class input_singletrack_factory_t : public service_factory_single_t<input_entry_impl_t<input_wrapper_singletrack_t<T>,0> > {}; //! Extended version of input_factory_t, with non-default flags. See: input_factory_t, input_entry::get_flags(). template<class T,unsigned t_flags> class input_factory_ex_t : public service_factory_single_t<input_entry_impl_t<T,t_flags> > {}; //! Extended version of input_singletrack_factory_t, with non-default flags. See: input_singletrack_factory_t, input_entry::get_flags(). template<class T,unsigned t_flags> class input_singletrack_factory_ex_t : public service_factory_single_t<input_entry_impl_t<input_wrapper_singletrack_t<T>,t_flags> > {}; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/library_manager.h ================================================ #ifndef _foobar2000_sdk_library_manager_h_ #define _foobar2000_sdk_library_manager_h_ /*! This service implements methods allowing you to interact with the Media Library.\n All methods are valid from main thread only.\n To avoid race conditions, methods that alter library contents should not be called from inside global callbacks.\n Usage: Use static_api_ptr_t<library_manager> to instantiate. */ class NOVTABLE library_manager : public service_base { public: //! Interface for use with library_manager::enum_items(). class NOVTABLE enum_callback { public: //! Return true to continue enumeration, false to abort. virtual bool on_item(const metadb_handle_ptr & p_item) = 0; }; //! Returns whether specified item is in the Media Library or not. virtual bool is_item_in_library(const metadb_handle_ptr & p_item) = 0; //! Returns whether current user settings allow specified item to be added to the Media Library or not. virtual bool is_item_addable(const metadb_handle_ptr & p_item) = 0; //! Returns whether current user settings allow specified item path to be added to the Media Library or not. virtual bool is_path_addable(const char * p_path) = 0; //! Retrieves path of specified item relative to Media Library directory it is in. Returns true on success, false when the item is not in the Media Library. virtual bool get_relative_path(const metadb_handle_ptr & p_item,pfc::string_base & p_out) = 0; //! Calls callback method for every item in the Media Library. Note that order of items in Media Library is undefined. virtual void enum_items(enum_callback & p_callback) = 0; //! Adds specified items to the Media Library (items actually added will be filtered according to user settings). virtual void add_items(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0; //! Removes specified items from the Media Library (does nothing if specific item is not in the Media Library). virtual void remove_items(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0; //! Adds specified items to the Media Library (items actually added will be filtered according to user settings). The difference between this and add_items() is that items are not added immediately; the operation is queued and executed later, so it is safe to call from e.g. global callbacks. virtual void add_items_async(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0; //! For internal use only; p_data must be sorted by metadb::path_compare; use file_operation_callback static methods instead of calling this directly. virtual void on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_data) = 0; //! Retrieves entire Media Library content. virtual void get_all_items(pfc::list_base_t<metadb_handle_ptr> & p_out) = 0; //! Returns whether Media Library functionality is enabled or not, for e.g. notifying user to change settings when trying to use a Media Library viewer without having configured Media Library first. virtual bool is_library_enabled() = 0; //! Pops up Media Library preferences page. virtual void show_preferences() = 0; //! Deprecated; use library_manager_v2::rescan_async() when possible.\n //! Rescans user-specified Media Library directories for new files and removes references to files that no longer exist from the Media Library.\n //! Note that this function creates modal dialog and does not return until the operation has completed.\n virtual void rescan() = 0; //! Deprecated; use library_manager_v2::check_dead_entries_async() when possible.\n //! Hints Media Library about possible dead items, typically used for "remove dead entries" context action in ML viewers. The implementation will verify whether the items are actually dead before ML contents are altered.\n //! Note that this function creates modal dialog and does not return until the operation has completed.\n virtual void check_dead_entries(const pfc::list_base_t<metadb_handle_ptr> & p_list) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_manager); }; //! New in 0.9.3 class NOVTABLE library_manager_v2 : public library_manager { public: //! Returns whether a rescan process is currently running. virtual bool is_rescan_running() = 0; //! Starts an async rescan process. Note that if another process is already running, the process is silently aborted. //! @param p_parent Parent window for displayed progress dialog. //! @param p_notify Allows caller to receive notifications about the process finishing. Status code: 1 on success, 0 on user abort. Pass NULL if caller doesn't care. virtual void rescan_async(HWND p_parent,completion_notify_ptr p_notify) = 0; //! Hints Media Library about possible dead items, typically used for "remove dead entries" context action in ML viewers. The implementation will verify whether the items are actually dead before ML contents are altered.\n //! @param p_list List of items to process. //! @param p_parent Parent window for displayed progress dialog. //! @param p_notify Allows caller to receive notifications about the process finishing. Status code: 1 on success, 0 on user abort. Pass NULL if caller doesn't care. virtual void check_dead_entries_async(const pfc::list_base_t<metadb_handle_ptr> & p_list,HWND p_parent,completion_notify_ptr p_notify) = 0; FB2K_MAKE_SERVICE_INTERFACE(library_manager_v2,library_manager); }; //! New in 0.9.4 class NOVTABLE library_manager_v3 : public library_manager_v2 { public: //! Retrieves directory path and subdirectory/filename formatting scheme for newly encoded/copied/moved tracks. //! @returns True on success, false when the feature has not been configured. virtual bool get_new_file_pattern_tracks(pfc::string_base & p_directory,pfc::string_base & p_format) = 0; //! Retrieves directory path and subdirectory/filename formatting scheme for newly encoded/copied/moved full album images. //! @returns True on success, false when the feature has not been configured. virtual bool get_new_file_pattern_images(pfc::string_base & p_directory,pfc::string_base & p_format) = 0; FB2K_MAKE_SERVICE_INTERFACE(library_manager_v3,library_manager_v2); }; //! Callback service receiving notifications about Media Library content changes. Methods called only from main thread.\n //! Use library_callback_factory_t template to register. class NOVTABLE library_callback : public service_base { public: //! Called when new items are added to the Media Library. virtual void on_items_added(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0; //! Called when some items have been removed from the Media Library. virtual void on_items_removed(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0; //! Called when some items in the Media Library have been modified. virtual void on_items_modified(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_callback); }; template<typename T> class library_callback_factory_t : public service_factory_single_t<T> {}; //! Implement this service to appear on "library viewers" list in Media Library preferences page.\n //! Use library_viewer_factory_t to register. class NOVTABLE library_viewer : public service_base { public: //! Retrieves GUID of your preferences page (pfc::guid_null if you don't have one). virtual GUID get_preferences_page() = 0; //! Queries whether "activate" action is supported (relevant button will be disabled if it's not). virtual bool have_activate() = 0; //! Activates your Media Library viewer component (e.g. shows its window). virtual void activate() = 0; //! Retrieves GUID of your library_viewer implementation, for internal identification. Note that this not the same as preferences page GUID. virtual GUID get_guid() = 0; //! Retrieves name of your Media Library viewer, a null-terminated UTF-8 encoded string. virtual const char * get_name() = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_viewer); }; template<typename T> class library_viewer_factory_t : public service_factory_single_t<T> {}; #endif _foobar2000_sdk_library_manager_h_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/link_resolver.cpp ================================================ #include "foobar2000.h" bool link_resolver::g_find(service_ptr_t<link_resolver> & p_out,const char * p_path) { service_enum_t<link_resolver> e; service_ptr_t<link_resolver> ptr; pfc::string_extension ext(p_path); while(e.next(ptr)) { if (ptr->is_our_path(p_path,ext)) { p_out = ptr; return true; } } return false; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/link_resolver.h ================================================ #ifndef _foobar2000_sdk_link_resolver_h_ #define _foobar2000_sdk_link_resolver_h_ //! Interface for resolving different sorts of link files. //! Utilized by mechanisms that convert filesystem path into list of playable locations. //! For security reasons, link may only point to playable object path, not to a playlist or another link. class NOVTABLE link_resolver : public service_base { public: //! Tests whether specified file is supported by this link_resolver service. //! @param p_path Path of file being queried. //! @param p_extension Extension of file being queried. This is provided for performance reasons, path already includes it. virtual bool is_our_path(const char * p_path,const char * p_extension) = 0; //! Resolves a link file. Before this is called, path must be accepted by is_our_path(). //! @param p_filehint Optional file interface to use. If null/empty, implementation should open file by itself. //! @param p_path Path of link file to resolve. //! @param p_out Receives path the link is pointing to. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void resolve(service_ptr_t<file> p_filehint,const char * p_path,pfc::string_base & p_out,abort_callback & p_abort) = 0; //! Helper function; finds link_resolver interface that supports specified link file. //! @param p_out Receives link_resolver interface on success. //! @param p_path Path of file to query. //! @returns True on success, false on failure (no interface that supports specified path could be found). static bool g_find(service_ptr_t<link_resolver> & p_out,const char * p_path); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(link_resolver); }; #endif //_foobar2000_sdk_link_resolver_h_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/main_thread_callback.h ================================================ //! Callback object class for main_thread_callback_manager service. class NOVTABLE main_thread_callback : public service_base { public: //! Gets called from main app thread. See main_thread_callback_manager description for more info. virtual void callback_run() = 0; FB2K_MAKE_SERVICE_INTERFACE(main_thread_callback,service_base); }; /*! Allows you to queue a callback object to be called from main app thread. This is commonly used to trigger main-thread-only API calls from worker threads.\n This can be also used from main app thread, to avoid race conditions when trying to use APIs that dispatch global callbacks from inside some other global callback, or using message loops / modal dialogs inside global callbacks. */ class NOVTABLE main_thread_callback_manager : public service_base { public: //! Queues a callback object. This can be called from any thread, implementation ensures multithread safety. Implementation will call p_callback->callback_run() once later. To get it called repeatedly, you would need to add your callback again. virtual void add_callback(service_ptr_t<main_thread_callback> p_callback) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(main_thread_callback_manager); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/mainmenu.cpp ================================================ #include "foobar2000.h" bool mainmenu_commands::g_execute(const GUID & p_guid,service_ptr_t<service_base> p_callback) { service_enum_t<mainmenu_commands> e; service_ptr_t<mainmenu_commands> ptr; while(e.next(ptr)) { const t_uint32 count = ptr->get_command_count(); for(t_uint32 n=0;n<count;n++) { if (ptr->get_command(n) == p_guid) { ptr->execute(n,p_callback); return true; } } } return false; } bool mainmenu_commands::g_find_by_name(const char * p_name,GUID & p_guid) { service_enum_t<mainmenu_commands> e; service_ptr_t<mainmenu_commands> ptr; pfc::string8_fastalloc temp; while(e.next(ptr)) { const t_uint32 count = ptr->get_command_count(); for(t_uint32 n=0;n<count;n++) { ptr->get_name(n,temp); if (stricmp_utf8(temp,p_name) == 0) { p_guid = ptr->get_command(n); return true; } } } return false; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/masstagger_action.h ================================================ #ifndef _FOOBAR2000_MASSTAG_ACTION_H_ #define _FOOBAR2000_MASSTAG_ACTION_H_ class NOVTABLE masstagger_action : public service_base { public: /** * Get name to display on list of available actions. * \param p_out store name in here */ virtual void get_name(pfc::string_base & p_out)=0; /** * Initialize and show configuration with default values, if appropriate. * \param p_parent handle to parent window * \returns true iff succesful */ virtual bool initialize(HWND p_parent) = 0; /** * Initialize and get configuration from parameter. * \param p_data a zero-terminated string containing previously stored configuration data. * \returns true iff succesful */ virtual bool initialize_fromconfig(const char * p_data)=0; /** * Show configuration with current values. * \param parent handle to parent window * \returns true if display string needs to be updated. */ virtual bool configure(HWND p_parent) = 0; /** * Get name to display on list of configured actions. * You should include value of settings, if possible. * \param p_name store name in here */ virtual void get_display_string(pfc::string_base & p_name) = 0; /** * Get current settings as zero-terminated string. * \param p_data store settings in here */ virtual void get_config(pfc::string_base & p_data) = 0; /** * Apply action on file info. * \param p_location location of current item * \param p_info file info of current item, contains modifications from previous actions * \param p_index zero-based index of current item in list of processed items * \param p_count number of processed items */ virtual void run(const playable_location & p_location, file_info * p_info, t_size p_index, t_size p_count) = 0; /** * Get GUID. * Used for identification when storing scripts. * \returns your GUID */ virtual const GUID & get_guid()=0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(masstagger_action); }; DECLARE_CLASS_GUID(masstagger_action,0x49da1f6c, 0x6744, 0x47f3, 0xbf, 0x93, 0x67, 0x1c, 0x37, 0x79, 0xff, 0xcd); template<typename T> class masstagger_action_factory_t : public service_factory_t<T> {}; #endif //_FOOBAR2000_MASSTAG_ACTION_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/mem_block_container.cpp ================================================ #include "foobar2000.h" void mem_block_container::from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) { if (p_bytes == 0) {set_size(0);} set_size(p_bytes); p_stream->read_object(get_ptr(),p_bytes,p_abort); } void mem_block_container::set(const void * p_buffer,t_size p_size) { set_size(p_size); memcpy(get_ptr(),p_buffer,p_size); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/mem_block_container.h ================================================ //! Generic interface for a memory block; used by various other interfaces to return memory blocks while allowing caller to allocate. class NOVTABLE mem_block_container { public: virtual const void * get_ptr() const = 0; virtual void * get_ptr() = 0; virtual t_size get_size() const = 0; virtual void set_size(t_size p_size) = 0; void from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort); void set(const void * p_buffer,t_size p_size); inline void copy(const mem_block_container & p_source) {set(p_source.get_ptr(),p_source.get_size());} inline void reset() {set_size(0);} const mem_block_container & operator=(const mem_block_container & p_source) {copy(p_source);return *this;} protected: mem_block_container() {} ~mem_block_container() {} }; //! mem_block_container implementation. template<template<typename> class t_alloc = pfc::alloc_standard> class mem_block_container_impl_t : public mem_block_container { public: const void * get_ptr() const {return m_data.get_ptr();} void * get_ptr() {return m_data.get_ptr();} t_size get_size() const {return m_data.get_size();} void set_size(t_size p_size) { m_data.set_size(p_size); } private: pfc::array_t<t_uint8,t_alloc> m_data; }; typedef mem_block_container_impl_t<> mem_block_container_impl; class mem_block_container_temp_impl : public mem_block_container { public: mem_block_container_temp_impl(void * p_buffer,t_size p_size) : m_buffer(p_buffer), m_buffer_size(p_size), m_size(0) {} const void * get_ptr() const {return m_buffer;} void * get_ptr() {return m_buffer;} t_size get_size() const {return m_size;} void set_size(t_size p_size) {if (p_size > m_buffer_size) throw pfc::exception_overflow(); m_size = p_size;} private: t_size m_size,m_buffer_size; void * m_buffer; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/menu.h ================================================ class NOVTABLE mainmenu_group : public service_base { public: virtual GUID get_guid() = 0; virtual GUID get_parent() = 0; virtual t_uint32 get_sort_priority() = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(mainmenu_group); }; class NOVTABLE mainmenu_group_popup : public mainmenu_group { public: virtual void get_display_string(pfc::string_base & p_out) = 0; FB2K_MAKE_SERVICE_INTERFACE(mainmenu_group_popup,mainmenu_group); }; class NOVTABLE mainmenu_commands : public service_base { public: enum { flag_disabled = 1<<0, flag_checked = 1<<1, sort_priority_base = 0x10000, sort_priority_dontcare = 0x80000000, sort_priority_last = infinite, }; //! Retrieves number of implemented commands. Index parameter of other methods must be in 0....command_count-1 range. virtual t_uint32 get_command_count() = 0; //! Retrieves GUID of specified command. virtual GUID get_command(t_uint32 p_index) = 0; //! Retrieves name of item, for list of commands to assign keyboard shortcuts to etc. virtual void get_name(t_uint32 p_index,pfc::string_base & p_out) = 0; //! Retrieves item's description for statusbar etc. virtual bool get_description(t_uint32 p_index,pfc::string_base & p_out) = 0; //! Retrieves GUID of owning menu group. virtual GUID get_parent() = 0; //! Retrieves sorting priority of the command; the lower the number, the upper in the menu your commands will appear. Third party components should use sorting_priority_base and up (values below are reserved for internal use). In case of equal priority, order is undefined. virtual t_uint32 get_sort_priority() {return sort_priority_dontcare;} //! Retrieves display string and display flags to use when menu is about to be displayed. If returns false, menu item won't be displayed. You can create keyboard-shortcut-only commands by always returning false from get_display(). virtual bool get_display(t_uint32 p_index,pfc::string_base & p_text,t_uint32 & p_flags) {p_flags = 0;get_name(p_index,p_text);return true;} //! Executes the command. p_callback parameter is reserved for future use and should be ignored / set to null pointer. virtual void execute(t_uint32 p_index,service_ptr_t<service_base> p_callback) = 0; static bool g_execute(const GUID & p_guid,service_ptr_t<service_base> p_callback = service_ptr_t<service_base>()); static bool g_find_by_name(const char * p_name,GUID & p_guid); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(mainmenu_commands); }; class NOVTABLE mainmenu_manager : public service_base { public: enum { flag_show_shortcuts = 1 << 0, flag_show_shortcuts_global = 1 << 1, }; virtual void instantiate(const GUID & p_root) = 0; #ifdef _WIN32 virtual void generate_menu_win32(HMENU p_menu,t_uint32 p_id_base,t_uint32 p_id_count,t_uint32 p_flags) = 0; #else #error portme #endif //@param p_id Identifier of command to execute, relative to p_id_base of generate_menu_*() //@returns true if command was executed successfully, false if not (e.g. command with given identifier not found). virtual bool execute_command(t_uint32 p_id,service_ptr_t<service_base> p_callback = service_ptr_t<service_base>()) = 0; virtual bool get_description(t_uint32 p_id,pfc::string_base & p_out) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(mainmenu_manager); }; class mainmenu_groups { public: static const GUID file,view,edit,playback,library,help; static const GUID file_open,file_add,file_playlist,file_etc; static const GUID playback_controls,playback_etc; static const GUID view_visualisations, view_alwaysontop; static const GUID edit_part1,edit_part2,edit_part3; static const GUID edit_part2_selection,edit_part2_sort,edit_part2_selection_sort; static const GUID file_etc_preferences, file_etc_exit; static const GUID help_about; static const GUID library_refresh; enum {priority_edit_part1,priority_edit_part2,priority_edit_part3}; enum {priority_edit_part2_commands,priority_edit_part2_selection,priority_edit_part2_sort}; enum {priority_edit_part2_selection_commands,priority_edit_part2_selection_sort}; enum {priority_file_open,priority_file_add,priority_file_playlist,priority_file_etc = mainmenu_commands::sort_priority_last}; }; class mainmenu_group_impl : public mainmenu_group { public: mainmenu_group_impl(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority) : m_guid(p_guid), m_parent(p_parent), m_priority(p_priority) {} GUID get_guid() {return m_guid;} GUID get_parent() {return m_parent;} t_uint32 get_sort_priority() {return m_priority;} private: GUID m_guid,m_parent; t_uint32 m_priority; }; class mainmenu_group_popup_impl : public mainmenu_group_popup { public: mainmenu_group_popup_impl(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority,const char * p_name) : m_guid(p_guid), m_parent(p_parent), m_priority(p_priority), m_name(p_name) {} GUID get_guid() {return m_guid;} GUID get_parent() {return m_parent;} t_uint32 get_sort_priority() {return m_priority;} void get_display_string(pfc::string_base & p_out) {p_out = m_name;} private: GUID m_guid,m_parent; t_uint32 m_priority; pfc::string8 m_name; }; typedef service_factory_single_t<mainmenu_group_impl> __mainmenu_group_factory; typedef service_factory_single_t<mainmenu_group_popup_impl> __mainmenu_group_popup_factory; class mainmenu_group_factory : public __mainmenu_group_factory { public: inline mainmenu_group_factory(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority) : __mainmenu_group_factory(p_guid,p_parent,p_priority) {} }; class mainmenu_group_popup_factory : public __mainmenu_group_popup_factory { public: inline mainmenu_group_popup_factory(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority,const char * p_name) : __mainmenu_group_popup_factory(p_guid,p_parent,p_priority,p_name) {} }; template<typename T> class mainmenu_commands_factory_t : public service_factory_single_t<T> {}; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/menu_helpers.cpp ================================================ #include "foobar2000.h" bool menu_helpers::context_get_description(const GUID& p_guid,pfc::string_base & out) { service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr; if (e.first(ptr)) do { unsigned action,num_actions = ptr->get_num_items(); for(action=0;action<num_actions;action++) { if (p_guid == ptr->get_item_guid(action)) { bool rv = ptr->get_item_description(action,out); if (!rv) out.reset(); return rv; } } } while(e.next(ptr)); return false; } static bool run_context_command_internal(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller) { bool done = false; if (data.get_count() > 0) { service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr; if (e.first(ptr)) do { unsigned action,num_actions = ptr->get_num_items(); for(action=0;action<num_actions;action++) { if (p_command == ptr->get_item_guid(action)) { TRACK_CALL_TEXT("menu_helpers::run_command(), by GUID"); ptr->item_execute_simple(action,p_subcommand,data,caller); done = true; break; } } if (done) break; } while(e.next(ptr)); } return done; } bool menu_helpers::run_command_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data) { return run_context_command_internal(p_command,p_subcommand,data,contextmenu_item::caller_undefined); } bool menu_helpers::run_command_context_ex(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller) { return run_context_command_internal(p_command,p_subcommand,data,caller); } bool menu_helpers::test_command_context(const GUID & p_guid) { service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr; bool done = false; if (e.first(ptr)) do { unsigned action,num_actions = ptr->get_num_items(); for(action=0;action<num_actions;action++) { if (ptr->get_item_guid(action) == p_guid) { done = true; break; } } if (done) break; } while(e.next(ptr)); return done; } static bool g_is_checked(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller) { service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr; bool done = false, rv = false; pfc::string8_fastalloc dummystring; if (e.first(ptr)) do { unsigned action,num_actions = ptr->get_num_items(); for(action=0;action<num_actions;action++) { if (p_command == ptr->get_item_guid(action)) { unsigned displayflags = 0; if (ptr->item_get_display_data(dummystring,displayflags,action,p_subcommand,data,caller)) { rv = !!(displayflags & contextmenu_item_node::FLAG_CHECKED); done = true; break; } } } if (done) break; } while(e.next(ptr)); return rv; } bool menu_helpers::is_command_checked_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data) { return g_is_checked(p_command,p_subcommand,data,contextmenu_item::caller_undefined); } bool menu_helpers::is_command_checked_context_playlist(const GUID & p_command,const GUID & p_subcommand) { metadb_handle_list temp; static_api_ptr_t<playlist_manager> api; api->activeplaylist_get_selected_items(temp); return g_is_checked(p_command,p_subcommand,temp,contextmenu_item::caller_playlist); } bool menu_helpers::run_command_context_playlist(const GUID & p_command,const GUID & p_subcommand) { metadb_handle_list temp; static_api_ptr_t<playlist_manager> api; api->activeplaylist_get_selected_items(temp); return run_command_context_ex(p_command,p_subcommand,temp,contextmenu_item::caller_playlist); } bool menu_helpers::run_command_context_now_playing(const GUID & p_command,const GUID & p_subcommand) { metadb_handle_ptr item; if (!static_api_ptr_t<playback_control>()->get_now_playing(item)) return false;//not playing return run_command_context_ex(p_command,p_subcommand,pfc::list_single_ref_t<metadb_handle_ptr>(item),contextmenu_item::caller_now_playing); } bool menu_helpers::guid_from_name(const char * p_name,unsigned p_name_len,GUID & p_out) { service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr; pfc::string8_fastalloc nametemp; while(e.next(ptr)) { unsigned n, m = ptr->get_num_items(); for(n=0;n<m;n++) { ptr->get_item_name(n,nametemp); if (!strcmp_ex(nametemp,infinite,p_name,p_name_len)) { p_out = ptr->get_item_guid(n); return true; } } } return false; } bool menu_helpers::name_from_guid(const GUID & p_guid,pfc::string_base & p_out) { service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr; pfc::string8_fastalloc nametemp; while(e.next(ptr)) { unsigned n, m = ptr->get_num_items(); for(n=0;n<m;n++) { if (p_guid == ptr->get_item_guid(n)) { ptr->get_item_name(n,p_out); return true; } } } return false; } static unsigned calc_total_action_count() { service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr; unsigned ret = 0; while(e.next(ptr)) ret += ptr->get_num_items(); return ret; } const char * menu_helpers::guid_to_name_table::search(const GUID & p_guid) { if (!m_inited) { m_data.set_size(calc_total_action_count()); t_size dataptr = 0; pfc::string8_fastalloc nametemp; service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr; while(e.next(ptr)) { unsigned n, m = ptr->get_num_items(); for(n=0;n<m;n++) { assert(dataptr < m_data.get_size()); ptr->get_item_name(n,nametemp); m_data[dataptr].m_name = strdup(nametemp); m_data[dataptr].m_guid = ptr->get_item_guid(n); dataptr++; } } assert(dataptr == m_data.get_size()); pfc::sort_t(m_data,entry_compare,m_data.get_size()); m_inited = true; } t_size index; if (pfc::bsearch_t(m_data.get_size(),m_data,entry_compare_search,p_guid,index)) return m_data[index].m_name; else return 0; } int menu_helpers::guid_to_name_table::entry_compare_search(const entry & entry1,const GUID & entry2) { return pfc::guid_compare(entry1.m_guid,entry2); } int menu_helpers::guid_to_name_table::entry_compare(const entry & entry1,const entry & entry2) { return pfc::guid_compare(entry1.m_guid,entry2.m_guid); } menu_helpers::guid_to_name_table::guid_to_name_table() { m_inited = false; } menu_helpers::guid_to_name_table::~guid_to_name_table() { t_size n, m = m_data.get_size(); for(n=0;n<m;n++) free(m_data[n].m_name); } int menu_helpers::name_to_guid_table::entry_compare_search(const entry & entry1,const search_entry & entry2) { return stricmp_utf8_ex(entry1.m_name,infinite,entry2.m_name,entry2.m_name_len); } int menu_helpers::name_to_guid_table::entry_compare(const entry & entry1,const entry & entry2) { return stricmp_utf8(entry1.m_name,entry2.m_name); } bool menu_helpers::name_to_guid_table::search(const char * p_name,unsigned p_name_len,GUID & p_out) { if (!m_inited) { m_data.set_size(calc_total_action_count()); t_size dataptr = 0; pfc::string8_fastalloc nametemp; service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr; while(e.next(ptr)) { unsigned n, m = ptr->get_num_items(); for(n=0;n<m;n++) { assert(dataptr < m_data.get_size()); ptr->get_item_name(n,nametemp); m_data[dataptr].m_name = strdup(nametemp); m_data[dataptr].m_guid = ptr->get_item_guid(n); dataptr++; } } assert(dataptr == m_data.get_size()); pfc::sort_t(m_data,entry_compare,m_data.get_size()); m_inited = true; } t_size index; search_entry temp = {p_name,p_name_len}; if (pfc::bsearch_t(m_data.get_size(),m_data,entry_compare_search,temp,index)) { p_out = m_data[index].m_guid; return true; } else return false; } menu_helpers::name_to_guid_table::name_to_guid_table() { m_inited = false; } menu_helpers::name_to_guid_table::~name_to_guid_table() { t_size n, m = m_data.get_size(); for(n=0;n<m;n++) free(m_data[n].m_name); } bool menu_helpers::find_command_by_name(const char * p_name,service_ptr_t<contextmenu_item> & p_item,unsigned & p_index) { pfc::string8_fastalloc path,name; service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr; if (e.first(ptr)) do { // if (ptr->get_type()==type) { unsigned action,num_actions = ptr->get_num_items(); for(action=0;action<num_actions;action++) { ptr->get_item_default_path(action,path); ptr->get_item_name(action,name); if (!path.is_empty()) path += "/"; path += name; if (!stricmp_utf8(p_name,path)) { p_item = ptr; p_index = action; return true; } } } } while(e.next(ptr)); return false; } bool menu_helpers::find_command_by_name(const char * p_name,GUID & p_command) { service_ptr_t<contextmenu_item> item; unsigned index; bool ret = find_command_by_name(p_name,item,index); if (ret) p_command = item->get_item_guid(index); return ret; } bool standard_commands::run_main(const GUID & p_guid) { service_enum_t<mainmenu_commands> e; service_ptr_t<mainmenu_commands> ptr; while(e.next(ptr)) { const t_size count = ptr->get_command_count(); for(t_size n=0;n<count;n++) { if (ptr->get_command(n) == p_guid) { ptr->execute(n,service_ptr_t<service_base>()); return true; } } } return false; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/menu_helpers.h ================================================ namespace menu_helpers { #ifdef _WIN32 void win32_auto_mnemonics(HMENU menu); #endif bool run_command_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data); bool run_command_context_ex(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller); bool run_command_context_playlist(const GUID & p_command,const GUID & p_subcommand); bool run_command_context_now_playing(const GUID & p_command,const GUID & p_subcommand); bool test_command_context(const GUID & p_guid); bool is_command_checked_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data); bool is_command_checked_context_playlist(const GUID & p_command,const GUID & p_subcommand); bool find_command_by_name(const char * p_name,service_ptr_t<contextmenu_item> & p_item,unsigned & p_index); bool find_command_by_name(const char * p_name,GUID & p_command); bool context_get_description(const GUID& p_guid,pfc::string_base & out); bool guid_from_name(const char * p_name,unsigned p_name_len,GUID & p_out); bool name_from_guid(const GUID & p_guid,pfc::string_base & p_out); class guid_to_name_table { public: guid_to_name_table(); ~guid_to_name_table(); const char * search(const GUID & p_guid); private: struct entry { char* m_name; GUID m_guid; }; pfc::array_t<entry> m_data; bool m_inited; static int entry_compare_search(const entry & entry1,const GUID & entry2); static int entry_compare(const entry & entry1,const entry & entry2); }; class name_to_guid_table { public: name_to_guid_table(); ~name_to_guid_table(); bool search(const char * p_name,unsigned p_name_len,GUID & p_out); private: struct entry { char* m_name; GUID m_guid; }; pfc::array_t<entry> m_data; bool m_inited; struct search_entry { const char * m_name; unsigned m_name_len; }; static int entry_compare_search(const entry & entry1,const search_entry & entry2); static int entry_compare(const entry & entry1,const entry & entry2); }; }; class standard_commands { public: static const GUID guid_context_file_properties, guid_context_file_open_directory, guid_context_copy_names, guid_context_send_to_playlist, guid_context_reload_info, guid_context_reload_info_if_changed, guid_context_rewrite_info, guid_context_remove_tags, guid_context_remove_from_database, guid_context_convert_run, guid_context_convert_run_singlefile,guid_context_convert_run_withcue, guid_context_write_cd, guid_context_rg_scan_track, guid_context_rg_scan_album, guid_context_rg_scan_album_multi, guid_context_rg_remove, guid_context_save_playlist, guid_context_masstag_edit, guid_context_masstag_rename, guid_main_always_on_top, guid_main_preferences, guid_main_about, guid_main_exit, guid_main_restart, guid_main_activate, guid_main_hide, guid_main_activate_or_hide, guid_main_titleformat_help, guid_main_next, guid_main_previous, guid_main_next_or_random, guid_main_random, guid_main_pause, guid_main_play, guid_main_play_or_pause, guid_main_rg_set_album, guid_main_rg_set_track, guid_main_rg_disable, guid_main_stop, guid_main_stop_after_current, guid_main_volume_down, guid_main_volume_up, guid_main_volume_mute, guid_main_add_directory, guid_main_add_files, guid_main_add_location, guid_main_add_playlist, guid_main_clear_playlist, guid_main_create_playlist, guid_main_highlight_playing, guid_main_load_playlist, guid_main_next_playlist, guid_main_previous_playlist, guid_main_open, guid_main_remove_playlist, guid_main_remove_dead_entries, guid_main_remove_duplicates, guid_main_rename_playlist, guid_main_save_all_playlists, guid_main_save_playlist, guid_main_playlist_search, guid_main_playlist_sel_crop, guid_main_playlist_sel_remove, guid_main_playlist_sel_invert, guid_main_playlist_undo, guid_main_show_console, guid_main_play_cd, guid_main_restart_resetconfig, guid_main_record, guid_main_playlist_moveback, guid_main_playlist_moveforward, guid_main_playlist_redo, guid_main_playback_follows_cursor, guid_main_cursor_follows_playback, guid_main_saveconfig, guid_main_playlist_select_all, guid_main_show_now_playing, guid_seek_ahead_1s, guid_seek_ahead_5s, guid_seek_ahead_10s, guid_seek_ahead_30s, guid_seek_ahead_1min, guid_seek_ahead_2min, guid_seek_ahead_5min, guid_seek_ahead_10min, guid_seek_back_1s, guid_seek_back_5s, guid_seek_back_10s, guid_seek_back_30s, guid_seek_back_1min, guid_seek_back_2min, guid_seek_back_5min, guid_seek_back_10min ; static bool run_main(const GUID & guid); static inline bool run_context(const GUID & guid,const pfc::list_base_const_t<metadb_handle_ptr> &data) {return menu_helpers::run_command_context(guid,pfc::guid_null,data);} static inline bool run_context(const GUID & guid,const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller) {return menu_helpers::run_command_context_ex(guid,pfc::guid_null,data,caller);} static inline bool context_file_properties(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_file_properties,data,caller);} static inline bool context_file_open_directory(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_file_open_directory,data,caller);} static inline bool context_copy_names(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_copy_names,data,caller);} static inline bool context_send_to_playlist(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_send_to_playlist,data,caller);} static inline bool context_reload_info(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_reload_info,data,caller);} static inline bool context_reload_info_if_changed(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_reload_info_if_changed,data,caller);} static inline bool context_rewrite_info(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rewrite_info,data,caller);} static inline bool context_remove_tags(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_remove_tags,data,caller);} static inline bool context_remove_from_database(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_remove_from_database,data,caller);} static inline bool context_convert_run(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_convert_run,data,caller);} static inline bool context_convert_run_singlefile(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_convert_run_singlefile,data,caller);} static inline bool context_write_cd(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_write_cd,data,caller);} static inline bool context_rg_scan_track(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_track,data,caller);} static inline bool context_rg_scan_album(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_album,data,caller);} static inline bool context_rg_scan_album_multi(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_album_multi,data,caller);} static inline bool context_rg_remove(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_remove,data,caller);} static inline bool context_save_playlist(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_save_playlist,data,caller);} static inline bool context_masstag_edit(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_masstag_edit,data,caller);} static inline bool context_masstag_rename(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_masstag_rename,data,caller);} static inline bool main_always_on_top() {return run_main(guid_main_always_on_top);} static inline bool main_preferences() {return run_main(guid_main_preferences);} static inline bool main_about() {return run_main(guid_main_about);} static inline bool main_exit() {return run_main(guid_main_exit);} static inline bool main_restart() {return run_main(guid_main_restart);} static inline bool main_activate() {return run_main(guid_main_activate);} static inline bool main_hide() {return run_main(guid_main_hide);} static inline bool main_activate_or_hide() {return run_main(guid_main_activate_or_hide);} static inline bool main_titleformat_help() {return run_main(guid_main_titleformat_help);} static inline bool main_playback_follows_cursor() {return run_main(guid_main_playback_follows_cursor);} static inline bool main_next() {return run_main(guid_main_next);} static inline bool main_previous() {return run_main(guid_main_previous);} static inline bool main_next_or_random() {return run_main(guid_main_next_or_random);} static inline bool main_random() {return run_main(guid_main_random);} static inline bool main_pause() {return run_main(guid_main_pause);} static inline bool main_play() {return run_main(guid_main_play);} static inline bool main_play_or_pause() {return run_main(guid_main_play_or_pause);} static inline bool main_rg_set_album() {return run_main(guid_main_rg_set_album);} static inline bool main_rg_set_track() {return run_main(guid_main_rg_set_track);} static inline bool main_rg_disable() {return run_main(guid_main_rg_disable);} static inline bool main_stop() {return run_main(guid_main_stop);} static inline bool main_stop_after_current() {return run_main(guid_main_stop_after_current);} static inline bool main_volume_down() {return run_main(guid_main_volume_down);} static inline bool main_volume_up() {return run_main(guid_main_volume_up);} static inline bool main_volume_mute() {return run_main(guid_main_volume_mute);} static inline bool main_add_directory() {return run_main(guid_main_add_directory);} static inline bool main_add_files() {return run_main(guid_main_add_files);} static inline bool main_add_location() {return run_main(guid_main_add_location);} static inline bool main_add_playlist() {return run_main(guid_main_add_playlist);} static inline bool main_clear_playlist() {return run_main(guid_main_clear_playlist);} static inline bool main_create_playlist() {return run_main(guid_main_create_playlist);} static inline bool main_highlight_playing() {return run_main(guid_main_highlight_playing);} static inline bool main_load_playlist() {return run_main(guid_main_load_playlist);} static inline bool main_next_playlist() {return run_main(guid_main_next_playlist);} static inline bool main_previous_playlist() {return run_main(guid_main_previous_playlist);} static inline bool main_open() {return run_main(guid_main_open);} static inline bool main_remove_playlist() {return run_main(guid_main_remove_playlist);} static inline bool main_remove_dead_entries() {return run_main(guid_main_remove_dead_entries);} static inline bool main_remove_duplicates() {return run_main(guid_main_remove_duplicates);} static inline bool main_rename_playlist() {return run_main(guid_main_rename_playlist);} static inline bool main_save_all_playlists() {return run_main(guid_main_save_all_playlists);} static inline bool main_save_playlist() {return run_main(guid_main_save_playlist);} static inline bool main_playlist_search() {return run_main(guid_main_playlist_search);} static inline bool main_playlist_sel_crop() {return run_main(guid_main_playlist_sel_crop);} static inline bool main_playlist_sel_remove() {return run_main(guid_main_playlist_sel_remove);} static inline bool main_playlist_sel_invert() {return run_main(guid_main_playlist_sel_invert);} static inline bool main_playlist_undo() {return run_main(guid_main_playlist_undo);} static inline bool main_show_console() {return run_main(guid_main_show_console);} static inline bool main_play_cd() {return run_main(guid_main_play_cd);} static inline bool main_restart_resetconfig() {return run_main(guid_main_restart_resetconfig);} static inline bool main_playlist_moveback() {return run_main(guid_main_playlist_moveback);} static inline bool main_playlist_moveforward() {return run_main(guid_main_playlist_moveforward);} static inline bool main_saveconfig() {return run_main(guid_main_saveconfig);} }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/menu_item.cpp ================================================ #include "foobar2000.h" bool contextmenu_item::item_get_display_data_root(pfc::string_base & p_out,unsigned & p_displayflags,unsigned p_index,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) { bool status = false; pfc::ptrholder_t<contextmenu_item_node_root> root ( instantiate_item(p_index,p_data,p_caller) ); if (root.is_valid()) status = root->get_display_data(p_out,p_displayflags,p_data,p_caller); return status; } static contextmenu_item_node * g_find_node(const GUID & p_guid,contextmenu_item_node * p_parent) { if (p_parent->get_guid() == p_guid) return p_parent; else if (p_parent->get_type() == contextmenu_item_node::TYPE_POPUP) { t_size n, m = p_parent->get_children_count(); for(n=0;n<m;n++) { contextmenu_item_node * temp = g_find_node(p_guid,p_parent->get_child(n)); if (temp) return temp; } return 0; } else return 0; } bool contextmenu_item::item_get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,unsigned p_index,const GUID & p_node,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller) { bool status = false; pfc::ptrholder_t<contextmenu_item_node_root> root ( instantiate_item(p_index,p_data,p_caller) ); if (root.is_valid()) { contextmenu_item_node * node = g_find_node(p_node,root.get_ptr()); if (node) status = node->get_display_data(p_out,p_displayflags,p_data,p_caller); } return status; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/menu_manager.cpp ================================================ #include "foobar2000.h" #ifdef WIN32 static void fix_ampersand(const char * src,pfc::string_base & out) { unsigned ptr = 0; while(src[ptr]) { if (src[ptr]=='&') { out.add_string("&&"); ptr++; while(src[ptr]=='&') { out.add_string("&&"); ptr++; } } else out.add_byte(src[ptr++]); } } static unsigned flags_to_win32(unsigned flags) { unsigned ret = 0; if (flags & contextmenu_item_node::FLAG_CHECKED) ret |= MF_CHECKED; if (flags & contextmenu_item_node::FLAG_DISABLED) ret |= MF_DISABLED; if (flags & contextmenu_item_node::FLAG_GRAYED) ret |= MF_GRAYED; return ret; } void contextmenu_manager::win32_build_menu(HMENU menu,contextmenu_node * parent,int base_id,int max_id)//menu item identifiers are base_id<=N<base_id+max_id (if theres too many items, they will be clipped) { if (parent!=0 && parent->get_type()==contextmenu_item_node::TYPE_POPUP) { pfc::string8_fastalloc temp; t_size child_idx,child_num = parent->get_num_children(); for(child_idx=0;child_idx<child_num;child_idx++) { contextmenu_node * child = parent->get_child(child_idx); if (child) { const char * name = child->get_name(); if (strchr(name,'&')) {fix_ampersand(name,temp);name=temp;} contextmenu_item_node::t_type type = child->get_type(); if (type==contextmenu_item_node::TYPE_POPUP) { HMENU new_menu = CreatePopupMenu(); uAppendMenu(menu,MF_STRING|MF_POPUP | flags_to_win32(child->get_display_flags()),(UINT_PTR)new_menu,name); win32_build_menu(new_menu,child,base_id,max_id); } else if (type==contextmenu_item_node::TYPE_SEPARATOR) { uAppendMenu(menu,MF_SEPARATOR,0,0); } else if (type==contextmenu_item_node::TYPE_COMMAND) { int id = child->get_id(); if (id>=0 && (max_id<0 || id<max_id)) { uAppendMenu(menu,MF_STRING | flags_to_win32(child->get_display_flags()),base_id+id,name); } } } } } } #endif bool contextmenu_manager::execute_by_id(unsigned id) { bool rv = false; contextmenu_node * ptr = find_by_id(id); if (ptr) {rv=true;ptr->execute();} return rv; } #ifdef WIN32 void contextmenu_manager::win32_run_menu_popup(HWND parent,const POINT * pt) { enum {ID_CUSTOM_BASE = 1}; int cmd; { POINT p; if (pt) p = *pt; else GetCursorPos(&p); HMENU hmenu = CreatePopupMenu(); try { win32_build_menu(hmenu,ID_CUSTOM_BASE,-1); menu_helpers::win32_auto_mnemonics(hmenu); cmd = TrackPopupMenu(hmenu,TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,p.x,p.y,0,parent,0); } catch(...) {DestroyMenu(hmenu); throw;} DestroyMenu(hmenu); } if (cmd>0) { if (cmd>=ID_CUSTOM_BASE) { execute_by_id(cmd - ID_CUSTOM_BASE); } } } void contextmenu_manager::win32_run_menu_context(HWND parent,const pfc::list_base_const_t<metadb_handle_ptr> & data,const POINT * pt,unsigned flags) { service_ptr_t<contextmenu_manager> manager; contextmenu_manager::g_create(manager); manager->init_context(data,flags); manager->win32_run_menu_popup(parent,pt); } void contextmenu_manager::win32_run_menu_context_playlist(HWND parent,const POINT * pt,unsigned flags) { service_ptr_t<contextmenu_manager> manager; contextmenu_manager::g_create(manager); manager->init_context_playlist(flags); manager->win32_run_menu_popup(parent,pt); } namespace { class mnemonic_manager { pfc::string8_fastalloc used; bool is_used(unsigned c) { char temp[8]; temp[pfc::utf8_encode_char(char_lower(c),temp)]=0; return !!strstr(used,temp); } static bool is_alphanumeric(char c) { return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9'); } void insert(const char * src,unsigned idx,pfc::string_base & out) { out.reset(); out.add_string(src,idx); out.add_string("&"); out.add_string(src+idx); used.add_char(char_lower(src[idx])); } public: bool check_string(const char * src) {//check for existing mnemonics const char * ptr = src; while(ptr = strchr(ptr,'&')) { if (ptr[1]=='&') ptr+=2; else { unsigned c = 0; if (pfc::utf8_decode_char(ptr+1,&c)>0) { if (!is_used(c)) used.add_char(char_lower(c)); } return true; } } return false; } bool process_string(const char * src,pfc::string_base & out)//returns if changed { if (check_string(src)) {out=src;return false;} unsigned idx=0; while(src[idx]==' ') idx++; while(src[idx]) { if (is_alphanumeric(src[idx]) && !is_used(src[idx])) { insert(src,idx,out); return true; } while(src[idx] && src[idx]!=' ' && src[idx]!='\t') idx++; if (src[idx]=='\t') break; while(src[idx]==' ') idx++; } //no success picking first letter of one of words idx=0; while(src[idx]) { if (src[idx]=='\t') break; if (is_alphanumeric(src[idx]) && !is_used(src[idx])) { insert(src,idx,out); return true; } idx++; } //giving up out = src; return false; } }; } void menu_helpers::win32_auto_mnemonics(HMENU menu) { mnemonic_manager mgr; unsigned n, m = GetMenuItemCount(menu); pfc::string8_fastalloc temp,temp2; for(n=0;n<m;n++)//first pass, check existing mnemonics { unsigned type = uGetMenuItemType(menu,n); if (type==MFT_STRING) { uGetMenuString(menu,n,temp,MF_BYPOSITION); mgr.check_string(temp); } } for(n=0;n<m;n++) { HMENU submenu = GetSubMenu(menu,n); if (submenu) win32_auto_mnemonics(submenu); { unsigned type = uGetMenuItemType(menu,n); if (type==MFT_STRING) { unsigned state = submenu ? 0 : GetMenuState(menu,n,MF_BYPOSITION); unsigned id = GetMenuItemID(menu,n); uGetMenuString(menu,n,temp,MF_BYPOSITION); if (mgr.process_string(temp,temp2)) { uModifyMenu(menu,n,MF_BYPOSITION|MF_STRING|state,id,temp2); } } } } } #endif static bool test_key(unsigned k) { return (GetKeyState(k) & 0x8000) ? true : false; } #define F_SHIFT (HOTKEYF_SHIFT<<8) #define F_CTRL (HOTKEYF_CONTROL<<8) #define F_ALT (HOTKEYF_ALT<<8) #define F_WIN (HOTKEYF_EXT<<8) static unsigned get_key_code(WPARAM wp) { unsigned code = (unsigned)(wp & 0xFF); if (test_key(VK_CONTROL)) code|=F_CTRL; if (test_key(VK_SHIFT)) code|=F_SHIFT; if (test_key(VK_MENU)) code|=F_ALT; if (test_key(VK_LWIN) || test_key(VK_RWIN)) code|=F_WIN; return code; } bool keyboard_shortcut_manager::on_keydown(shortcut_type type,WPARAM wp) { if (type==TYPE_CONTEXT) return false; metadb_handle_list dummy; return process_keydown(type,dummy,get_key_code(wp)); } bool keyboard_shortcut_manager::on_keydown_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller) { if (data.get_count()==0) return false; return process_keydown_ex(TYPE_CONTEXT,data,get_key_code(wp),caller); } bool keyboard_shortcut_manager::on_keydown_auto(WPARAM wp) { if (on_keydown(TYPE_MAIN,wp)) return true; if (on_keydown(TYPE_CONTEXT_PLAYLIST,wp)) return true; if (on_keydown(TYPE_CONTEXT_NOW_PLAYING,wp)) return true; return false; } bool keyboard_shortcut_manager::on_keydown_auto_playlist(WPARAM wp) { metadb_handle_list data; static_api_ptr_t<playlist_manager> api; api->activeplaylist_get_selected_items(data); return on_keydown_auto_context(data,wp,contextmenu_item::caller_playlist); } bool keyboard_shortcut_manager::on_keydown_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller) { if (on_keydown_context(data,wp,caller)) return true; else return on_keydown_auto(wp); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/message_loop.h ================================================ class NOVTABLE message_filter { public: virtual bool pretranslate_message(MSG * p_msg) = 0; }; class NOVTABLE idle_handler { public: virtual bool on_idle() = 0; }; class NOVTABLE message_loop : public service_base { public: virtual void add_message_filter(message_filter * ptr) = 0; virtual void remove_message_filter(message_filter * ptr) = 0; virtual void add_idle_handler(idle_handler * ptr) = 0; virtual void remove_idle_handler(idle_handler * ptr) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(message_loop); }; class message_filter_impl_base : public message_filter { public: message_filter_impl_base() {static_api_ptr_t<message_loop>()->add_message_filter(this);} ~message_filter_impl_base() {static_api_ptr_t<message_loop>()->remove_message_filter(this);} bool pretranslate_message(MSG * p_msg) {return false;} PFC_CLASS_NOT_COPYABLE(message_filter_impl_base,message_filter_impl_base); }; class message_filter_impl_accel : public message_filter_impl_base { protected: bool pretranslate_message(MSG * p_msg) { if (m_wnd != NULL) { if (GetActiveWindow() == m_wnd) { if (TranslateAccelerator(m_wnd,m_accel.get(),p_msg) != 0) { return true; } } } return false; } public: message_filter_impl_accel(HINSTANCE p_instance,const TCHAR * p_accel) : m_wnd(NULL) { m_accel.load(p_instance,p_accel); } void set_wnd(HWND p_wnd) {m_wnd = p_wnd;} private: HWND m_wnd; win32_accelerator m_accel; PFC_CLASS_NOT_COPYABLE(message_filter_impl_accel,message_filter_impl_accel); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/metadb.cpp ================================================ #include "foobar2000.h" void metadb::handle_create_replace_path_canonical(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path) { handle_create(p_out,make_playable_location(p_new_path,p_source->get_subsong_index())); } void metadb::handle_create_replace_path(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path) { pfc::string8 path; filesystem::g_get_canonical_path(p_new_path,path); handle_create_replace_path_canonical(p_out,p_source,path); } void metadb::handle_replace_path_canonical(metadb_handle_ptr & p_out,const char * p_new_path) { metadb_handle_ptr temp; handle_create_replace_path_canonical(temp,p_out,p_new_path); p_out = temp; } metadb_io::t_load_info_state metadb_io::load_info(metadb_handle_ptr p_item,t_load_info_type p_type,HWND p_parent_window,bool p_show_errors) { return load_info_multi(pfc::list_single_ref_t<metadb_handle_ptr>(p_item),p_type,p_parent_window,p_show_errors); } metadb_io::t_update_info_state metadb_io::update_info(metadb_handle_ptr p_item,file_info & p_info,HWND p_parent_window,bool p_show_errors) { file_info * blah = &p_info; return update_info_multi(pfc::list_single_ref_t<metadb_handle_ptr>(p_item),pfc::list_single_ref_t<file_info*>(blah),p_parent_window,p_show_errors); } file_info_update_helper::file_info_update_helper(metadb_handle_ptr p_item) { const t_size count = 1; m_data.add_item(p_item); m_infos.set_size(count); m_mask.set_size(count); for(t_size n=0;n<count;n++) m_mask[n] = false; } file_info_update_helper::file_info_update_helper(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) { const t_size count = p_data.get_count(); m_data.add_items(p_data); m_infos.set_size(count); m_mask.set_size(count); for(t_size n=0;n<count;n++) m_mask[n] = false; } bool file_info_update_helper::read_infos(HWND p_parent,bool p_show_errors) { static_api_ptr_t<metadb_io> api; if (api->load_info_multi(m_data,metadb_io::load_info_default,p_parent,p_show_errors) != metadb_io::load_info_success) return false; t_size n; const t_size m = m_data.get_count(); t_size loaded_count = 0; for(n=0;n<m;n++) { bool val = m_data[n]->get_info(m_infos[n]); if (val) loaded_count++; m_mask[n] = val; } return loaded_count == m; } file_info_update_helper::t_write_result file_info_update_helper::write_infos(HWND p_parent,bool p_show_errors) { t_size n, outptr = 0; const t_size count = m_data.get_count(); pfc::array_t<metadb_handle_ptr> items_to_update; pfc::array_t<file_info *> infos_to_write; items_to_update.set_size(count); infos_to_write.set_size(count); for(n=0;n<count;n++) { if (m_mask[n]) { items_to_update[outptr] = m_data[n]; infos_to_write[outptr] = &m_infos[n]; outptr++; } } if (outptr == 0) return write_ok; else { static_api_ptr_t<metadb_io> api; switch(api->update_info_multi( pfc::list_const_array_t<metadb_handle_ptr,const pfc::array_t<metadb_handle_ptr>&>(items_to_update,outptr), pfc::list_const_array_t<file_info*,const pfc::array_t<file_info*>&>(infos_to_write,outptr), p_parent, true )) { case metadb_io::update_info_success: return write_ok; case metadb_io::update_info_aborted: return write_aborted; default: case metadb_io::update_info_errors: return write_error; } } } t_size file_info_update_helper::get_item_count() const { return m_data.get_count(); } bool file_info_update_helper::is_item_valid(t_size p_index) const { return m_mask[p_index]; } file_info & file_info_update_helper::get_item(t_size p_index) { return m_infos[p_index]; } metadb_handle_ptr file_info_update_helper::get_item_handle(t_size p_index) const { return m_data[p_index]; } void file_info_update_helper::invalidate_item(t_size p_index) { m_mask[p_index] = false; } void metadb_io::hint_async(metadb_handle_ptr p_item,const file_info & p_info,const t_filestats & p_stats,bool p_fresh) { const file_info * blargh = &p_info; hint_multi_async(pfc::list_single_ref_t<metadb_handle_ptr>(p_item),pfc::list_single_ref_t<const file_info *>(blargh),pfc::list_single_ref_t<t_filestats>(p_stats),bit_array_val(p_fresh)); } bool metadb::g_get_random_handle(metadb_handle_ptr & p_out) { if (static_api_ptr_t<playback_control>()->get_now_playing(p_out)) return true; { static_api_ptr_t<playlist_manager> api; t_size playlist_count = api->get_playlist_count(); t_size active_playlist = api->get_active_playlist(); if (active_playlist != infinite) { if (api->playlist_get_focus_item_handle(p_out,active_playlist)) return true; } for(t_size n = 0; n < playlist_count; n++) { if (api->playlist_get_focus_item_handle(p_out,n)) return true; } if (active_playlist != infinite) { t_size item_count = api->playlist_get_item_count(active_playlist); if (item_count > 0) { if (api->playlist_get_item_handle(p_out,active_playlist,0)) return true; } } for(t_size n = 0; n < playlist_count; n++) { t_size item_count = api->playlist_get_item_count(n); if (item_count > 0) { if (api->playlist_get_item_handle(p_out,n,0)) return true; } } } return false; } void metadb_io_v2::update_info_async_simple(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const pfc::list_base_const_t<const file_info*> & p_new_info, HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) { update_info_async(p_list,new service_impl_t<file_info_filter_impl>(p_list,p_new_info),p_parent_window,p_op_flags,p_notify); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/metadb.h ================================================ #ifndef _METADB_H_ #define _METADB_H_ //! API for tag read/write operations. Legal to call from main thread only, except for hint_multi_async() / hint_async().\n //! Implemented only by core, do not reimplement.\n //! Use static_api_ptr_t template to access metadb_io methods.\n //! WARNING: Methods that perform file access (tag reads/writes) run a modal message loop. They SHOULD NOT be called from global callbacks and such. class NOVTABLE metadb_io : public service_base { public: enum t_load_info_type { load_info_default, load_info_force, load_info_check_if_changed }; enum t_update_info_state { update_info_success, update_info_aborted, update_info_errors, }; enum t_load_info_state { load_info_success, load_info_aborted, load_info_errors, }; //! Returns whether some tag I/O operation is currently running. Another one can't be started. __declspec(deprecated) virtual bool is_busy() = 0; //! Returns whether - in result of user settings - all update operations will fail. __declspec(deprecated) virtual bool is_updating_disabled() = 0; //! Returns whether - in result of user settings - all update operations will silently succeed but without actually modifying files. __declspec(deprecated) virtual bool is_file_updating_blocked() = 0; //! If another tag I/O operation is running, this call will give focus to its progress window. __declspec(deprecated) virtual void highlight_running_process() = 0; //! Loads tags from multiple items. __declspec(deprecated) virtual t_load_info_state load_info_multi(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,t_load_info_type p_type,HWND p_parent_window,bool p_show_errors) = 0; //! Updates tags on multiple items. __declspec(deprecated) virtual t_update_info_state update_info_multi(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const pfc::list_base_const_t<file_info*> & p_new_info,HWND p_parent_window,bool p_show_errors) = 0; //! Rewrites tags on multiple items. __declspec(deprecated) virtual t_update_info_state rewrite_info_multi(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,HWND p_parent_window,bool p_show_errors) = 0; //! Removes tags from multiple items. __declspec(deprecated) virtual t_update_info_state remove_info_multi(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,HWND p_parent_window,bool p_show_errors) = 0; virtual void hint_multi(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const pfc::list_base_const_t<const file_info*> & p_infos,const pfc::list_base_const_t<t_filestats> & p_stats,const bit_array & p_fresh_mask) = 0; virtual void hint_multi_async(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const pfc::list_base_const_t<const file_info*> & p_infos,const pfc::list_base_const_t<t_filestats> & p_stats,const bit_array & p_fresh_mask) = 0; virtual void hint_reader(service_ptr_t<class input_info_reader> p_reader,const char * p_path,abort_callback & p_abort) = 0; //! For internal use only. virtual void path_to_handles_simple(const char * p_path,pfc::list_base_t<metadb_handle_ptr> & p_out) = 0; //! Dispatches metadb_io_callback calls with specified items. To be used with metadb_display_hook when your component needs specified items refreshed. virtual void dispatch_refresh(const pfc::list_base_const_t<metadb_handle_ptr> & p_list) = 0; void hint_async(metadb_handle_ptr p_item,const file_info & p_info,const t_filestats & p_stats,bool p_fresh); __declspec(deprecated) t_load_info_state load_info(metadb_handle_ptr p_item,t_load_info_type p_type,HWND p_parent_window,bool p_show_errors); __declspec(deprecated) t_update_info_state update_info(metadb_handle_ptr p_item,file_info & p_info,HWND p_parent_window,bool p_show_errors); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb_io); }; //! Implementing this class gives you direct control over which part of file_info gets altered during a tag update uperation. To be used with metadb_io_v2::update_info_async(). class NOVTABLE file_info_filter : public service_base { public: //! Alters specified file_info entry; called as a part of tag update process. Specified file_info has been read from a file, and will be written back.\n //! WARNING: This will be typically called from another thread than main app thread (precisely, from thread created by tag updater). You should copy all relevant data to members of your file_info_filter instance in constructor and reference only member data in apply_filter() implementation. //! @returns True when you have altered file_info and changes need to be written back to the file; false if no changes have been made. virtual bool apply_filter(metadb_handle_ptr p_location,t_filestats p_stats,file_info & p_info) = 0; FB2K_MAKE_SERVICE_INTERFACE(file_info_filter,service_base); }; //! Advanced interface for passing infos read from files to metadb backend. Use metadb_io_v2::create_hint_list() to instantiate. class NOVTABLE metadb_hint_list : public service_base { public: //! Adds a hint to the list. //! @param p_location Location of the item the hint applies to. //! @param p_info file_info object describing the item. //! @param p_stats Information about the file containing item the hint applies to. //! @param p_freshflag Set to true if the info has been directly read from the file, false if it comes from another source such as a playlist file. virtual void add_hint(metadb_handle_ptr const & p_location,const file_info & p_info,const t_filestats & p_stats,bool p_freshflag) = 0; //! Reads info from specified info reader instance and adds hints. May throw an exception in case info read has failed. virtual void add_hint_reader(const char * p_path,service_ptr_t<input_info_reader> const & p_reader,abort_callback & p_abort) = 0; //! Call this when you're done working with this metadb_hint_list instance, to apply hints and dispatch callbacks. If you don't call this, all added hints will be ignored. virtual void on_done() = 0; FB2K_MAKE_SERVICE_INTERFACE(metadb_hint_list,service_base); }; //! New in 0.9.3. Extends metadb_io functionality with nonblocking versions of tag read/write functions, and some other utility features. class NOVTABLE metadb_io_v2 : public metadb_io { public: enum { //! By default, when some part of requested operation could not be performed for reasons other than user abort, a popup dialog with description of the problem is spawned.\n //! Set this flag to disable error notification. op_flag_no_errors = 1 << 0, //! Set this flag to make the progress dialog not steal focus on creation. op_flag_background = 1 << 1, //! Set this flag to delay the progress dialog becoming visible, so it does not appear at all during short operations. Also implies op_flag_background effect. op_flag_delay_ui = 1 << 2, }; //! @param p_list List of items to process. //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. //! @param p_notify Called when the task is completed. Status code is one of t_load_info_state values. Can be null if caller doesn't care. virtual void load_info_async(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,t_load_info_type p_type,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0; //! @param p_list List of items to process. //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. //! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care. //! @param p_filter Callback handling actual file_info alterations. Typically used to replace entire meta part of file_info, or to alter something else such as ReplayGain while leaving meta intact. virtual void update_info_async(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,service_ptr_t<file_info_filter> p_filter,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0; //! @param p_list List of items to process. //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. //! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care. virtual void rewrite_info_async(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0; //! @param p_list List of items to process. //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. //! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care. virtual void remove_info_async(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0; //! Creates a metadb_hint_list object. virtual service_ptr_t<metadb_hint_list> create_hint_list() = 0; //! @param p_list List of items to process. //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. //! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care. //! @param p_new_info New infos to write to specified items. void update_info_async_simple(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const pfc::list_base_const_t<const file_info*> & p_new_info, HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify); FB2K_MAKE_SERVICE_INTERFACE(metadb_io_v2,metadb_io); }; //! Callback service receiving notifications about metadb contents changes. class NOVTABLE metadb_io_callback : public service_base { public: //! Called when metadb contents change. (Or, one of display hook component requests display update). //! @param p_items_sorted List of items that have been updated. The list is always sorted by pointer value, to allow fast bsearch to test whether specific item has changed. //! @param p_fromhook Set to true when actual contents haven't changed but one of display hooks requested an update. virtual void on_changed_sorted(const pfc::list_base_const_t<metadb_handle_ptr> & p_items_sorted, bool p_fromhook) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb_io_callback); }; //! Entrypoint service for metadb_handle related operations.\n //! Implemented only by core, do not reimplement.\n //! Use static_api_ptr_t template to access it, e.g. static_api_ptr_t<metadb>()->handle_create(myhandle,mylocation); class NOVTABLE metadb : public service_base { public: //! Locks metadb to prevent other threads from modifying it while you're working with some of its contents. Some functions (metadb_handle::get_info_locked(), metadb_handle::get_info_async_locked()) can be called only from inside metadb lock section. virtual void database_lock()=0; //! Unlocks metadb after database_lock(). Some functions (metadb_handle::get_info_locked(), metadb_handle::get_info_async_locked()) can be called only from inside metadb lock section. virtual void database_unlock()=0; //! Returns metadb_handle object referencing specified location (attempts to find existing one, creates new one if doesn't exist). //! @param p_out Receives metadb_handle pointer. //! @param p_location Location to create metadb_handle for. virtual void handle_create(metadb_handle_ptr & p_out,const playable_location & p_location)=0; void handle_create_replace_path_canonical(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path); void handle_replace_path_canonical(metadb_handle_ptr & p_out,const char * p_new_path); void handle_create_replace_path(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path); //! Helper function; attempts to retrieve a handle to any known playable location to be used for e.g. titleformatting script preview.\n //! @returns True on success; false on failure (no known playable locations). static bool g_get_random_handle(metadb_handle_ptr & p_out); enum {case_sensitive = true}; typedef pfc::comparator_strcmp path_comparator; inline static int path_compare_ex(const char * p1,unsigned len1,const char * p2,unsigned len2) {return case_sensitive ? pfc::strcmp_ex(p1,len1,p2,len2) : stricmp_utf8_ex(p1,len1,p2,len2);} inline static int path_compare(const char * p1,const char * p2) {return case_sensitive ? strcmp(p1,p2) : stricmp_utf8(p1,p2);} inline static int path_compare_metadb_handle(const metadb_handle_ptr & p1,const metadb_handle_ptr & p2) {return path_compare(p1->get_path(),p2->get_path());} FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb); }; class in_metadb_sync { public: in_metadb_sync() { m_api->database_lock(); } ~in_metadb_sync() { m_api->database_unlock(); } private: static_api_ptr_t<metadb> m_api; }; class in_metadb_sync_fromptr { public: in_metadb_sync_fromptr(const service_ptr_t<metadb> & p_api) : m_api(p_api) {m_api->database_lock();} ~in_metadb_sync_fromptr() {m_api->database_unlock();} private: service_ptr_t<metadb> m_api; }; class in_metadb_sync_fromhandle { public: in_metadb_sync_fromhandle(const service_ptr_t<metadb_handle> & p_api) : m_api(p_api) {m_api->metadb_lock();} ~in_metadb_sync_fromhandle() {m_api->metadb_unlock();} private: service_ptr_t<metadb_handle> m_api; }; //! Deprecated - use metadb_io_v2::update_info_async w/ file_info_filter whenever possible. class __declspec(deprecated("Use metadb_io_v2::update_info_async instead whenever possible.")) file_info_update_helper { public: file_info_update_helper(const pfc::list_base_const_t<metadb_handle_ptr> & p_data); file_info_update_helper(metadb_handle_ptr p_item); bool read_infos(HWND p_parent,bool p_show_errors); enum t_write_result { write_ok, write_aborted, write_error }; t_write_result write_infos(HWND p_parent,bool p_show_errors); t_size get_item_count() const; bool is_item_valid(t_size p_index) const;//returns false where info reading failed file_info & get_item(t_size p_index); metadb_handle_ptr get_item_handle(t_size p_index) const; void invalidate_item(t_size p_index); private: metadb_handle_list m_data; pfc::array_t<file_info_impl> m_infos; pfc::array_t<bool> m_mask; }; class titleformat_text_out; class titleformat_hook_function_params; /* Implementing this service installs a global hook for metadb_handle::format_title field processing. \n This should be implemented only where absolutely necessary, for safety and performance reasons. \n metadb_display_hook methods should NEVER make any other API calls (other than possibly querying information from passed metadb_handle pointer), only read implementation-specific private data and return as soon as possible. Since those are called from metadb_handle::format_title, no assumptions should be made about calling context (threading etc). \n Both methods are called from inside metadb lock, so no additional locking is required to use *_locked metadb_handle methods. See titleformat_hook for more info about methods/parameters. \n If there are multiple metadb_display_hook implementations registered, call order is undefined. */ class metadb_display_hook : public service_base { public: virtual bool process_field(metadb_handle * p_handle,titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) = 0; virtual bool process_function(metadb_handle * p_handle,titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb_display_hook); }; //! Helper implementation of file_info_filter_impl. class file_info_filter_impl : public file_info_filter { public: file_info_filter_impl(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const pfc::list_base_const_t<const file_info*> & p_new_info) { pfc::dynamic_assert(p_list.get_count() == p_new_info.get_count()); pfc::array_t<t_size> order; order.set_size(p_list.get_count()); order_helper::g_fill(order.get_ptr(),order.get_size()); p_list.sort_get_permutation_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>,order.get_ptr()); m_handles.set_count(order.get_size()); m_infos.set_size(order.get_size()); for(t_size n = 0; n < order.get_size(); n++) { m_handles[n] = p_list[order[n]]; m_infos[n] = *p_new_info[order[n]]; } } bool apply_filter(metadb_handle_ptr p_location,t_filestats p_stats,file_info & p_info) { t_size index; if (m_handles.bsearch_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>,p_location,index)) { p_info = m_infos[index]; return true; } else { return false; } } private: metadb_handle_list m_handles; pfc::array_t<file_info_impl> m_infos; }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/metadb_handle.cpp ================================================ #include "foobar2000.h" double metadb_handle::get_length() { double rv = 0; in_metadb_sync_fromhandle l_sync(this); const file_info * info; if (get_info_locked(info)) rv = info->get_length(); return rv; } t_filetimestamp metadb_handle::get_filetimestamp() { return get_filestats().m_timestamp; } t_filesize metadb_handle::get_filesize() { return get_filestats().m_size; } bool metadb_handle::format_title_legacy(titleformat_hook * p_hook,pfc::string_base & p_out,const char * p_spec,titleformat_text_filter * p_filter) { service_ptr_t<titleformat_object> script; if (static_api_ptr_t<titleformat_compiler>()->compile(script,p_spec)) { return format_title(p_hook,p_out,script,p_filter); } else { p_out.reset(); return false; } } bool metadb_handle::g_should_reload(const t_filestats & p_old_stats,const t_filestats & p_new_stats,bool p_fresh) { if (p_new_stats.m_timestamp == filetimestamp_invalid) return p_fresh; else if (p_fresh) return p_old_stats!= p_new_stats; else return p_old_stats.m_timestamp < p_new_stats.m_timestamp; } bool metadb_handle::should_reload(const t_filestats & p_new_stats, bool p_fresh) const { if (!is_info_loaded_async()) return true; else return g_should_reload(get_filestats(),p_new_stats,p_fresh); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/metadb_handle.h ================================================ #ifndef _FOOBAR2000_METADB_HANDLE_H_ #define _FOOBAR2000_METADB_HANDLE_H_ class titleformat_hook; class titleformat_text_filter; //! metadb_handle object represents interface to reference-counted file_info cache entry for specified location.\n //! To obtain a metadb_handle to specific location, use metadb::handle_create(). To obtain a list of metadb_handle objects corresponding to specific path (directory, playlist, multitrack file, etc), use relevant playlist_loader static helper methods.\n //! metadb_handle is also the most efficient way of passing playable object locations around because it provides fast access to both location and infos, and is reference counted so duplicating it is as fast as possible.\n //! To retrieve a path of a file from a metadb_handle, use metadb_handle::get_path() function. Note that metadb_handle is NOT just file path, some formats support multiple subsongs per physical file, which are signaled using subsong indexes.\n class NOVTABLE metadb_handle : public service_base { public: //! Retrieves location represented by this metadb_handle object. Returned reference is valid until calling context releases metadb_handle that returned it (metadb_handle_ptr is deallocated etc). virtual const playable_location & get_location() const = 0;//never fails, returned pointer valid till the object is released //! Renders information about item referenced by this metadb_handle object. //! @param p_hook Optional callback object overriding fields and functions; set to NULL if not used. //! @param p_out String receiving the output on success. //! @param p_script Titleformat script to use. Use titleformat_compiler service to create one. //! @param p_filter Optional callback object allowing input to be filtered according to context (i.e. removal of linebreak characters present in tags when rendering playlist lines). Set to NULL when not used. //! @returns true on success, false when dummy file_info instance was used because actual info is was not (yet) known. virtual bool format_title(titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<class titleformat_object> & p_script,titleformat_text_filter * p_filter) = 0; //! Locks metadb to prevent other threads from modifying it while you're working with some of its contents. Some functions (metadb_handle::get_info_locked(), metadb_handle::get_info_async_locked()) can be called only from inside metadb lock section. //! Same as metadb::database_lock(). virtual void metadb_lock() = 0; //! Unlocks metadb after metadb_lock(). Some functions (metadb_handle::get_info_locked(), metadb_handle::get_info_async_locked()) can be called only from inside metadb lock section. //! Same as metadb::database_unlock(). virtual void metadb_unlock() = 0; //! Returns last seen file stats, filestats_invalid if unknown. virtual t_filestats get_filestats() const = 0; //! Queries whether cached info about item referenced by this metadb_handle object is already available.\n //! Note that state of cached info changes only inside main thread, so you can safely assume that it doesn't change while some block of your code inside main thread is being executed. virtual bool is_info_loaded() const = 0; //! Queries cached info about item referenced by this metadb_handle object. Returns true on success, false when info is not yet known.\n //! Note that state of cached info changes only inside main thread, so you can safely assume that it doesn't change while some block of your code inside main thread is being executed. virtual bool get_info(file_info & p_info) const = 0; //! Queries cached info about item referenced by this metadb_handle object. Returns true on success, false when info is not yet known. This is more efficient than get_info() since no data is copied.\n //! You must lock the metadb before calling this function, and unlock it after you are done working with the returned pointer, to ensure multithread safety.\n //! Note that state of cached info changes only inside main thread, so you can safely assume that it doesn't change while some block of your code inside main thread is being executed. //! @param p_info On success, receives a pointer to metadb's file_info object. The pointer is for temporary use only, and becomes invalid when metadb is unlocked. virtual bool get_info_locked(const file_info * & p_info) const = 0; //! Queries whether cached info about item referenced by this metadb_handle object is already available.\n //! This is intended for use in special cases when you need to immediately retrieve info sent by metadb_io hint from another thread; state of returned data can be altered by any thread, as opposed to non-async methods. virtual bool is_info_loaded_async() const = 0; //! Queries cached info about item referenced by this metadb_handle object. Returns true on success, false when info is not yet known.\n //! This is intended for use in special cases when you need to immediately retrieve info sent by metadb_io hint from another thread; state of returned data can be altered by any thread, as opposed to non-async methods. virtual bool get_info_async(file_info & p_info) const = 0; //! Queries cached info about item referenced by this metadb_handle object. Returns true on success, false when info is not yet known. This is more efficient than get_info() since no data is copied.\n //! You must lock the metadb before calling this function, and unlock it after you are done working with the returned pointer, to ensure multithread safety.\n //! This is intended for use in special cases when you need to immediately retrieve info sent by metadb_io hint from another thread; state of returned data can be altered by any thread, as opposed to non-async methods. //! @param p_info On success, receives a pointer to metadb's file_info object. The pointer is for temporary use only, and becomes invalid when metadb is unlocked. virtual bool get_info_async_locked(const file_info * & p_info) const = 0; //! Renders information about item referenced by this metadb_handle object, using external file_info data. virtual void format_title_from_external_info(const file_info & p_info,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<class titleformat_object> & p_script,titleformat_text_filter * p_filter)=0; static bool g_should_reload(const t_filestats & p_old_stats,const t_filestats & p_new_stats,bool p_fresh); bool should_reload(const t_filestats & p_new_stats,bool p_fresh) const; //! Helper provided for backwards compatibility; takes formatting script as text string and calls relevant titleformat_compiler methods; returns false when the script could not be compiled.\n //! See format_title() for descriptions of parameters.\n //! Bottleneck warning: you should consider using precompiled titleformat script object and calling regular format_title() instead when processing large numbers of items. bool format_title_legacy(titleformat_hook * p_hook,pfc::string_base & out,const char * p_spec,titleformat_text_filter * p_filter); //! Retrieves path of item described by this metadb_handle instance. Returned string is valid until calling context releases metadb_handle that returned it (metadb_handle_ptr is deallocated etc). inline const char * get_path() const {return get_location().get_path();} //! Retrieves subsong index of item described by this metadb_handle instance (used for multiple playable tracks within single physical file). inline t_uint32 get_subsong_index() const {return get_location().get_subsong_index();} double get_length();//helper t_filetimestamp get_filetimestamp(); t_filesize get_filesize(); FB2K_MAKE_SERVICE_INTERFACE(metadb_handle,service_base); }; typedef service_ptr_t<metadb_handle> metadb_handle_ptr; namespace metadb_handle_list_helper { void sort_by_format_partial(pfc::list_base_t<metadb_handle_ptr> & p_list,t_size base,t_size count,const char * spec,titleformat_hook * p_hook); void sort_by_format_get_order_partial(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,t_size base,t_size count,t_size* order,const char * spec,titleformat_hook * p_hook); void sort_by_format_partial(pfc::list_base_t<metadb_handle_ptr> & p_list,t_size base,t_size count,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook); void sort_by_format_get_order_partial(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,t_size base,t_size count,t_size* order,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook); void sort_by_relative_path_partial(pfc::list_base_t<metadb_handle_ptr> & p_list,t_size base,t_size count); void sort_by_relative_path_get_order_partial(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,t_size base,t_size count,t_size* order); void remove_duplicates(pfc::list_base_t<metadb_handle_ptr> & p_list); void sort_by_pointer_remove_duplicates(pfc::list_base_t<metadb_handle_ptr> & p_list); void sort_by_path_quick(pfc::list_base_t<metadb_handle_ptr> & p_list); void sort_by_pointer(pfc::list_base_t<metadb_handle_ptr> & p_list); t_size bsearch_by_pointer(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const metadb_handle_ptr & val); double calc_total_duration(const pfc::list_base_const_t<metadb_handle_ptr> & p_list); void sort_by_path(pfc::list_base_t<metadb_handle_ptr> & p_list); }; template<template<typename> class t_alloc = pfc::alloc_fast > class metadb_handle_list_t : public service_list_t<metadb_handle,t_alloc> { private: typedef metadb_handle_list_t<t_alloc> t_self; typedef list_base_const_t<metadb_handle_ptr> t_interface; public: inline void sort_by_format(const char * spec,titleformat_hook * p_hook) {return sort_by_format_partial(0,get_count(),spec,p_hook);} inline void sort_by_format_partial(t_size base,t_size count,const char * spec,titleformat_hook * p_hook) {metadb_handle_list_helper::sort_by_format_partial(*this,base,count,spec,p_hook);} inline void sort_by_format_get_order(t_size* order,const char * spec,titleformat_hook * p_hook) const {sort_by_format_get_order_partial(0,get_count(),order,spec,p_hook);} inline void sort_by_format_get_order_partial(t_size base,t_size count,t_size* order,const char * spec,titleformat_hook * p_hook) const {metadb_handle_list_helper::sort_by_format_get_order_partial(*this,base,count,order,spec,p_hook);} inline void sort_by_format(const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook) {return sort_by_format_partial(0,get_count(),p_script,p_hook);} inline void sort_by_format_partial(t_size base,t_size count,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook) {metadb_handle_list_helper::sort_by_format_partial(*this,base,count,p_script,p_hook);} inline void sort_by_format_get_order(t_size* order,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook) const {sort_by_format_get_order_partial(0,get_count(),order,p_script,p_hook);} inline void sort_by_format_get_order_partial(t_size base,t_size count,t_size* order,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook) const {metadb_handle_list_helper::sort_by_format_get_order_partial(*this,base,count,order,p_script,p_hook);} inline void sort_by_relative_path() {sort_by_relative_path_partial(0,get_count());} inline void sort_by_relative_path_partial(t_size base,t_size count) {metadb_handle_list_helper::sort_by_relative_path_partial(*this,base,count);} inline void sort_by_relative_path_get_order(t_size* order) const {sort_by_relative_path_get_order_partial(0,get_count(),order);} inline void sort_by_relative_path_get_order_partial(t_size base,t_size count,t_size* order) const {metadb_handle_list_helper::sort_by_relative_path_get_order_partial(*this,base,count,order);} inline void remove_duplicates() {metadb_handle_list_helper::remove_duplicates(*this);} inline void sort_by_pointer_remove_duplicates() {metadb_handle_list_helper::sort_by_pointer_remove_duplicates(*this);} inline void sort_by_path_quick() {metadb_handle_list_helper::sort_by_path_quick(*this);} inline void sort_by_pointer() {metadb_handle_list_helper::sort_by_pointer(*this);} inline t_size bsearch_by_pointer(const metadb_handle_ptr & val) const {return metadb_handle_list_helper::bsearch_by_pointer(*this,val);} inline double calc_total_duration() const {return metadb_handle_list_helper::calc_total_duration(*this);} inline void sort_by_path() {metadb_handle_list_helper::sort_by_path(*this);} const t_self & operator=(const t_self & p_source) {remove_all(); add_items(p_source);return *this;} const t_self & operator=(const t_interface & p_source) {remove_all(); add_items(p_source);return *this;} metadb_handle_list_t(const t_self & p_source) {add_items(p_source);} metadb_handle_list_t(const t_interface & p_source) {add_items(p_source);} metadb_handle_list_t() {} }; typedef metadb_handle_list_t<> metadb_handle_list; namespace metadb_handle_list_helper { void sorted_by_pointer_extract_difference(metadb_handle_list const & p_list_1,metadb_handle_list const & p_list_2,metadb_handle_list & p_list_1_specific,metadb_handle_list & p_list_2_specific); }; class metadb_handle_lock { metadb_handle_ptr m_ptr; public: inline metadb_handle_lock(const metadb_handle_ptr & param) { m_ptr = param; m_ptr->metadb_lock(); } inline ~metadb_handle_lock() {m_ptr->metadb_unlock();} }; inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const metadb_handle_ptr & p_location) { if (p_location.is_valid()) return p_fmt << p_location->get_location(); else return p_fmt << "[invalid location]"; } class string_format_title { public: string_format_title(metadb_handle_ptr p_item,const char * p_script) { p_item->format_title_legacy(NULL,m_data,p_script,NULL); } string_format_title(metadb_handle_ptr p_item,service_ptr_t<class titleformat_object> p_script) { p_item->format_title(NULL,m_data,p_script,NULL); } const char * get_ptr() const {return m_data.get_ptr();} operator const char * () const {return m_data.get_ptr();} private: pfc::string8_fastalloc m_data; }; #endif //_FOOBAR2000_METADB_HANDLE_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/metadb_handle_list.cpp ================================================ #include "foobar2000.h" namespace { struct custom_sort_data { HANDLE text; //int subsong; t_size index; }; } static int __cdecl custom_sort_compare(const custom_sort_data & elem1, const custom_sort_data & elem2 ) {//depends on unicode/ansi, nonportable (win32 lstrcmpi) int ret = uSortStringCompare(elem1.text,elem2.text);//uStringCompare // if (ret == 0) ret = elem1.subsong - elem2.subsong; if (ret == 0) ret = pfc::sgn_t((t_ssize)elem1.index - (t_ssize)elem2.index); return ret; } void metadb_handle_list_helper::sort_by_format_partial(pfc::list_base_t<metadb_handle_ptr> & p_list,t_size base,t_size count,const char * spec,titleformat_hook * p_hook) { service_ptr_t<titleformat_object> script; if (static_api_ptr_t<titleformat_compiler>()->compile(script,spec)) sort_by_format_partial(p_list,base,count,script,p_hook); } void metadb_handle_list_helper::sort_by_format_get_order_partial(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,t_size base,t_size count,t_size* order,const char * spec,titleformat_hook * p_hook) { service_ptr_t<titleformat_object> script; if (static_api_ptr_t<titleformat_compiler>()->compile(script,spec)) sort_by_format_get_order_partial(p_list,base,count,order,script,p_hook); } void metadb_handle_list_helper::sort_by_format_partial(pfc::list_base_t<metadb_handle_ptr> & p_list,t_size base,t_size count,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook) { pfc::array_t<t_size> order; order.set_size(count); sort_by_format_get_order_partial(p_list,base,count,order.get_ptr(),p_script,p_hook); p_list.reorder_partial(base,order.get_ptr(),count); } void metadb_handle_list_helper::sort_by_format_get_order_partial(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,t_size base,t_size count,t_size* order,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook) { assert(base+count<=p_list.get_count()); t_size n; pfc::array_t<custom_sort_data> data; data.set_size(count); pfc::string8_fastalloc temp; pfc::string8_fastalloc temp2; temp.prealloc(512); for(n=0;n<count;n++) { metadb_handle_ptr item; p_list.get_item_ex(item,base+n); assert(item.is_valid()); item->format_title(p_hook,temp,p_script,0); data[n].index = n; data[n].text = uSortStringCreate(temp); //data[n].subsong = item->get_subsong_index(); } pfc::sort_t(data,custom_sort_compare,count); //qsort(data.get_ptr(),count,sizeof(custom_sort_data),(int (__cdecl *)(const void *elem1, const void *elem2 ))custom_sort_compare); for(n=0;n<count;n++) { order[n]=data[n].index; uSortStringFree(data[n].text); } } void metadb_handle_list_helper::sort_by_relative_path_partial(pfc::list_base_t<metadb_handle_ptr> & p_list,t_size base,t_size count) { assert(base+count<=p_list.get_count()); pfc::array_t<t_size> order; order.set_size(count); sort_by_relative_path_get_order_partial(p_list,base,count,order.get_ptr()); p_list.reorder_partial(base,order.get_ptr(),count); } void metadb_handle_list_helper::sort_by_relative_path_get_order_partial(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,t_size base,t_size count,t_size* order) { assert(base+count<=p_list.get_count()); t_size n; pfc::array_t<custom_sort_data> data; data.set_size(count); static_api_ptr_t<library_manager> api; pfc::string8_fastalloc temp; temp.prealloc(512); for(n=0;n<count;n++) { metadb_handle_ptr item; p_list.get_item_ex(item,base+n); if (!api->get_relative_path(item,temp)) temp = ""; data[n].index = n; data[n].text = uSortStringCreate(temp); //data[n].subsong = item->get_subsong_index(); } pfc::sort_t(data,custom_sort_compare,count); //qsort(data.get_ptr(),count,sizeof(custom_sort_data),(int (__cdecl *)(const void *elem1, const void *elem2 ))custom_sort_compare); for(n=0;n<count;n++) { order[n]=data[n].index; uSortStringFree(data[n].text); } } void metadb_handle_list_helper::remove_duplicates(pfc::list_base_t<metadb_handle_ptr> & p_list) { t_size count = p_list.get_count(); if (count>0) { bit_array_bittable mask(count); pfc::array_t<t_size> order; order.set_size(count); order_helper::g_fill(order); p_list.sort_get_permutation_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>,order.get_ptr()); t_size n; bool found = false; for(n=0;n<count-1;n++) { if (p_list.get_item(order[n])==p_list.get_item(order[n+1])) { found = true; mask.set(order[n+1],true); } } if (found) p_list.remove_mask(mask); } } void metadb_handle_list_helper::sort_by_pointer_remove_duplicates(pfc::list_base_t<metadb_handle_ptr> & p_list) { t_size count = p_list.get_count(); if (count>0) { sort_by_pointer(p_list); bool b_found = false; t_size n; for(n=0;n<count-1;n++) { if (p_list.get_item(n)==p_list.get_item(n+1)) { b_found = true; break; } } if (b_found) { bit_array_bittable mask(count); t_size n; for(n=0;n<count-1;n++) { if (p_list.get_item(n)==p_list.get_item(n+1)) mask.set(n+1,true); } p_list.remove_mask(mask); } } } void metadb_handle_list_helper::sort_by_path_quick(pfc::list_base_t<metadb_handle_ptr> & p_list) { p_list.sort_t(metadb::path_compare_metadb_handle); } void metadb_handle_list_helper::sort_by_pointer(pfc::list_base_t<metadb_handle_ptr> & p_list) { //it seems MSVC71 /GL does something highly retarded here //p_list.sort_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>); p_list.sort(); } t_size metadb_handle_list_helper::bsearch_by_pointer(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const metadb_handle_ptr & val) { t_size blah; if (p_list.bsearch_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>,val,blah)) return blah; else return ~0; } void metadb_handle_list_helper::sorted_by_pointer_extract_difference(metadb_handle_list const & p_list_1,metadb_handle_list const & p_list_2,metadb_handle_list & p_list_1_specific,metadb_handle_list & p_list_2_specific) { t_size found_1, found_2; const t_size count_1 = p_list_1.get_count(), count_2 = p_list_2.get_count(); t_size ptr_1, ptr_2; found_1 = found_2 = 0; ptr_1 = ptr_2 = 0; while(ptr_1 < count_1 || ptr_2 < count_2) { while(ptr_1 < count_1 && (ptr_2 == count_2 || p_list_1[ptr_1] < p_list_2[ptr_2])) { found_1++; t_size ptr_1_new = ptr_1 + 1; while(ptr_1_new < count_1 && p_list_1[ptr_1_new] == p_list_1[ptr_1]) ptr_1_new++; ptr_1 = ptr_1_new; } while(ptr_2 < count_2 && (ptr_1 == count_1 || p_list_2[ptr_2] < p_list_1[ptr_1])) { found_2++; t_size ptr_2_new = ptr_2 + 1; while(ptr_2_new < count_2 && p_list_2[ptr_2_new] == p_list_2[ptr_2]) ptr_2_new++; ptr_2 = ptr_2_new; } while(ptr_1 < count_1 && ptr_2 < count_2 && p_list_1[ptr_1] == p_list_2[ptr_2]) {ptr_1++; ptr_2++;} } p_list_1_specific.set_count(found_1); p_list_2_specific.set_count(found_2); if (found_1 > 0 || found_2 > 0) { found_1 = found_2 = 0; ptr_1 = ptr_2 = 0; while(ptr_1 < count_1 || ptr_2 < count_2) { while(ptr_1 < count_1 && (ptr_2 == count_2 || p_list_1[ptr_1] < p_list_2[ptr_2])) { p_list_1_specific[found_1++] = p_list_1[ptr_1]; t_size ptr_1_new = ptr_1 + 1; while(ptr_1_new < count_1 && p_list_1[ptr_1_new] == p_list_1[ptr_1]) ptr_1_new++; ptr_1 = ptr_1_new; } while(ptr_2 < count_2 && (ptr_1 == count_1 || p_list_2[ptr_2] < p_list_1[ptr_1])) { p_list_2_specific[found_2++] = p_list_2[ptr_2]; t_size ptr_2_new = ptr_2 + 1; while(ptr_2_new < count_2 && p_list_2[ptr_2_new] == p_list_2[ptr_2]) ptr_2_new++; ptr_2 = ptr_2_new; } while(ptr_1 < count_1 && ptr_2 < count_2 && p_list_1[ptr_1] == p_list_2[ptr_2]) {ptr_1++; ptr_2++;} } } } double metadb_handle_list_helper::calc_total_duration(const pfc::list_base_const_t<metadb_handle_ptr> & p_list) { double ret = 0; t_size n, m = p_list.get_count(); for(n=0;n<m;n++) { double temp = p_list.get_item(n)->get_length(); if (temp > 0) ret += temp; } return ret; } void metadb_handle_list_helper::sort_by_path(pfc::list_base_t<metadb_handle_ptr> & p_list) { sort_by_format_partial(p_list,0,p_list.get_count(),"%path_sort%",0); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/modeless_dialog.cpp ================================================ #include "foobar2000.h" void modeless_dialog_manager::g_add(HWND p_wnd) { service_enum_t<modeless_dialog_manager> e; service_ptr_t<modeless_dialog_manager> ptr; if (e.first(ptr)) do { ptr->add(p_wnd); } while(e.next(ptr)); } void modeless_dialog_manager::g_remove(HWND p_wnd) { service_enum_t<modeless_dialog_manager> e; service_ptr_t<modeless_dialog_manager> ptr; if (e.first(ptr)) do { ptr->remove(p_wnd); } while(e.next(ptr)); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/modeless_dialog.h ================================================ #ifndef _MODELESS_DIALOG_H_ #define _MODELESS_DIALOG_H_ //! Service for plugging your nonmodal dialog windows into main app loop to receive IsDialogMessage()-translated messages.\n //! Note that all methods are valid from main app thread only.\n //! Usage: static_api_ptr_t<modeless_dialog_manager> or modeless_dialog_manager::g_add / modeless_dialog_manager::g_remove. class NOVTABLE modeless_dialog_manager : public service_base { public: //! Adds specified window to global list of windows to receive IsDialogMessage(). virtual void add(HWND p_wnd) = 0; //! Removes specified window from global list of windows to receive IsDialogMessage(). virtual void remove(HWND p_wnd) = 0; //! Static helper; see add(). static void g_add(HWND p_wnd); //! Static helper; see remove(). static void g_remove(HWND p_wnd); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(modeless_dialog_manager); }; #endif //_MODELESS_DIALOG_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/packet_decoder.cpp ================================================ #include "foobar2000.h" void packet_decoder::g_open(service_ptr_t<packet_decoder> & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort) { service_enum_t<packet_decoder_entry> e; service_ptr_t<packet_decoder_entry> ptr; while(e.next(ptr)) { if (ptr->is_our_setup(p_owner,p_param1,p_param2,p_param2size)) { ptr->open(p_out,p_decode,p_owner,p_param1,p_param2,p_param2size,p_abort); return; } } throw exception_io_data(); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/packet_decoder.h ================================================ //! Provides interface to decode various audio data types to PCM. Use packet_decoder_factory_t template to register. class packet_decoder : public service_base { protected: //! Prototype of function that must be implemented by packet_decoder implementation but is not accessible through packet_decoder interface itself. //! Determines whether specific packet_decoder implementation supports specified decoder setup data. static bool g_is_our_setup(const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) {return false;} //! Prototype of function that must be implemented by packet_decoder implementation but is not accessible through packet_decoder interface itself. //! Initializes packet decoder instance with specified decoder setup data. This is called only once, before any other methods. //! @param p_decode If set to true, decode() and reset_after_seek() calls can be expected later. If set to false, those methods will not be called on this packet_decoder instance - for an example when caller is only retrieving information about the file rather than preparing to decode it. void open(const GUID & p_owner,bool p_decode,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort) {throw exception_io_data();} public: //! Forwards additional information about stream being decoded. \n //! Calling: this must be called immediately after packet_decoder object is created, before any other methods are called.\n //! Implementation: this is called after open() (which is called by implementation framework immediately after creation), and before any other methods are called. virtual t_size set_stream_property(const GUID & p_type,t_size p_param1,const void * p_param2,t_size p_param2size) = 0; //! Retrieves additional user-readable tech infos that decoder can provide. //! @param p_info Interface receiving information about the stream being decoded. Note that it already contains partial info about the file; existing info should not be erased, decoder-provided info should be merged with it. virtual void get_info(file_info & p_info) = 0; //! Returns many frames back to start decoding when seeking. virtual unsigned get_max_frame_dependency()=0; //! Returns much time back to start decoding when seeking (for containers where going back by specified number of frames is not trivial). virtual double get_max_frame_dependency_time()=0; //! Flushes decoder after seeking. virtual void reset_after_seek()=0; //! Decodes a block of audio data.\n //! It may return empty chunk even when successful (caused by encoder+decoder delay for an example), caller must check for it and handle it appropriately. virtual void decode(const void * p_buffer,t_size p_bytes,audio_chunk & p_chunk,abort_callback & p_abort)=0; //! Returns whether this packet decoder supports analyze_first_frame() function. virtual bool analyze_first_frame_supported() = 0; //! Optional. Some codecs need to analyze first frame of the stream to return additional info about the stream, such as encoding setup. This can be only called immediately after instantiation (and set_stream_property() if present), before any actual decoding or get_info(). Caller can determine whether this method is supported or not by calling analyze_first_frame_supported(), to avoid reading first frame when decoder won't utiilize the extra info for an example. If particular decoder can't utilize first frame info in any way (and analyze_first_frame_supported() returns false), this function should do nothing and succeed. virtual void analyze_first_frame(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0; //! Static helper, creates a packet_decoder instance and initializes it with specific decoder setup data. static void g_open(service_ptr_t<packet_decoder> & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort); static const GUID owner_MP4,owner_matroska,owner_MP3,owner_MP2,owner_MP1,owner_MP4_ALAC,owner_ADTS,owner_ADIF, owner_Ogg, owner_MP4_AMR, owner_MP4_AMR_WB; struct matroska_setup { const char * codec_id; unsigned sample_rate,sample_rate_output; unsigned channels; unsigned codec_private_size; const void * codec_private; }; //owner_MP4: param1 - codec ID (MP4 audio type), param2 - MP4 codec initialization data //owner_MP3: raw MP3/MP2 file, parameters ignored //owner_matroska: param2 = matroska_setup struct, param2size size must be equal to sizeof(matroska_setup) //these are used to initialize PCM decoder static const GUID property_samplerate,property_bitspersample,property_channels,property_byteorder,property_signed,property_channelmask; //property_samplerate : param1 == sample rate in hz //property_bitspersample : param1 == bits per sample //property_channels : param1 == channel count //property_byteorder : if (param1) little_endian; else big_endian; //property_signed : if (param1) signed; else unsigned; //property_ogg_header : p_param1 = unused, p_param2 = ogg_packet structure, retval: 0 when more headers are wanted, 1 when done parsing headers //property_ogg_query_sample_rate : returns sample rate, no parameters //property_ogg_packet : p_param1 = unused, p_param2 = ogg_packet strucute static const GUID property_ogg_header, property_ogg_query_sample_rate, property_ogg_packet; FB2K_MAKE_SERVICE_INTERFACE(packet_decoder,service_base); }; class packet_decoder_streamparse : public packet_decoder { public: virtual void decode_ex(const void * p_buffer,t_size p_bytes,t_size & p_bytes_processed,audio_chunk & p_chunk,abort_callback & p_abort) = 0; virtual void analyze_first_frame_ex(const void * p_buffer,t_size p_bytes,t_size & p_bytes_processed,abort_callback & p_abort) = 0; FB2K_MAKE_SERVICE_INTERFACE(packet_decoder_streamparse,packet_decoder); }; class packet_decoder_entry : public service_base { public: virtual bool is_our_setup(const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) = 0; virtual void open(service_ptr_t<packet_decoder> & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(packet_decoder_entry); }; template<class T> class packet_decoder_entry_impl_t : public packet_decoder_entry { public: bool is_our_setup(const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) { return T::g_is_our_setup(p_owner,p_param1,p_param2,p_param2size); } void open(service_ptr_t<packet_decoder> & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort) { assert(is_our_setup(p_owner,p_param1,p_param2,p_param2size)); service_ptr_t<T> instance = new service_impl_t<T>(); instance->open(p_owner,p_decode,p_param1,p_param2,p_param2size,p_abort); p_out = instance.get_ptr(); } }; template<typename T> class packet_decoder_factory_t : public service_factory_single_t<packet_decoder_entry_impl_t<T> > {}; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/play_callback.h ================================================ #ifndef _PLAY_CALLBACK_H_ #define _PLAY_CALLBACK_H_ /*! Class receiving notifications about playback events. Note that all methods are called only from app's main thread. Use play_callback_manager to register your dynamically created instances. Statically registered version is available too - see play_callback_static. */ class NOVTABLE play_callback { public: //! Playback process is being initialized. on_playback_new_track() should be called soon after this when first file is successfully opened for decoding. virtual void FB2KAPI on_playback_starting(play_control::t_track_command p_command,bool p_paused)=0; //! Playback advanced to new track. virtual void FB2KAPI on_playback_new_track(metadb_handle_ptr p_track) = 0; //! Playback stopped. virtual void FB2KAPI on_playback_stop(play_control::t_stop_reason p_reason)=0; //! User has seeked to specific time. virtual void FB2KAPI on_playback_seek(double p_time)=0; //! Called on pause/unpause. virtual void FB2KAPI on_playback_pause(bool p_state)=0; //! Called when currently played file gets edited. virtual void FB2KAPI on_playback_edited(metadb_handle_ptr p_track) = 0; //! Dynamic info (VBR bitrate etc) change. virtual void FB2KAPI on_playback_dynamic_info(const file_info & p_info) = 0; //! Per-track dynamic info (stream track titles etc) change. Happens less often than on_playback_dynamic_info(). virtual void FB2KAPI on_playback_dynamic_info_track(const file_info & p_info) = 0; //! Called every second, for time display virtual void FB2KAPI on_playback_time(double p_time) = 0; //! User changed volume settings. Possibly called when not playing. //! @param p_new_val new volume level in dB; 0 for full volume. virtual void FB2KAPI on_volume_change(float p_new_val) = 0; enum { flag_on_playback_starting = 1 << 0, flag_on_playback_new_track = 1 << 1, flag_on_playback_stop = 1 << 2, flag_on_playback_seek = 1 << 3, flag_on_playback_pause = 1 << 4, flag_on_playback_edited = 1 << 5, flag_on_playback_dynamic_info = 1 << 6, flag_on_playback_dynamic_info_track = 1 << 7, flag_on_playback_time = 1 << 8, flag_on_volume_change = 1 << 9, flag_on_playback_all = flag_on_playback_starting | flag_on_playback_new_track | flag_on_playback_stop | flag_on_playback_seek | flag_on_playback_pause | flag_on_playback_edited | flag_on_playback_dynamic_info | flag_on_playback_dynamic_info_track | flag_on_playback_time, }; protected: play_callback() {} ~play_callback() {} }; //! Standard API (always present); manages registrations of dynamic play_callbacks. //! Usage: use static_api_ptr_t<play_callback_manager>. //! Do not reimplement. class NOVTABLE play_callback_manager : public service_base { public: //! Registers a play_callback object. //! @param p_callback Interface to register. //! @param p_flags Indicates which notifications are requested. virtual void FB2KAPI register_callback(play_callback * p_callback,unsigned p_flags,bool p_forward_status_on_register) = 0; //! Unregisters a play_callback object. //! @p_callback Previously registered interface to unregister. virtual void FB2KAPI unregister_callback(play_callback * p_callback) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(play_callback_manager); }; //! Static (autoregistered) version of play_callback. Use play_callback_static_factory_t to register. class play_callback_static : public service_base, public play_callback { public: //! Controls which methods your callback wants called; returned value should not change in run time, you should expect it to be queried only once (on startup). See play_callback::flag_* constants. virtual unsigned get_flags() = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(play_callback_static); }; template<typename T> class play_callback_static_factory_t : public service_factory_single_t<T> {}; //! Gets notified about tracks being played. Notification occurs when at least 60s of the track has been played, or the track has reached its end after at least 1/3 of it has been played through. //! Use playback_statistics_collector_factory_t to register. class NOVTABLE playback_statistics_collector : public service_base { public: virtual void on_item_played(metadb_handle_ptr p_item) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playback_statistics_collector); }; template<typename T> class playback_statistics_collector_factory_t : public service_factory_single_t<T> {}; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/playable_location.cpp ================================================ #include "foobar2000.h" int playable_location::g_compare(const playable_location & p_item1,const playable_location & p_item2) { int ret = metadb::path_compare(p_item1.get_path(),p_item2.get_path()); if (ret != 0) return 0; return pfc::compare_t(p_item1.get_subsong(),p_item2.get_subsong()); } pfc::string_base & operator<<(pfc::string_base & p_fmt,const playable_location & p_location) { p_fmt << "\"" << file_path_display(p_location.get_path()) << "\""; t_uint32 index = p_location.get_subsong_index(); if (index != 0) p_fmt << " / index: " << p_location.get_subsong_index(); return p_fmt; } bool playable_location::operator==(const playable_location & p_other) const { return metadb::path_compare(get_path(),p_other.get_path()) == 0 && get_subsong() == p_other.get_subsong(); } bool playable_location::operator!=(const playable_location & p_other) const { return !(*this == p_other); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/playable_location.h ================================================ #ifndef _FOOBAR2000_PLAYABLE_LOCATION_H_ #define _FOOBAR2000_PLAYABLE_LOCATION_H_ #include "service.h" //playable_location stores location of a playable resource, currently implemented as file path and integer for indicating multiple playable "subsongs" per file //also see: file_info.h //for getting more info about resource referenced by a playable_location, see metadb.h //char* strings are all UTF-8 class NOVTABLE playable_location//interface (for passing around between DLLs) { public: virtual const char * get_path() const =0; virtual void set_path(const char*)=0; virtual t_uint32 get_subsong() const =0; virtual void set_subsong(t_uint32)=0; void copy(const playable_location & p_other) { set_path(p_other.get_path()); set_subsong(p_other.get_subsong()); } static int g_compare(const playable_location & p_item1,const playable_location & p_item2); const playable_location & operator=(const playable_location & src) {copy(src);return *this;} bool operator==(const playable_location & p_other) const; bool operator!=(const playable_location & p_other) const; inline bool is_empty() {return get_path()[0]==0 && get_subsong()==0;} inline void reset() {set_path("");set_subsong(0);} inline t_uint32 get_subsong_index() const {return get_subsong();} inline void set_subsong_index(t_uint32 v) {set_subsong(v);} protected: playable_location() {} ~playable_location() {} }; typedef playable_location * pplayable_location; typedef playable_location const * pcplayable_location; typedef playable_location & rplayable_location; typedef playable_location const & rcplayable_location; class playable_location_impl : public playable_location//implementation { public: const char * get_path() const {return m_path;} void set_path(const char* p_path) {m_path=p_path;} t_uint32 get_subsong() const {return m_subsong;} void set_subsong(t_uint32 p_subsong) {m_subsong=p_subsong;} const playable_location_impl & operator=(const playable_location & src) {copy(src);return *this;} const playable_location_impl & operator=(const playable_location_impl & src) {copy(src);return *this;} playable_location_impl() : m_subsong(0) {} playable_location_impl(const char * p_path,t_uint32 p_subsong) : m_path(p_path), m_subsong(p_subsong) {} playable_location_impl(const playable_location & src) {copy(src);} playable_location_impl(const playable_location_impl & src) {copy(src);} private: pfc::string_simple m_path; t_uint32 m_subsong; }; // usage: something( make_playable_location("file://c:\blah.ogg",0) ); // only for use as a parameter to a function taking const playable_location & class make_playable_location : public playable_location { const char * path; t_uint32 num; void set_path(const char*) {throw pfc::exception_not_implemented();} void set_subsong(t_uint32) {throw pfc::exception_not_implemented();} public: const char * get_path() const {return path;} t_uint32 get_subsong() const {return num;} make_playable_location(const char * p_path,t_uint32 p_num) : path(p_path), num(p_num) {} }; pfc::string_base & operator<<(pfc::string_base & p_fmt,const playable_location & p_location); #endif //_FOOBAR2000_PLAYABLE_LOCATION_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/playback_control.cpp ================================================ #include "foobar2000.h" double playback_control::playback_get_length() { double rv = 0; metadb_handle_ptr ptr; if (get_now_playing(ptr)) { rv = ptr->get_length(); } return rv; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/playback_control.h ================================================ #ifndef _PLAY_CONTROL_H_ #define _PLAY_CONTROL_H_ //! Provides control for various playback-related operations. //! All methods provided by this interface work from main app thread only. Calling from another thread will do nothing or trigger an exception. If you need to trigger one of playback_control methods from another thread, see main_thread_callback. //! Do not call playback_control methods from inside any kind of global callback (e.g. playlist callback), otherwise race conditions may occur. //! Use static_api_ptr_t to instantiate. See static_api_ptr_t documentation for more info. class NOVTABLE playback_control : public service_base { public: enum t_stop_reason { stop_reason_user = 0, stop_reason_eof, stop_reason_starting_another, stop_reason_shutting_down, }; enum t_track_command { track_command_default = 0, track_command_play, track_command_next, track_command_prev, track_command_settrack, track_command_rand, track_command_resume, }; //! Retrieves now playing item handle. //! @returns true on success, false on failure (not playing). virtual bool get_now_playing(metadb_handle_ptr & p_out) = 0; //! Starts playback. If playback is already active, existing process is stopped first. //! @param p_command Specifies what track to start playback from. See t_track_Command enum for more info. //! @param p_paused Specifies whether playback should be started as paused. virtual void start(t_track_command p_command = track_command_play,bool p_paused = false) = 0; //! Stops playback. virtual void stop() = 0; //! Returns whether playback is active. virtual bool is_playing() = 0; //! Returns whether playback is active and in paused state. virtual bool is_paused() = 0; //! Toggles pause state if playback is active. //! @param p_state set to true when pausing or to false when unpausing. virtual void pause(bool p_state) = 0; //! Retrieves stop-after-current-track option state. virtual bool get_stop_after_current() = 0; //! Alters stop-after-current-track option state. virtual void set_stop_after_current(bool p_state) = 0; //! Alters playback volume level. //! @param p_value volume in dB; 0 for full volume. virtual void set_volume(float p_value) = 0; //! Retrieves playback volume level. //! @returns current playback volume level, in dB; 0 for full volume. virtual float get_volume() = 0; //! Alters playback volume level one step up. virtual void volume_up() = 0; //! Alters playback volume level one step down. virtual void volume_down() = 0; //! Toggles playback mute state. virtual void volume_mute_toggle() = 0; //! Seeks in currenly played track to specified time. //! @param p_time target time in seconds. virtual void playback_seek(double p_time) = 0; //! Seeks in currently played track by specified time forward or back. //! @param p_delta time in seconds to seek by; can be positive to seek forward or negative to seek back. virtual void playback_seek_delta(double p_delta) = 0; //! Returns whether currently played track is seekable. If it's not, playback_seek/playback_seek_delta calls will be ignored. virtual bool playback_can_seek() = 0; //! Returns current playback position within currently played track, in seconds. virtual double playback_get_position() = 0; //! Type used to indicate level of dynamic playback-related info displayed. Safe to use with <> opereators, e.g. level above N always includes information rendered by level N. enum t_display_level { //! No playback-related info display_level_none, //! Static info and is_playing/is_paused stats display_level_basic, //! Display_level_static + dynamic track titles on e.g. live streams display_level_titles, //! Display_level_titles + timing + VBR bitrate display etc display_level_all, }; //! Renders information about currently playing item. //! @param p_hook Optional callback object overriding fields and functions; set to NULL if not used. //! @param p_out String receiving the output on success. //! @param p_script Titleformat script to use. Use titleformat_compiler service to create one. //! @param p_filter Optional callback object allowing input to be filtered according to context (i.e. removal of linebreak characters present in tags when rendering playlist lines). Set to NULL when not used. //! @param p_level Indicates level of dynamic playback-related info displayed. See t_display_level enum for more details. //! @returns true on success, false when no item is currently being played. virtual bool playback_format_title(titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<class titleformat_object> & p_script,titleformat_text_filter * p_filter,t_display_level p_level) = 0; //! Helper; renders info about any item, including currently playing item info if the item is currently played. bool playback_format_title_ex(metadb_handle_ptr p_item,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<class titleformat_object> & p_script,titleformat_text_filter * p_filter,t_display_level p_level) { if (p_item.is_empty()) return playback_format_title(p_hook,p_out,p_script,p_filter,p_level); metadb_handle_ptr temp; if (get_now_playing(temp)) { if (temp == p_item) { return playback_format_title(p_hook,p_out,p_script,p_filter,p_level); } } p_item->format_title(p_hook,p_out,p_script,p_filter); return true; } //! Helper; retrieves length of currently playing item. double playback_get_length(); //! Toggles stop-after-current state. void toggle_stop_after_current() {set_stop_after_current(!get_stop_after_current());} //! Toggles pause state. void toggle_pause() {pause(!is_paused());} //! Starts playback if playback is inactive, otherwise toggles pause. void play_or_pause() {if (is_playing()) toggle_pause(); else start();} //deprecated inline void play_start(t_track_command p_command = track_command_play,bool p_paused = false) {start(p_command,p_paused);} //deprecated inline void play_stop() {stop();} FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playback_control); }; class playback_control_v2 : public playback_control { public: virtual float get_volume_step() = 0; FB2K_MAKE_SERVICE_INTERFACE(playback_control_v2,playback_control); }; //for compatibility with old code typedef playback_control play_control; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/playlist.cpp ================================================ #include "foobar2000.h" namespace { class enum_items_callback_retrieve_item : public playlist_manager::enum_items_callback { metadb_handle_ptr m_item; public: enum_items_callback_retrieve_item() : m_item(0) {} bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) { assert(m_item.is_empty()); m_item = p_location; return false; } inline const metadb_handle_ptr & get_item() {return m_item;} }; class enum_items_callback_retrieve_selection : public playlist_manager::enum_items_callback { bool m_state; public: enum_items_callback_retrieve_selection() : m_state(false) {} bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) { m_state = b_selected; return false; } inline bool get_state() {return m_state;} }; class enum_items_callback_retrieve_selection_mask : public playlist_manager::enum_items_callback { bit_array_var & m_out; public: enum_items_callback_retrieve_selection_mask(bit_array_var & p_out) : m_out(p_out) {} bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) { m_out.set(p_index,b_selected); return true; } }; class enum_items_callback_retrieve_all_items : public playlist_manager::enum_items_callback { pfc::list_base_t<metadb_handle_ptr> & m_out; public: enum_items_callback_retrieve_all_items(pfc::list_base_t<metadb_handle_ptr> & p_out) : m_out(p_out) {m_out.remove_all();} bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) { m_out.add_item(p_location); return true; } }; class enum_items_callback_retrieve_selected_items : public playlist_manager::enum_items_callback { pfc::list_base_t<metadb_handle_ptr> & m_out; public: enum_items_callback_retrieve_selected_items(pfc::list_base_t<metadb_handle_ptr> & p_out) : m_out(p_out) {} bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) { if (b_selected) m_out.add_item(p_location); return true; } }; class enum_items_callback_count_selection : public playlist_manager::enum_items_callback { t_size m_counter,m_max; public: enum_items_callback_count_selection(t_size p_max) : m_max(p_max), m_counter(0) {} bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) { if (b_selected) { if (++m_counter >= m_max) return false; } return true; } inline t_size get_count() {return m_counter;} }; } void playlist_manager::playlist_get_all_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out) { playlist_get_items(p_playlist,out,bit_array_true()); } void playlist_manager::playlist_get_selected_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out) { playlist_enum_items(p_playlist,enum_items_callback_retrieve_selected_items(out),bit_array_true()); } void playlist_manager::playlist_get_selection_mask(t_size p_playlist,bit_array_var & out) { playlist_enum_items(p_playlist,enum_items_callback_retrieve_selection_mask(out),bit_array_true()); } bool playlist_manager::playlist_is_item_selected(t_size p_playlist,t_size p_item) { enum_items_callback_retrieve_selection callback; playlist_enum_items(p_playlist,callback,bit_array_one(p_item)); return callback.get_state(); } bool playlist_manager::playlist_get_item_handle(metadb_handle_ptr & p_out,t_size p_playlist,t_size p_item) { enum_items_callback_retrieve_item callback; playlist_enum_items(p_playlist,callback,bit_array_one(p_item)); p_out = callback.get_item(); return p_out.is_valid(); } bool playlist_manager::playlist_move_selection(t_size p_playlist,int p_delta) { if (p_delta==0) return true; t_size count = playlist_get_item_count(p_playlist); pfc::array_t<t_size> order; order.set_size(count); pfc::array_t<bool> selection; selection.set_size(count); { t_size n; for(n=0;n<count;n++) order[n]=n; } playlist_get_selection_mask(p_playlist,bit_array_var_table(selection.get_ptr(),selection.get_size())); if (p_delta<0) { for(;p_delta<0;p_delta++) { t_size idx; for(idx=1;idx<count;idx++) { if (selection[idx] && !selection[idx-1]) { pfc::swap_t(order[idx],order[idx-1]); pfc::swap_t(selection[idx],selection[idx-1]); } } } } else { for(;p_delta>0;p_delta--) { t_size idx; for(idx=count-2;(int)idx>=0;idx--) { if (selection[idx] && !selection[idx+1]) { pfc::swap_t(order[idx],order[idx+1]); pfc::swap_t(selection[idx],selection[idx+1]); } } } } return playlist_reorder_items(p_playlist,order.get_ptr(),count); } //retrieving status t_size playlist_manager::activeplaylist_get_item_count() { t_size playlist = get_active_playlist(); if (playlist == infinite) return 0; else return playlist_get_item_count(playlist); } void playlist_manager::activeplaylist_enum_items(enum_items_callback & p_callback,const bit_array & p_mask) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_enum_items(playlist,p_callback,p_mask); } t_size playlist_manager::activeplaylist_get_focus_item() { t_size playlist = get_active_playlist(); if (playlist == infinite) return infinite; else return playlist_get_focus_item(playlist); } bool playlist_manager::activeplaylist_get_name(pfc::string_base & p_out) { t_size playlist = get_active_playlist(); if (playlist == infinite) return false; else return playlist_get_name(playlist,p_out); } //modifying playlist bool playlist_manager::activeplaylist_reorder_items(const t_size * order,t_size count) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_reorder_items(playlist,order,count); else return false; } void playlist_manager::activeplaylist_set_selection(const bit_array & affected,const bit_array & status) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_set_selection(playlist,affected,status); } bool playlist_manager::activeplaylist_remove_items(const bit_array & mask) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_remove_items(playlist,mask); else return false; } bool playlist_manager::activeplaylist_replace_item(t_size p_item,const metadb_handle_ptr & p_new_item) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_replace_item(playlist,p_item,p_new_item); else return false; } void playlist_manager::activeplaylist_set_focus_item(t_size p_item) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_set_focus_item(playlist,p_item); } t_size playlist_manager::activeplaylist_insert_items(t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_insert_items(playlist,p_base,data,p_selection); else return infinite; } void playlist_manager::activeplaylist_ensure_visible(t_size p_item) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_ensure_visible(playlist,p_item); } bool playlist_manager::activeplaylist_rename(const char * p_name,t_size p_name_len) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_rename(playlist,p_name,p_name_len); else return false; } bool playlist_manager::activeplaylist_is_item_selected(t_size p_item) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_is_item_selected(playlist,p_item); else return false; } bool playlist_manager::activeplaylist_get_item_handle(metadb_handle_ptr & p_out,t_size p_item) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_get_item_handle(p_out,playlist,p_item); else return false; } void playlist_manager::activeplaylist_move_selection(int p_delta) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_move_selection(playlist,p_delta); } void playlist_manager::activeplaylist_get_selection_mask(bit_array_var & out) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_get_selection_mask(playlist,out); } void playlist_manager::activeplaylist_get_all_items(pfc::list_base_t<metadb_handle_ptr> & out) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_get_all_items(playlist,out); } void playlist_manager::activeplaylist_get_selected_items(pfc::list_base_t<metadb_handle_ptr> & out) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_get_selected_items(playlist,out); } bool playlist_manager::remove_playlist(t_size idx) { return remove_playlists(bit_array_one(idx)); } bool playlist_incoming_item_filter::process_location(const char * url,pfc::list_base_t<metadb_handle_ptr> & out,bool filter,const char * p_mask,const char * p_exclude,HWND p_parentwnd) { return process_locations(pfc::list_single_ref_t<const char*>(url),out,filter,p_mask,p_exclude,p_parentwnd); } void playlist_manager::playlist_clear(t_size p_playlist) { playlist_remove_items(p_playlist,bit_array_true()); } void playlist_manager::activeplaylist_clear() { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_clear(playlist); } bool playlist_manager::playlist_add_items(t_size playlist,const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection) { return playlist_insert_items(playlist,infinite,data,p_selection) != infinite; } bool playlist_manager::activeplaylist_add_items(const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_add_items(playlist,data,p_selection); else return false; } bool playlist_manager::playlist_insert_items_filter(t_size p_playlist,t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select) { metadb_handle_list temp; static_api_ptr_t<playlist_incoming_item_filter> api; if (!api->filter_items(p_data,temp)) return false; return playlist_insert_items(p_playlist,p_base,temp,bit_array_val(p_select)) != infinite; } bool playlist_manager::activeplaylist_insert_items_filter(t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_insert_items_filter(playlist,p_base,p_data,p_select); else return false; } bool playlist_manager::playlist_insert_locations(t_size p_playlist,t_size p_base,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd) { metadb_handle_list temp; static_api_ptr_t<playlist_incoming_item_filter> api; if (!api->process_locations(p_urls,temp,true,0,0,p_parentwnd)) return false; return playlist_insert_items(p_playlist,p_base,temp,bit_array_val(p_select)) != infinite; } bool playlist_manager::activeplaylist_insert_locations(t_size p_base,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_insert_locations(playlist,p_base,p_urls,p_select,p_parentwnd); else return false; } bool playlist_manager::playlist_add_items_filter(t_size p_playlist,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select) { return playlist_insert_items_filter(p_playlist,infinite,p_data,p_select); } bool playlist_manager::activeplaylist_add_items_filter(const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select) { return activeplaylist_insert_items_filter(infinite,p_data,p_select); } bool playlist_manager::playlist_add_locations(t_size p_playlist,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd) { return playlist_insert_locations(p_playlist,infinite,p_urls,p_select,p_parentwnd); } bool playlist_manager::activeplaylist_add_locations(const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd) { return activeplaylist_insert_locations(infinite,p_urls,p_select,p_parentwnd); } void playlist_manager::reset_playing_playlist() { set_playing_playlist(get_active_playlist()); } void playlist_manager::playlist_clear_selection(t_size p_playlist) { playlist_set_selection(p_playlist,bit_array_true(),bit_array_false()); } void playlist_manager::activeplaylist_clear_selection() { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_clear_selection(playlist); } void playlist_manager::activeplaylist_undo_backup() { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_undo_backup(playlist); } bool playlist_manager::activeplaylist_undo_restore() { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_undo_restore(playlist); else return false; } bool playlist_manager::activeplaylist_redo_restore() { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_redo_restore(playlist); else return false; } void playlist_manager::playlist_remove_selection(t_size p_playlist,bool p_crop) { bit_array_bittable table(playlist_get_item_count(p_playlist)); playlist_get_selection_mask(p_playlist,table); if (p_crop) playlist_remove_items(p_playlist,bit_array_not(table)); else playlist_remove_items(p_playlist,table); } void playlist_manager::activeplaylist_remove_selection(bool p_crop) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_remove_selection(playlist,p_crop); } void playlist_manager::activeplaylist_item_format_title(t_size p_item,titleformat_hook * p_hook,pfc::string_base & out,const service_ptr_t<titleformat_object> & p_script,titleformat_text_filter * p_filter,play_control::t_display_level p_playback_info_level) { t_size playlist = get_active_playlist(); if (playlist == infinite) out = "NJET"; else playlist_item_format_title(playlist,p_item,p_hook,out,p_script,p_filter,p_playback_info_level); } void playlist_manager::playlist_set_selection_single(t_size p_playlist,t_size p_item,bool p_state) { playlist_set_selection(p_playlist,bit_array_one(p_item),bit_array_val(p_state)); } void playlist_manager::activeplaylist_set_selection_single(t_size p_item,bool p_state) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_set_selection_single(playlist,p_item,p_state); } t_size playlist_manager::playlist_get_selection_count(t_size p_playlist,t_size p_max) { enum_items_callback_count_selection callback(p_max); playlist_enum_items(p_playlist,callback,bit_array_true()); return callback.get_count(); } t_size playlist_manager::activeplaylist_get_selection_count(t_size p_max) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_get_selection_count(playlist,p_max); else return 0; } bool playlist_manager::playlist_get_focus_item_handle(metadb_handle_ptr & p_out,t_size p_playlist) { t_size index = playlist_get_focus_item(p_playlist); if (index == infinite) return false; return playlist_get_item_handle(p_out,p_playlist,index); } bool playlist_manager::activeplaylist_get_focus_item_handle(metadb_handle_ptr & p_out) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_get_focus_item_handle(p_out,playlist); else return false; } t_size playlist_manager::find_playlist(const char * p_name,t_size p_name_length) { t_size n, m = get_playlist_count(); pfc::string8_fastalloc temp; for(n=0;n<m;n++) { if (!playlist_get_name(n,temp)) break; if (stricmp_utf8_ex(temp,temp.length(),p_name,p_name_length) == 0) return n; } return infinite; } t_size playlist_manager::find_or_create_playlist(const char * p_name,t_size p_name_length) { t_size index = find_playlist(p_name,p_name_length); if (index != infinite) return index; return create_playlist(p_name,p_name_length,infinite); } t_size playlist_manager::create_playlist_autoname(t_size p_index) { static const char new_playlist_text[] = "New Playlist"; if (find_playlist(new_playlist_text,infinite) == infinite) return create_playlist(new_playlist_text,infinite,p_index); for(t_size walk = 2; ; walk++) { pfc::string_fixed_t<64> namebuffer; namebuffer << new_playlist_text << " (" << walk << ")"; if (find_playlist(namebuffer,infinite) == infinite) return create_playlist(namebuffer,infinite,p_index); } } bool playlist_manager::activeplaylist_sort_by_format(const char * spec,bool p_sel_only) { t_size playlist = get_active_playlist(); if (playlist != infinite) return playlist_sort_by_format(playlist,spec,p_sel_only); else return false; } bool playlist_manager::highlight_playing_item() { t_size playlist,item; if (!get_playing_item_location(&playlist,&item)) return false; set_active_playlist(playlist); playlist_set_focus_item(playlist,item); playlist_set_selection(playlist,bit_array_true(),bit_array_one(item)); playlist_ensure_visible(playlist,item); return true; } void playlist_manager::playlist_get_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out,const bit_array & p_mask) { playlist_enum_items(p_playlist,enum_items_callback_retrieve_all_items(out),p_mask); } void playlist_manager::activeplaylist_get_items(pfc::list_base_t<metadb_handle_ptr> & out,const bit_array & p_mask) { t_size playlist = get_active_playlist(); if (playlist != infinite) playlist_get_items(playlist,out,p_mask); } void playlist_manager::active_playlist_fix() { t_size playlist = get_active_playlist(); if (playlist == infinite) { t_size max = get_playlist_count(); if (max == 0) { create_playlist_autoname(); } set_active_playlist(0); } } namespace { class enum_items_callback_remove_list : public playlist_manager::enum_items_callback { const metadb_handle_list & m_data; bit_array_var & m_table; t_size m_found; public: enum_items_callback_remove_list(const metadb_handle_list & p_data,bit_array_var & p_table) : m_data(p_data), m_table(p_table), m_found(0) {} bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) { bool found = m_data.bsearch_by_pointer(p_location) != infinite; m_table.set(p_index,found); if (found) m_found++; return true; } inline t_size get_found() const {return m_found;} }; } void playlist_manager::remove_items_from_all_playlists(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) { t_size playlist_num, playlist_max = get_playlist_count(); if (playlist_max != infinite) { metadb_handle_list temp; temp.add_items(p_data); temp.sort_by_pointer(); for(playlist_num = 0; playlist_num < playlist_max; playlist_num++ ) { t_size playlist_item_count = playlist_get_item_count(playlist_num); if (playlist_item_count == infinite) break; bit_array_bittable table(playlist_item_count); enum_items_callback_remove_list callback(temp,table); playlist_enum_items(playlist_num,callback,bit_array_true()); if (callback.get_found()>0) playlist_remove_items(playlist_num,table); } } } bool playlist_manager::get_all_items(pfc::list_base_t<metadb_handle_ptr> & out) { t_size n, m = get_playlist_count(); if (m == infinite) return false; enum_items_callback_retrieve_all_items callback(out); for(n=0;n<m;n++) { playlist_enum_items(n,callback,bit_array_true()); } return true; } t_uint32 playlist_manager::activeplaylist_lock_get_filter_mask() { t_size playlist = get_active_playlist(); if (playlist == infinite) return ~0; else return playlist_lock_get_filter_mask(playlist); } bool playlist_manager::activeplaylist_is_undo_available() { t_size playlist = get_active_playlist(); if (playlist == infinite) return false; else return playlist_is_undo_available(playlist); } bool playlist_manager::activeplaylist_is_redo_available() { t_size playlist = get_active_playlist(); if (playlist == infinite) return false; else return playlist_is_redo_available(playlist); } bool playlist_manager::remove_playlist_switch(t_size idx) { bool need_switch = get_active_playlist() == idx; if (remove_playlist(idx)) { if (need_switch) { t_size total = get_playlist_count(); if (total > 0) { if (idx >= total) idx = total-1; set_active_playlist(idx); } } return true; } else return false; } bool t_playback_queue_item::operator==(const t_playback_queue_item & p_item) const { return m_handle == p_item.m_handle && m_playlist == p_item.m_playlist && m_item == p_item.m_item; } bool t_playback_queue_item::operator!=(const t_playback_queue_item & p_item) const { return m_handle != p_item.m_handle || m_playlist != p_item.m_playlist || m_item != p_item.m_item; } bool playlist_manager::activeplaylist_execute_default_action(t_size p_item) { t_size idx = get_active_playlist(); if (idx == infinite) return false; else return playlist_execute_default_action(idx,p_item); } namespace { class completion_notify_dfd : public completion_notify { public: completion_notify_dfd(const pfc::list_base_const_t<metadb_handle_ptr> & p_data,service_ptr_t<process_locations_notify> p_notify) : m_data(p_data), m_notify(p_notify) {} void on_completion(unsigned p_code) { switch(p_code) { case metadb_io::load_info_aborted: m_notify->on_aborted(); break; default: m_notify->on_completion(m_data); break; } } private: metadb_handle_list m_data; service_ptr_t<process_locations_notify> m_notify; }; }; void dropped_files_data_impl::to_handles_async_ex(t_uint32 p_op_flags,HWND p_parentwnd,service_ptr_t<process_locations_notify> p_notify) { if (m_is_paths) { static_api_ptr_t<playlist_incoming_item_filter_v2>()->process_locations_async( m_paths, p_op_flags, NULL, NULL, p_parentwnd, p_notify); } else { t_uint32 flags = 0; if (p_op_flags & playlist_incoming_item_filter_v2::op_flag_background) flags |= metadb_io_v2::op_flag_background; if (p_op_flags & playlist_incoming_item_filter_v2::op_flag_delay_ui) flags |= metadb_io_v2::op_flag_delay_ui; static_api_ptr_t<metadb_io_v2>()->load_info_async(m_handles,metadb_io::load_info_default,p_parentwnd,flags,new service_impl_t<completion_notify_dfd>(m_handles,p_notify)); } } void dropped_files_data_impl::to_handles_async(bool p_filter,HWND p_parentwnd,service_ptr_t<process_locations_notify> p_notify) { to_handles_async_ex(p_filter ? 0 : playlist_incoming_item_filter_v2::op_flag_no_filter,p_parentwnd,p_notify); } bool dropped_files_data_impl::to_handles(pfc::list_base_t<metadb_handle_ptr> & p_out,bool p_filter,HWND p_parentwnd) { if (m_is_paths) { return static_api_ptr_t<playlist_incoming_item_filter>()->process_locations(m_paths,p_out,p_filter,NULL,NULL,p_parentwnd); } else { if (static_api_ptr_t<metadb_io>()->load_info_multi(m_handles,metadb_io::load_info_default,p_parentwnd,true) == metadb_io::load_info_aborted) return false; p_out = m_handles; return true; } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/playlist.h ================================================ #ifndef _PLAYLIST_H_ #define _PLAYLIST_H_ //! This interface allows filtering of playlist modification operations.\n //! Implemented by components "locking" playlists; use playlist_manager::playlist_lock_install() etc to takeover specific playlist with your instance of playlist_lock. class NOVTABLE playlist_lock : public service_base { public: enum { filter_add = 1 << 0, filter_remove = 1 << 1, filter_reorder = 1 << 2, filter_replace = 1 << 3, filter_rename = 1 << 4, filter_remove_playlist = 1 << 5, filter_default_action = 1 << 6, }; //! Queries whether specified item insertiion operation is allowed in the locked playlist. //! @param p_base Index from which the items are being inserted. //! @param p_data Items being inserted. //! @param p_selection Caller-requested selection state of items being inserted. //! @returns True to allow the operation, false to block it. virtual bool query_items_add(t_size p_base, const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const bit_array & p_selection) = 0; //! Queries whether specified item reorder operation is allowed in the locked playlist. //! @param p_order Pointer to array containing permutation defining requested reorder operation. //! @param p_count Number of items in array pointed to by p_order. This should always be equal to number of items on the locked playlist. //! @returns True to allow the operation, false to block it. virtual bool query_items_reorder(const t_size * p_order,t_size p_count) = 0; //! Queries whether specified item removal operation is allowed in the locked playlist. //! @param p_mask Specifies which items from locked playlist are being removed. //! @param p_force If set to true, the call is made only for notification purpose and items are getting removed regardless (after e.g. they have been physically removed). //! @returns True to allow the operation, false to block it. Note that return value is ignored if p_force is set to true. virtual bool query_items_remove(const bit_array & p_mask,bool p_force) = 0; //! Queries whether specified item replacement operation is allowed in the locked playlist. //! @param p_index Index of the item being replaced. //! @param p_old Old value of the item being replaced. //! @param p_new New value of the item being replaced. //! @returns True to allow the operation, false to block it. virtual bool query_item_replace(t_size p_index,const metadb_handle_ptr & p_old,const metadb_handle_ptr & p_new)=0; //! Queries whether renaming the locked playlist is allowed. //! @param p_new_name Requested new name of the playlist; a UTF-8 encoded string. //! @param p_new_name_len Length limit of the name string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings. //! @returns True to allow the operation, false to block it. virtual bool query_playlist_rename(const char * p_new_name,t_size p_new_name_len) = 0; //! Queries whether removal of the locked playlist is allowed. Note that the lock will be released when the playlist is removed. //! @returns True to allow the operation, false to block it. virtual bool query_playlist_remove() = 0; //! Executes "default action" (doubleclick etc) for specified playlist item. When the playlist is not locked, default action starts playback of the item. //! @returns True if custom default action was executed, false to fall-through to default one for non-locked playlists (start playback). virtual bool execute_default_action(t_size p_item) = 0; //! Notifies lock about changed index of the playlist, in result of user reordering playlists or removing other playlists. virtual void on_playlist_index_change(t_size p_new_index) = 0; //! Notifies lock about the locked playlist getting removed. virtual void on_playlist_remove() = 0; //! Retrieves human-readable name of playlist lock to display. virtual void get_lock_name(pfc::string_base & p_out) = 0; //! Requests user interface of component controlling the playlist lock to be shown. virtual void show_ui() = 0; //! Queries which actions the lock filters. The return value must not change while the lock is registered with playlist_manager. The return value is a combination of one or more filter_* constants. virtual t_uint32 get_filter_mask() = 0; FB2K_MAKE_SERVICE_INTERFACE(playlist_lock,service_base); }; struct t_playback_queue_item { metadb_handle_ptr m_handle; t_size m_playlist,m_item; bool operator==(const t_playback_queue_item & p_item) const; bool operator!=(const t_playback_queue_item & p_item) const; }; //! This service provides methods for all sorts of playlist interaction.\n //! All playlist_manager methods are valid only from main app thread.\n //! Usage: static_api_ptr_t<playlist_manager>. class NOVTABLE playlist_manager : public service_base { public: //! Callback interface for playlist enumeration methods. class NOVTABLE enum_items_callback { public: //! @returns True to continue enumeration, false to abort. virtual bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) = 0;//return false to stop }; //! Retrieves number of playlists. virtual t_size get_playlist_count() = 0; //! Retrieves index of active playlist; infinite if no playlist is active. virtual t_size get_active_playlist() = 0; //! Sets active playlist (infinite to set no active playlist). virtual void set_active_playlist(t_size p_index) = 0; //! Retrieves playlist from which items to be played are taken from. virtual t_size get_playing_playlist() = 0; //! Sets playlist from which items to be played are taken from. virtual void set_playing_playlist(t_size p_index) = 0; //! Removes playlists according to specified mask. See also: bit_array. virtual bool remove_playlists(const bit_array & p_mask) = 0; //! Creates a new playlist. //! @param p_name Name of playlist to create; a UTF-8 encoded string. //! @param p_name_length Length limit of playlist name string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings. //! @param p_index Index at which to insert new playlist; set to infinite to put it at the end of playlist list. //! @returns Actual index of newly inserted playlist, infinite on failure (call from invalid context). virtual t_size create_playlist(const char * p_name,t_size p_name_length,t_size p_index) = 0; //! Reorders playlist list according to specified permutation. //! @returns True on success, false on failure (call from invalid context). virtual bool reorder(const t_size * p_order,t_size p_count) = 0; //! Retrieves number of items on specified playlist. virtual t_size playlist_get_item_count(t_size p_playlist) = 0; //! Enumerates contents of specified playlist. virtual void playlist_enum_items(t_size p_playlist,enum_items_callback & p_callback,const bit_array & p_mask) = 0; //! Retrieves index of focus item on specified playlist; returns infinite when no item has focus. virtual t_size playlist_get_focus_item(t_size p_playlist) = 0; //! Retrieves name of specified playlist. virtual bool playlist_get_name(t_size p_playlist,pfc::string_base & p_out) = 0; //! Reorders items in specified playlist according to specified permutation. virtual bool playlist_reorder_items(t_size p_playlist,const t_size * p_order,t_size p_count) = 0; //! Selects/deselects items on specified playlist. //! @param p_playlist Index of playlist to alter. //! @param p_affected Mask of items to alter. //! @param p_status Mask of selected/deselected state to apply to items specified by p_affected. virtual void playlist_set_selection(t_size p_playlist,const bit_array & p_affected,const bit_array & p_status) = 0; //! Removes specified items from specified playlist. Returns true on success or false on failure (playlist locked). virtual bool playlist_remove_items(t_size p_playlist,const bit_array & mask)=0; //! Replaces specified item on specified playlist. Returns true on success or false on failure (playlist locked). virtual bool playlist_replace_item(t_size p_playlist,t_size p_item,const metadb_handle_ptr & p_new_item) = 0; //! Sets index of focus item on specified playlist; use infinite to set no focus item. virtual void playlist_set_focus_item(t_size p_playlist,t_size p_item) = 0; //! Inserts new items into specified playlist, at specified position. virtual t_size playlist_insert_items(t_size p_playlist,t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection) = 0; //! Tells playlist renderers to make sure that specified item is visible. virtual void playlist_ensure_visible(t_size p_playlist,t_size p_item) = 0; //! Renames specified playlist. //! @param p_name New name of playlist; a UTF-8 encoded string. //! @param p_name_length Length limit of playlist name string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings. //! @returns True on success, false on failure (playlist locked). virtual bool playlist_rename(t_size p_index,const char * p_name,t_size p_name_length) = 0; //! Creates an undo restore point for specified playlist. virtual void playlist_undo_backup(t_size p_playlist) = 0; //! Reverts specified playlist to last undo restore point and generates a redo restore point. //! @returns True on success, false on failure (playlist locked or no restore point available). virtual bool playlist_undo_restore(t_size p_playlist) = 0; //! Reverts specified playlist to next redo restore point and generates an undo restore point. //! @returns True on success, false on failure (playlist locked or no restore point available). virtual bool playlist_redo_restore(t_size p_playlist) = 0; //! Returns whether an undo restore point is available for specified playlist. virtual bool playlist_is_undo_available(t_size p_playlist) = 0; //! Returns whether a redo restore point is available for specified playlist. virtual bool playlist_is_redo_available(t_size p_playlist) = 0; //! Renders information about specified playlist item, using specified titleformatting script parameters. //! @param p_playlist Index of playlist containing item being processed. //! @param p_item Index of item being processed in the playlist containing it. //! @param p_hook Titleformatting script hook to use; see titleformat_hook documentation for more info. Set to NULL when hook functionality is not needed. //! @param p_out String object receiving results. //! @param p_script Compiled titleformatting script to use; see titleformat_object cocumentation for more info. //! @param p_filter Text filter to use; see titleformat_text_filter documentation for more info. Set to NULL when text filter functionality is not needed. //! @param p_playback_info_level Level of playback related information requested. See playback_control::t_display_level documentation for more info. virtual void playlist_item_format_title(t_size p_playlist,t_size p_item,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<titleformat_object> & p_script,titleformat_text_filter * p_filter,playback_control::t_display_level p_playback_info_level)=0; //! Retrieves playlist position of currently playing item. //! @param p_playlist Receives index of playlist containing currently playing item on success. //! @param p_index Receives index of currently playing item in the playlist that contains it on success. //! @returns True on success, false on failure (not playing or currently played item has been removed from the playlist it was on when starting). virtual bool get_playing_item_location(t_size * p_playlist,t_size * p_index) = 0; //! Sorts specified playlist - entire playlist or selection only - by specified title formatting pattern, or randomizes the order. //! @param p_playlist Index of playlist to alter. //! @param p_pattern Title formatting pattern to sort by (an UTF-8 encoded null-termindated string). Set to NULL to randomize the order of items. //! @param p_sel_only Set to false to sort/randomize whole playlist, or to true to sort/randomize only selection on the playlist. //! @returns True on success, false on failure (playlist locked etc). virtual bool playlist_sort_by_format(t_size p_playlist,const char * p_pattern,bool p_sel_only) = 0; //! For internal use only; p_items must be sorted by metadb::path_compare; use file_operation_callback static methods instead of calling this directly. virtual void on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_items) = 0; //! For internal use only; p_from must be sorted by metadb::path_compare; use file_operation_callback static methods instead of calling this directly. virtual void on_files_moved_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) = 0; virtual bool playlist_lock_install(t_size p_playlist,const service_ptr_t<playlist_lock> & p_lock) = 0;//returns false when invalid playlist or already locked virtual bool playlist_lock_uninstall(t_size p_playlist,const service_ptr_t<playlist_lock> & p_lock) = 0; virtual bool playlist_lock_is_present(t_size p_playlist) = 0; virtual bool playlist_lock_query_name(t_size p_playlist,pfc::string_base & p_out) = 0; virtual bool playlist_lock_show_ui(t_size p_playlist) = 0; virtual t_uint32 playlist_lock_get_filter_mask(t_size p_playlist) = 0; //! Retrieves number of available playback order modes. virtual t_size playback_order_get_count() = 0; //! Retrieves name of specified playback order move. //! @param p_index Index of playback order mode to query, from 0 to playback_order_get_count() return value - 1. //! @returns Null-terminated UTF-8 encoded string containing name of the playback order mode. Returned pointer points to statically allocated string and can be safely stored without having to free it later. virtual const char * playback_order_get_name(t_size p_index) = 0; //! Retrieves GUID of specified playback order mode. Used for managing playback modes without relying on names. //! @param p_index Index of playback order mode to query, from 0 to playback_order_get_count() return value - 1. virtual GUID playback_order_get_guid(t_size p_index) = 0; //! Retrieves index of active playback order mode. virtual t_size playback_order_get_active() = 0; //! Sets index of active playback order mode. virtual void playback_order_set_active(t_size p_index) = 0; virtual void queue_remove_mask(bit_array const & p_mask) = 0; virtual void queue_add_item_playlist(t_size p_playlist,t_size p_item) = 0; virtual void queue_add_item(metadb_handle_ptr p_item) = 0; virtual t_size queue_get_count() = 0; virtual void queue_get_contents(pfc::list_base_t<t_playback_queue_item> & p_out) = 0; //! Returns index (0-based) on success, infinite on failure (item not in queue). virtual t_size queue_find_index(t_playback_queue_item const & p_item) = 0; //! Registers a playlist callback; registered object receives notifications about any modifications of any of loaded playlists. //! @param p_callback Callback interface to register. //! @param p_flags Flags indicating which callback methods are requested. See playlist_callback::flag_* constants for more info. The main purpose of flags parameter is working set optimization by not calling methods that do nothing. virtual void register_callback(class playlist_callback * p_callback,unsigned p_flags) = 0; //! Registers a playlist callback; registered object receives notifications about any modifications of active playlist. //! @param p_callback Callback interface to register. //! @param p_flags Flags indicating which callback methods are requested. See playlist_callback_single::flag_* constants for more info. The main purpose of flags parameter is working set optimization by not calling methods that do nothing. virtual void register_callback(class playlist_callback_single * p_callback,unsigned p_flags) = 0; //! Unregisters a playlist callback (playlist_callback version). virtual void unregister_callback(class playlist_callback * p_callback) = 0; //! Unregisters a playlist callback (playlist_callback_single version). virtual void unregister_callback(class playlist_callback_single * p_callback) = 0; //! Modifies flags indicating which calback methods are requested (playlist_callback version). virtual void modify_callback(class playlist_callback * p_callback,unsigned p_flags) = 0; //! Modifies flags indicating which calback methods are requested (playlist_callback_single version). virtual void modify_callback(class playlist_callback_single * p_callback,unsigned p_flags) = 0; //! Executes default doubleclick/enter action for specified item on specified playlist (starts playing the item unless overridden by a lock to do something else). virtual bool playlist_execute_default_action(t_size p_playlist,t_size p_item) = 0; //! Helper; removes all items from the playback queue. void queue_flush() {queue_remove_mask(bit_array_true());} //! Helper; returns whether there are items in the playback queue. bool queue_is_active() {return queue_get_count() > 0;} //! Helper; highlights currently playing item; returns true on success or false on failure (not playing or currently played item has been removed from playlist since playback started). bool highlight_playing_item(); //! Helper; removes single playlist of specified index. bool remove_playlist(t_size p_playlist); //! Helper; removes single playlist of specified index, and switches to another playlist when possible. bool remove_playlist_switch(t_size p_playlist); //! Helper; returns whether specified item on specified playlist is selected or not. bool playlist_is_item_selected(t_size p_playlist,t_size p_item); //! Helper; retrieves metadb_handle of specified playlist item. Returns true on success, false on failure (invalid parameters). bool playlist_get_item_handle(metadb_handle_ptr & p_out,t_size p_playlist,t_size p_item); //! Moves selected items up/down in the playlist by specified offset. //! @param p_playlist Index of playlist to alter. //! @param p_delta Offset to move items by. Set it to a negative valuye to move up, or to a positive value to move down. //! @returns True on success, false on failure (e.g. playlist locked). bool playlist_move_selection(t_size p_playlist,int p_delta); //! Retrieves selection map of specific playlist, using bit_array_var interface. void playlist_get_selection_mask(t_size p_playlist,bit_array_var & out); void playlist_get_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out,const bit_array & p_mask); void playlist_get_all_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out); void playlist_get_selected_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out); //! Clears contents of specified playlist (removes all items from it). void playlist_clear(t_size p_playlist); bool playlist_add_items(t_size playlist,const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection); void playlist_clear_selection(t_size p_playlist); void playlist_remove_selection(t_size p_playlist,bool p_crop = false); //retrieving status t_size activeplaylist_get_item_count(); void activeplaylist_enum_items(enum_items_callback & p_callback,const bit_array & p_mask); t_size activeplaylist_get_focus_item();//focus may be infinite if no item is focused bool activeplaylist_get_name(pfc::string_base & p_out); //modifying playlist bool activeplaylist_reorder_items(const t_size * order,t_size count); void activeplaylist_set_selection(const bit_array & affected,const bit_array & status); bool activeplaylist_remove_items(const bit_array & mask); bool activeplaylist_replace_item(t_size p_item,const metadb_handle_ptr & p_new_item); void activeplaylist_set_focus_item(t_size p_item); t_size activeplaylist_insert_items(t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection); void activeplaylist_ensure_visible(t_size p_item); bool activeplaylist_rename(const char * p_name,t_size p_name_len); void activeplaylist_undo_backup(); bool activeplaylist_undo_restore(); bool activeplaylist_redo_restore(); bool activeplaylist_is_item_selected(t_size p_item); bool activeplaylist_get_item_handle(metadb_handle_ptr & item,t_size p_item); void activeplaylist_move_selection(int p_delta); void activeplaylist_get_selection_mask(bit_array_var & out); void activeplaylist_get_items(pfc::list_base_t<metadb_handle_ptr> & out,const bit_array & p_mask); void activeplaylist_get_all_items(pfc::list_base_t<metadb_handle_ptr> & out); void activeplaylist_get_selected_items(pfc::list_base_t<metadb_handle_ptr> & out); void activeplaylist_clear(); bool activeplaylist_add_items(const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection); bool playlist_insert_items_filter(t_size p_playlist,t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select); bool activeplaylist_insert_items_filter(t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select); //! DEPRECATED (0.9.3) - use playlist_incoming_item_filter_v2::process_locations_async whenever possible bool playlist_insert_locations(t_size p_playlist,t_size p_base,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd); //! DEPRECATED (0.9.3) - use playlist_incoming_item_filter_v2::process_locations_async whenever possible bool activeplaylist_insert_locations(t_size p_base,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd); bool playlist_add_items_filter(t_size p_playlist,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select); bool activeplaylist_add_items_filter(const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select); bool playlist_add_locations(t_size p_playlist,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd); bool activeplaylist_add_locations(const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd); void reset_playing_playlist(); void activeplaylist_clear_selection(); void activeplaylist_remove_selection(bool p_crop = false); void activeplaylist_item_format_title(t_size p_item,titleformat_hook * p_hook,pfc::string_base & out,const service_ptr_t<titleformat_object> & p_script,titleformat_text_filter * p_filter,play_control::t_display_level p_playback_info_level); void playlist_set_selection_single(t_size p_playlist,t_size p_item,bool p_state); void activeplaylist_set_selection_single(t_size p_item,bool p_state); t_size playlist_get_selection_count(t_size p_playlist,t_size p_max); t_size activeplaylist_get_selection_count(t_size p_max); bool playlist_get_focus_item_handle(metadb_handle_ptr & p_item,t_size p_playlist); bool activeplaylist_get_focus_item_handle(metadb_handle_ptr & item); t_size find_playlist(const char * p_name,t_size p_name_length); t_size find_or_create_playlist(const char * p_name,t_size p_name_length); t_size create_playlist_autoname(t_size p_index = infinite); bool activeplaylist_sort_by_format(const char * spec,bool p_sel_only); t_uint32 activeplaylist_lock_get_filter_mask(); bool activeplaylist_is_undo_available(); bool activeplaylist_is_redo_available(); bool activeplaylist_execute_default_action(t_size p_item); void remove_items_from_all_playlists(const pfc::list_base_const_t<metadb_handle_ptr> & p_data); void active_playlist_fix(); bool get_all_items(pfc::list_base_t<metadb_handle_ptr> & out); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_manager); }; class NOVTABLE playlist_callback { public: virtual void FB2KAPI on_items_added(t_size p_playlist,t_size p_start, const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const bit_array & p_selection)=0;//inside any of these methods, you can call playlist APIs to get exact info about what happened (but only methods that read playlist state, not those that modify it) virtual void FB2KAPI on_items_reordered(t_size p_playlist,const t_size * p_order,t_size p_count)=0;//changes selection too; doesnt actually change set of items that are selected or item having focus, just changes their order virtual void FB2KAPI on_items_removing(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0;//called before actually removing them virtual void FB2KAPI on_items_removed(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0; virtual void FB2KAPI on_items_selection_change(t_size p_playlist,const bit_array & p_affected,const bit_array & p_state) = 0; virtual void FB2KAPI on_item_focus_change(t_size p_playlist,t_size p_from,t_size p_to)=0;//focus may be -1 when no item has focus; reminder: focus may also change on other callbacks virtual void FB2KAPI on_items_modified(t_size p_playlist,const bit_array & p_mask)=0; virtual void FB2KAPI on_items_modified_fromplayback(t_size p_playlist,const bit_array & p_mask,play_control::t_display_level p_level)=0; struct t_on_items_replaced_entry { t_size m_index; metadb_handle_ptr m_old,m_new; }; virtual void FB2KAPI on_items_replaced(t_size p_playlist,const bit_array & p_mask,const pfc::list_base_const_t<t_on_items_replaced_entry> & p_data)=0; virtual void FB2KAPI on_item_ensure_visible(t_size p_playlist,t_size p_idx)=0; virtual void FB2KAPI on_playlist_activate(t_size p_old,t_size p_new) = 0; virtual void FB2KAPI on_playlist_created(t_size p_index,const char * p_name,t_size p_name_len) = 0; virtual void FB2KAPI on_playlists_reorder(const t_size * p_order,t_size p_count) = 0; virtual void FB2KAPI on_playlists_removing(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) = 0; virtual void FB2KAPI on_playlists_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) = 0; virtual void FB2KAPI on_playlist_renamed(t_size p_index,const char * p_new_name,t_size p_new_name_len) = 0; virtual void FB2KAPI on_default_format_changed() = 0; virtual void FB2KAPI on_playback_order_changed(t_size p_new_index) = 0; virtual void FB2KAPI on_playlist_locked(t_size p_playlist,bool p_locked) = 0; enum { flag_on_items_added = 1 << 0, flag_on_items_reordered = 1 << 1, flag_on_items_removing = 1 << 2, flag_on_items_removed = 1 << 3, flag_on_items_selection_change = 1 << 4, flag_on_item_focus_change = 1 << 5, flag_on_items_modified = 1 << 6, flag_on_items_modified_fromplayback = 1 << 7, flag_on_items_replaced = 1 << 8, flag_on_item_ensure_visible = 1 << 9, flag_on_playlist_activate = 1 << 10, flag_on_playlist_created = 1 << 11, flag_on_playlists_reorder = 1 << 12, flag_on_playlists_removing = 1 << 13, flag_on_playlists_removed = 1 << 14, flag_on_playlist_renamed = 1 << 15, flag_on_default_format_changed = 1 << 16, flag_on_playback_order_changed = 1 << 17, flag_on_playlist_locked = 1 << 18, flag_all = ~0, }; protected: playlist_callback() {} ~playlist_callback() {} }; class NOVTABLE playlist_callback_static : public service_base, public playlist_callback { public: virtual unsigned get_flags() = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_callback_static); }; class NOVTABLE playlist_callback_single { public: virtual void FB2KAPI on_items_added(t_size start, const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const bit_array & p_selection)=0;//inside any of these methods, you can call playlist APIs to get exact info about what happened (but only methods that read playlist state, not those that modify it) virtual void FB2KAPI on_items_reordered(const t_size * order,t_size count)=0;//changes selection too; doesnt actually change set of items that are selected or item having focus, just changes their order virtual void FB2KAPI on_items_removing(const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0;//called before actually removing them virtual void FB2KAPI on_items_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0; virtual void FB2KAPI on_items_selection_change(const bit_array & p_affected,const bit_array & p_state) = 0; virtual void FB2KAPI on_item_focus_change(t_size p_from,t_size p_to)=0;//focus may be -1 when no item has focus; reminder: focus may also change on other callbacks virtual void FB2KAPI on_items_modified(const bit_array & p_mask)=0; virtual void FB2KAPI on_items_modified_fromplayback(const bit_array & p_mask,play_control::t_display_level p_level)=0; virtual void FB2KAPI on_items_replaced(const bit_array & p_mask,const pfc::list_base_const_t<playlist_callback::t_on_items_replaced_entry> & p_data)=0; virtual void FB2KAPI on_item_ensure_visible(t_size p_idx)=0; virtual void FB2KAPI on_playlist_switch() = 0; virtual void FB2KAPI on_playlist_renamed(const char * p_new_name,t_size p_new_name_len) = 0; virtual void FB2KAPI on_playlist_locked(bool p_locked) = 0; virtual void FB2KAPI on_default_format_changed() = 0; virtual void FB2KAPI on_playback_order_changed(t_size p_new_index) = 0; enum { flag_on_items_added = 1 << 0, flag_on_items_reordered = 1 << 1, flag_on_items_removing = 1 << 2, flag_on_items_removed = 1 << 3, flag_on_items_selection_change = 1 << 4, flag_on_item_focus_change = 1 << 5, flag_on_items_modified = 1 << 6, flag_on_items_modified_fromplayback = 1 << 7, flag_on_items_replaced = 1 << 8, flag_on_item_ensure_visible = 1 << 9, flag_on_playlist_switch = 1 << 10, flag_on_playlist_renamed = 1 << 11, flag_on_playlist_locked = 1 << 12, flag_on_default_format_changed = 1 << 13, flag_on_playback_order_changed = 1 << 14, flag_all = ~0, }; protected: playlist_callback_single() {} ~playlist_callback_single() {} }; class playlist_callback_single_static : public service_base, public playlist_callback_single { public: virtual unsigned get_flags() = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_callback_single_static); }; //! Class used for async processing of IDataObject. Content of IDataObject can be dumped into dropped_files_data without any time-consuming operations - won't block calling app when used inside drag&drop handler - and actual time-consuming processing (listing directories and reading infos) can be done later.\n //! In 0.9.3 and up, instead of going thru dropped_files_data, you can use playlist_incoming_item_filter_v2::process_dropped_files_async(). class NOVTABLE dropped_files_data { public: virtual void set_paths(pfc::string_list_const const & p_paths) = 0; virtual void set_handles(const pfc::list_base_const_t<metadb_handle_ptr> & p_handles) = 0; protected: dropped_files_data() {} ~dropped_files_data() {} }; class NOVTABLE playlist_incoming_item_filter : public service_base { public: virtual bool filter_items(const pfc::list_base_const_t<metadb_handle_ptr> & in,pfc::list_base_t<metadb_handle_ptr> & out) = 0;//sort / remove duplicates //! Deprecated; use playlist_incoming_item_filter_v2::process_locations_async() when possible.\n //! Converts one or more paths to a list of metadb_handles; displays a progress dialog.\n //! Note that this function creates modal dialog and does not return until the operation has completed. //! @returns True on success, false on user abort. virtual bool process_locations(const pfc::list_base_const_t<const char*> & p_urls,pfc::list_base_t<metadb_handle_ptr> & p_out,bool p_filter,const char * p_restrict_mask_override, const char * p_exclude_mask_override,HWND p_parentwnd) = 0; //! Deprecated; use playlist_incoming_item_filter_v2::process_dropped_files_async() when possible.\n //! Converts an IDataObject to a list of metadb_handles. //! Using this function is strongly disrecommended as it implies blocking the drag&drop source app (as well as our app).\n //! @returns True on success, false on user abort or unknown data format. virtual bool process_dropped_files(interface IDataObject * pDataObject,pfc::list_base_t<metadb_handle_ptr> & p_out,bool p_filter,HWND p_parentwnd) = 0; //! Checks whether IDataObject contains one of known data formats that can be translated to a list of metadb_handles. virtual bool process_dropped_files_check(interface IDataObject * pDataObject) = 0; //! Checks whether IDataObject contains our own private data format (drag&drop within the app etc). virtual bool process_dropped_files_check_if_native(interface IDataObject * pDataObject) = 0; //! Creates an IDataObject from specified metadb_handle list. virtual interface IDataObject * create_dataobject(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0; //! Checks whether IDataObject contains one of known data formats that can be translated to a list of metadb_handles.\n //! This function also returns drop effects to use (see: IDropTarget::DragEnter(), IDropTarget::DragOver() ). In certain cases, drag effects are necessary for drag&drop to work at all (such as dragging links from IE).\n virtual bool process_dropped_files_check_ex(interface IDataObject * pDataObject, DWORD * p_effect) = 0; //! Dumps IDataObject content to specified dropped_files_data object, without any time-consuming processing.\n //! Using this function instead of process_dropped_files() and processing dropped_files_data outside drop handler allows you to avoid blocking drop source app when processing large directories etc.\n //! Note: since 0.9.3, it is recommended to use playlist_incoming_item_filter_v2::process_dropped_files_async() instead. //! @returns True on success, false when IDataObject does not contain any of known data formats. virtual bool process_dropped_files_delayed(dropped_files_data & p_out,interface IDataObject * pDataObject) = 0; //helper bool process_location(const char * url,pfc::list_base_t<metadb_handle_ptr> & out,bool filter,const char * p_mask,const char * p_exclude,HWND p_parentwnd); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_incoming_item_filter); }; //! For use with playlist_incoming_item_filter_v2::process_locations_async(). class NOVTABLE process_locations_notify : public service_base { public: virtual void on_completion(const pfc::list_base_const_t<metadb_handle_ptr> & p_items) = 0; virtual void on_aborted() = 0; FB2K_MAKE_SERVICE_INTERFACE(process_locations_notify,service_base); }; typedef service_ptr_t<process_locations_notify> process_locations_notify_ptr; //! New (0.9.3) class NOVTABLE playlist_incoming_item_filter_v2 : public playlist_incoming_item_filter { public: enum { //! Set this to disable presorting (according to user settings) and duplicate removal in output list. Should be unset in most cases. op_flag_no_filter = 1 << 0, //! Set this flag to make the progress dialog not steal focus on creation. op_flag_background = 1 << 1, //! Set this flag to delay the progress dialog becoming visible, so it does not appear at all during short operations. Also implies op_flag_background effect. op_flag_delay_ui = 1 << 2, }; //! Converts one or more paths to a list of metadb_handles. The function returns immediately; specified callback object receives results when the operation has completed. //! @param p_urls List of paths to process. //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. //! @param p_restrict_mask_override Override of "restrict incoming items to" setting. Pass NULL to use the value from preferences. //! @param p_exclude_mask_override Override of "exclude file types" setting. Pass NULL to use value from preferences. //! @param p_parentwnd Parent window for spawned progress dialogs. //! @param p_notify Callback receiving notifications about success/abort of the operation as well as output item list. virtual void process_locations_async(const pfc::list_base_const_t<const char*> & p_urls,t_uint32 p_op_flags,const char * p_restrict_mask_override, const char * p_exclude_mask_override,HWND p_parentwnd,process_locations_notify_ptr p_notify) = 0; //! Converts an IDataObject to a list of metadb_handles. The function returns immediately; specified callback object receives results when the operation has completed. //! @param p_dataobject IDataObject to process. //! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation. //! @param p_parentwnd Parent window for spawned progress dialogs. //! @param p_notify Callback receiving notifications about success/abort of the operation as well as output item list. virtual void process_dropped_files_async(interface IDataObject * p_dataobject,t_uint32 p_op_flags,HWND p_parentwnd,process_locations_notify_ptr p_notify) = 0; FB2K_MAKE_SERVICE_INTERFACE(playlist_incoming_item_filter_v2,playlist_incoming_item_filter); }; //! Implementation of dropped_files_data. class dropped_files_data_impl : public dropped_files_data { public: dropped_files_data_impl() : m_is_paths(false) {} void set_paths(pfc::string_list_const const & p_paths) { m_is_paths = true; m_paths = p_paths; } void set_handles(const pfc::list_base_const_t<metadb_handle_ptr> & p_handles) { m_is_paths = false; m_handles = p_handles; } void to_handles_async(bool p_filter,HWND p_parentwnd,service_ptr_t<process_locations_notify> p_notify); //! @param p_op_flags Can be null, or one or more of playlist_incoming_item_filter_v2::op_flag_* enum values combined, altering behaviors of the operation. void to_handles_async_ex(t_uint32 p_op_flags,HWND p_parentwnd,service_ptr_t<process_locations_notify> p_notify); bool to_handles(pfc::list_base_t<metadb_handle_ptr> & p_out,bool p_filter,HWND p_parentwnd); private: pfc::string_list_impl m_paths; metadb_handle_list m_handles; bool m_is_paths; }; class NOVTABLE playback_queue_callback : public service_base { public: enum t_change_origin { changed_user_added, changed_user_removed, changed_playback_advance, }; virtual void on_changed(t_change_origin p_origin) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playback_queue_callback); }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/playlist_loader.cpp ================================================ #include "foobar2000.h" static void process_path_internal(const char * p_path,const service_ptr_t<file> & p_reader,playlist_loader_callback & callback,playlist_loader_callback::t_entry_type type,const t_filestats & p_stats); namespace { class archive_callback_impl : public archive_callback { public: archive_callback_impl(playlist_loader_callback & p_callback) : m_callback(p_callback) {} bool on_entry(archive * owner,const char * p_path,const t_filestats & p_stats,const service_ptr_t<file> & p_reader) { process_path_internal(p_path,p_reader,m_callback,playlist_loader_callback::entry_directory_enumerated,p_stats); return !m_callback.is_aborting(); } bool is_aborting() const {return m_callback.is_aborting();} abort_callback_event get_abort_event() const {return m_callback.get_abort_event();} private: playlist_loader_callback & m_callback; }; } void playlist_loader::g_load_playlist(const char * p_path,playlist_loader_callback & callback) { TRACK_CALL_TEXT("playlist_loader::g_load_playlist"); pfc::string8 filename; filename = file_path_canonical(p_path); service_enum_t<playlist_loader> e; service_ptr_t<playlist_loader> l; pfc::string_extension extension(filename); service_ptr_t<file> l_file; { bool /*have_content_type, */have_extension; // string8 content_type; // have_content_type = r->get_content_type(content_type); have_extension = extension.length()>0; if (e.first(l)) do { if ( // have_content_type && l->is_our_content_type(content_type) || (have_extension && !stricmp_utf8(l->get_extension(),extension))) { if (l_file.is_empty()) { filesystem::g_open_read(l_file,filename,callback); } TRACK_CODE("playlist_loader::open",l->open(filename,l_file,callback)); return;//success } } while(e.next(l)); } throw exception_io_unsupported_format(); } static void track_indexer__g_get_tracks_e(const char * p_path,const service_ptr_t<file> & p_reader,const t_filestats & p_stats,playlist_loader_callback::t_entry_type p_type,playlist_loader_callback & p_callback,bool & p_got_input) { if (p_reader.is_empty() && filesystem::g_is_remote_safe(p_path)) { metadb_handle_ptr handle; p_callback.handle_create(handle,make_playable_location(p_path,0)); p_got_input = true; p_callback.on_entry(handle,p_type,p_stats,true); } else { service_ptr_t<input_info_reader> instance; input_entry::g_open_for_info_read(instance,p_reader,p_path,p_callback); t_filestats stats = instance->get_file_stats(p_callback); t_uint32 subsong,subsong_count = instance->get_subsong_count(); for(subsong=0;subsong<subsong_count;subsong++) { p_callback.check_e(); metadb_handle_ptr handle; t_uint32 index = instance->get_subsong(subsong); p_callback.handle_create(handle,make_playable_location(p_path,index)); p_got_input = true; if (p_callback.want_info(handle,p_type,stats,true)) { file_info_impl info; instance->get_info(index,info,p_callback); p_callback.on_entry_info(handle,p_type,stats,info,true); } else { p_callback.on_entry(handle,p_type,stats,true); } } } } static void track_indexer__g_get_tracks_wrap(const char * p_path,const service_ptr_t<file> & p_reader,const t_filestats & p_stats,playlist_loader_callback::t_entry_type p_type,playlist_loader_callback & p_callback) { bool got_input = false; bool fail = false; try { track_indexer__g_get_tracks_e(p_path,p_reader,p_stats,p_type,p_callback,got_input); } catch(exception_aborted) { throw; } catch(exception_io_unsupported_format) { fail = true; } catch(std::exception const & e) { fail = true; console::formatter() << "could not enumerate tracks (" << e << ") on:\n" << file_path_display(p_path); } if (fail) { if (!got_input && !p_callback.is_aborting()) { if (p_type == playlist_loader_callback::entry_user_requested) { metadb_handle_ptr handle; p_callback.handle_create(handle,make_playable_location(p_path,0)); p_callback.on_entry(handle,p_type,p_stats,true); } } } } static void process_path_internal(const char * p_path,const service_ptr_t<file> & p_reader,playlist_loader_callback & p_callback,playlist_loader_callback::t_entry_type p_type,const t_filestats & p_stats) { //p_path must be canonical p_callback.check_e(); p_callback.on_progress(p_path); { if (p_reader.is_empty()) { directory_callback_impl directory_results(true); try { filesystem::g_list_directory(p_path,directory_results,p_callback); for(t_size n=0;n<directory_results.get_count();n++) { process_path_internal(directory_results.get_item(n),0,p_callback,playlist_loader_callback::entry_directory_enumerated,directory_results.get_item_stats(n)); } return; } catch(exception_aborted) {throw;} catch(...) { //do nothing, fall thru //fixme - catch only filesystem exceptions? } } bool found = false; { archive_callback_impl archive_results(p_callback); service_enum_t<filesystem> e; service_ptr_t<filesystem> f; while(e.next(f)) { p_callback.check_e(); service_ptr_t<archive> arch; if (f->service_query_t(arch)) { if (p_reader.is_valid()) p_reader->reopen(p_callback); try { TRACK_CODE("archive::archive_list",arch->archive_list(p_path,p_reader,archive_results,true)); return; } catch(exception_aborted) {throw;} catch(...) {} } } } } { service_ptr_t<link_resolver> ptr; if (link_resolver::g_find(ptr,p_path)) { if (p_reader.is_valid()) p_reader->reopen(p_callback); pfc::string8 temp; try { TRACK_CODE("link_resolver::resolve",ptr->resolve(p_reader,p_path,temp,p_callback)); track_indexer__g_get_tracks_wrap(temp,0,filestats_invalid,playlist_loader_callback::entry_from_playlist,p_callback); return;//success } catch(exception_aborted) {throw;} catch(...) {} } } track_indexer__g_get_tracks_wrap(p_path,p_reader,p_stats,p_type,p_callback); } void playlist_loader::g_process_path(const char * p_filename,playlist_loader_callback & callback,playlist_loader_callback::t_entry_type type) { TRACK_CALL_TEXT("playlist_loader::g_process_path"); file_path_canonical filename(p_filename); process_path_internal(filename,0,callback,type,filestats_invalid); } void playlist_loader::g_save_playlist(const char * p_filename,const pfc::list_base_const_t<metadb_handle_ptr> & data,abort_callback & p_abort) { TRACK_CALL_TEXT("playlist_loader::g_save_playlist"); pfc::string8 filename; filesystem::g_get_canonical_path(p_filename,filename); try { service_ptr_t<file> r; filesystem::g_open(r,filename,filesystem::open_mode_write_new,p_abort); pfc::string_extension ext(filename); service_enum_t<playlist_loader> e; service_ptr_t<playlist_loader> l; if (e.first(l)) do { if (l->can_write() && !stricmp_utf8(ext,l->get_extension())) { try { TRACK_CODE("playlist_loader::write",l->write(filename,r,data,p_abort)); return; } catch(exception_io_data) {} } } while(e.next(l)); throw exception_io_data(); } catch(...) { try {filesystem::g_remove(filename,p_abort);} catch(...) {} throw; } } bool playlist_loader::g_process_path_ex(const char * filename,playlist_loader_callback & callback,playlist_loader_callback::t_entry_type type) { try { g_load_playlist(filename,callback); return true; } catch(exception_io_unsupported_format) {//not a playlist format g_process_path(filename,callback,type); return false; } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/playlist_loader.h ================================================ #ifndef _PLAYLIST_LOADER_H_ #define _PLAYLIST_LOADER_H_ //! Callback interface receiving item locations from playlist loader. Also inherits abort_callback methods and can be passed to functions that require an abort_callback. \n //! See playlist_loader_callback_impl for a basic implementation of playlist_loader_callback. Typically, you call one of standard services such as playlist_incoming_item_filter instead of implementing this interface and calling playlist_loader methods directly. class NOVTABLE playlist_loader_callback : public abort_callback { public: //! Enumeration type representing origin of item passed to playlist_loader_callback. enum t_entry_type { //! User-requested (such as directly dropped to window or picked in openfiledialog). entry_user_requested, //! From directory content enumeration. entry_directory_enumerated, //! Referenced by playlist file. entry_from_playlist, }; //! Indicates specified path being processed; provided for updating GUI. Note that optimally GUI should not be updated every time this is called because that could introduce a bottleneck. virtual void on_progress(const char * p_path) = 0; //! Receives an item from one of playlist_loader functions. //! @param p_item Item location, in form of metadb_handle pointer. //! @param p_type Origin of this item - see t_entry_type for more info. //! @param p_stats File stats of this item; set to filestats_invalid if not available. //! @param p_fresh Fresh flag; indicates whether stats are directly from filesystem (true) or as stored earlier in a playlist file (false). virtual void on_entry(const metadb_handle_ptr & p_item,t_entry_type p_type,const t_filestats & p_stats,bool p_fresh) = 0; //! Queries whether file_info for specified item is requested. In typical scenario, if want_info() returns false, on_entry() will be called with same parameters; otherwise caller will attempt to read info from the item and call on_entry_info() with same parameters and file_info read from the item. //! @param p_item Item location, in form of metadb_handle pointer. //! @param p_type Origin of this item - see t_entry_type for more info. //! @param p_stats File stats of this item; set to filestats_invalid if not available. //! @param p_fresh Fresh flag; indicates whether stats are directly from filesystem (true) or as stored earlier in a playlist file (false). virtual bool want_info(const metadb_handle_ptr & p_item,t_entry_type p_type,const t_filestats & p_stats,bool p_fresh) = 0; //! Receives an item from one of playlist_loader functions; including file_info data. Except for file_info to be typically used as hint for metadb backend, behavior of this method is same as on_entry(). //! @param p_item Item location, in form of metadb_handle pointer. //! @param p_type Origin of this item - see t_entry_type for more info. //! @param p_stats File stats of this item; set to filestats_invalid if not available. //! @param p_info Information about the item, read from the file directly (if p_fresh is set to true) or from e.g. playlist file (if p_fresh is set to false). //! @param p_fresh Fresh flag; indicates whether stats are directly from filesystem (true) or as stored earlier in a playlist file (false). virtual void on_entry_info(const metadb_handle_ptr & p_item,t_entry_type p_type,const t_filestats & p_stats,const file_info & p_info,bool p_fresh) = 0; //! Same as metadb::handle_create(); provided here to avoid repeated metadb instantiation bottleneck since calling code will need this function often. virtual void handle_create(metadb_handle_ptr & p_out,const playable_location & p_location) = 0; protected: playlist_loader_callback() {} ~playlist_loader_callback() {} }; //! Basic implementation of playlist_loader_callback. class playlist_loader_callback_impl : public playlist_loader_callback { public: playlist_loader_callback_impl(abort_callback & p_abort) : m_abort(p_abort) {} bool is_aborting() const {return m_abort.is_aborting();} abort_callback_event get_abort_event() const {return m_abort.get_abort_event();} const metadb_handle_ptr & get_item(t_size idx) const {return m_data[idx];} const metadb_handle_ptr & operator[](t_size idx) const {return m_data[idx];} t_size get_count() const {return m_data.get_count();} const metadb_handle_list & get_data() const {return m_data;} void on_progress(const char * path) {} void on_entry(const metadb_handle_ptr & ptr,t_entry_type type,const t_filestats & p_stats,bool p_fresh) {m_data.add_item(ptr);} bool want_info(const metadb_handle_ptr & ptr,t_entry_type type,const t_filestats & p_stats,bool p_fresh) {return false;} void on_entry_info(const metadb_handle_ptr & ptr,t_entry_type type,const t_filestats & p_stats,const file_info & p_info,bool p_fresh) {m_data.add_item(ptr);} void handle_create(metadb_handle_ptr & p_out,const playable_location & p_location) {m_api->handle_create(p_out,p_location);} private: metadb_handle_list m_data; abort_callback & m_abort; static_api_ptr_t<metadb> m_api; }; //! Service handling playlist file operations. There are multiple implementations handling different playlist formats; you can add new implementations to allow new custom playlist file formats to be read or written.\n //! Also provides static helper functions for turning a filesystem path into a list of playable item locations. \n //! Note that you should typically call playlist_incoming_item_filter methods instead of calling playlist_loader methods directly to get a list of playable items from a specified path; this way you get a core-implemented threading and abortable dialog displaying progress.\n //! To register your own implementation, use playlist_loader_factory_t template.\n //! To call existing implementations, use static helper methods of playlist_loader class. class NOVTABLE playlist_loader : public service_base { public: //! Parses specified playlist file into list of playable locations. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown. //! @param p_path Path of playlist file to parse. Used for relative path handling purposes (p_file parameter is used for actual file access). //! @param p_file File interface to use for reading. Read/write pointer must be set to beginning by caller before calling. //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. virtual void open(const char * p_path, const service_ptr_t<file> & p_file,playlist_loader_callback & p_callback) = 0; //! Writes a playlist file containing specific item list to specified file. Will fail (pfc::exception_not_implemented) if specified playlist_loader is read-only (can_write() returns false). //! @param p_path Path of playlist file to write. Used for relative path handling purposes (p_file parameter is used for actual file access). //! @param p_file File interface to use for writing. Caller should ensure that the file is empty (0 bytes long) before calling. //! @param p_data List of items to save to playlist file. //! @param p_abort abort_callback object signaling user aborting the operation. Note that aborting a save playlist operation will most likely leave user with corrupted/incomplete file. virtual void write(const char * p_path, const service_ptr_t<file> & p_file,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,abort_callback & p_abort) = 0; //! Returns extension of file format handled by this playlist_loader implementation (a UTF-8 encoded null-terminated string). virtual const char * get_extension() = 0; //! Returns whether this playlist_loader implementation supports writing. If can_write() returns false, all write() calls will fail. virtual bool can_write() = 0; //! Returns whether specified content type is one of playlist types supported by this playlist_loaded implementation or not. //! @param p_content_type Content type to query, a UTF-8 encoded null-terminated string. virtual bool is_our_content_type(const char* p_content_type) = 0; //! Returns whether playlist format extension supported by this implementation should be listed on file types associations page. virtual bool is_associatable() = 0; //! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown. //! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string. //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. static void g_load_playlist(const char * p_path,playlist_loader_callback & p_callback); //! Saves specified list of locations into a playlist file. Throws exception_io or derivatives on failure, exception_aborted on abort. //! @param p_path Filesystem path to save playlist to, a UTF-8 encoded null-terminated string. //! @param p_data List of items to save to playlist file. //! @param p_abort abort_callback object signaling user aborting the operation. Note that aborting a save playlist operation will most likely leave user with corrupted/incomplete file. static void g_save_playlist(const char * p_path,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,abort_callback & p_abort); //! Processes specified path to generate list of playable items. Includes recursive directory/archive enumeration. \n //! Does not touch playlist files encountered - use g_process_path_ex() if specified path is possibly a playlist file; playlist files found inside directories or archives are ignored regardless.\n //! Warning: caller must handle exceptions which will occur in case of I/O failure. //! @param p_path Filesystem path to process; a UTF-8 encoded null-terminated string. //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. //! @param p_type Origin of p_path string. Reserved for internal use in recursive calls, should be left at default value; it controls various internal behaviors. static void g_process_path(const char * p_path,playlist_loader_callback & p_callback,playlist_loader_callback::t_entry_type p_type = playlist_loader_callback::entry_user_requested); //! Calls attempts to process specified path as a playlist; if that fails (i.e. not a playlist), calls g_process_path with same parameters. See g_process_path for parameter descriptions. \n //! Warning: caller must handle exceptions which will occur in case of I/O failure or playlist parsing failure. //! @returns True if specified path was processed as a playlist file, false otherwise (relevant in some scenarios where output is sorted after loading, playlist file contents should not be sorted). static bool g_process_path_ex(const char * p_path,playlist_loader_callback & p_callback,playlist_loader_callback::t_entry_type p_type = playlist_loader_callback::entry_user_requested); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_loader); }; template<typename t_myclass> class playlist_loader_factory_t : public service_factory_single_t<t_myclass> {}; #endif //_PLAYLIST_LOADER_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/playlist_lock.cpp ================================================ #include "foobar2000.h" ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/popup_message.cpp ================================================ #include "foobar2000.h" void popup_message::g_show_ex(const char * p_msg,unsigned p_msg_length,const char * p_title,unsigned p_title_length,t_icon p_icon) { static_api_ptr_t<popup_message>()->show_ex(p_msg,p_msg_length,p_title,p_title_length,p_icon); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/popup_message.h ================================================ #ifndef _POPUP_MESSAGE_H_ #define _POPUP_MESSAGE_H_ //! This interface allows you to show generic nonmodal noninteractive dialog with a text message. This should be used instead of MessageBox where possible.\n //! Usage: use popup_message::g_show / popup_message::g_show_ex static helpers, or static_api_ptr_t<popup_message>.\n //! Note that all strings are UTF-8. class NOVTABLE popup_message : public service_base { public: enum t_icon {icon_information, icon_error, icon_query}; //! Activates the popup dialog; returns immediately (the dialog remains visible). //! @param p_msg Message to show (UTF-8 encoded string). //! @param p_msg_length Length limit of message string to show, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings. //! @param p_title Title of dialog to show (UTF-8 encoded string). //! @param p_title_length Length limit of the title string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings. //! @param p_icon Icon of the dialog - can be set to icon_information, icon_error or icon_query. virtual void show_ex(const char * p_msg,unsigned p_msg_length,const char * p_title,unsigned p_title_length,t_icon p_icon = icon_information) = 0; //! Activates the popup dialog; returns immediately (the dialog remains visible); helper function built around show_ex(), takes null terminated strings with no length limit parameters. //! @param p_msg Message to show (UTF-8 encoded string). //! @param p_title Title of dialog to show (UTF-8 encoded string). //! @param p_icon Icon of the dialog - can be set to icon_information, icon_error or icon_query. inline void show(const char * p_msg,const char * p_title,t_icon p_icon = icon_information) {show_ex(p_msg,infinite,p_title,infinite,p_icon);} //! Static helper function instantiating the service and activating the message dialog. See show_ex() for description of parameters. static void g_show_ex(const char * p_msg,unsigned p_msg_length,const char * p_title,unsigned p_title_length,t_icon p_icon = icon_information); //! Static helper function instantiating the service and activating the message dialog. See show() for description of parameters. static inline void g_show(const char * p_msg,const char * p_title,t_icon p_icon = icon_information) {g_show_ex(p_msg,infinite,p_title,infinite,p_icon);} FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(popup_message); }; #endif //_POPUP_MESSAGE_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/preferences_page.cpp ================================================ #include "foobar2000.h" bool preferences_page::get_help_url(pfc::string_base & p_out) { p_out = "http://help.foobar2000.org/"; p_out += core_version_info::g_get_version_string(); p_out += "/preferences/"; p_out += (const char*) pfc::print_guid(get_guid()); p_out += "/"; p_out += get_name(); return true; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/preferences_page.h ================================================ #ifndef _FOOBAR2000_SDK_PREFERENCES_PAGE_H_ #define _FOOBAR2000_SDK_PREFERENCES_PAGE_H_ //! Implementing this service will generate a page in preferences dialog. Use preferences_page_factory_t template to register. class NOVTABLE preferences_page : public service_base { public: //! Creates preferences page dialog window. It is safe to assume that two dialog instances will never coexist. Caller is responsible for embedding it into preferences dialog itself. virtual HWND create(HWND p_parent) = 0; //! Retrieves name of the prefernces page to be displayed in preferences tree (static string). virtual const char * get_name() = 0; //! Retrieves GUID of the page. virtual GUID get_guid() = 0; //! Retrieves GUID of parent page/branch of this page. See preferences_page::guid_* constants for list of standard parent GUIDs. Can also be a GUID of another page or a branch (see: preferences_branch). virtual GUID get_parent_guid() = 0; //! Queries whether this page supports "reset page" feature. virtual bool reset_query() = 0; //! Activates "reset page" feature. It is safe to assume that the preferences page dialog does not exist at the point this is called (caller destroys it before calling reset and creates it again afterwards). virtual void reset() = 0; //! Retrieves help URL. Without overriding it, it will redirect to foobar2000 wiki. virtual bool get_help_url(pfc::string_base & p_out); static const GUID guid_root, guid_hidden, guid_tools,guid_core,guid_display,guid_playback,guid_visualisations,guid_input,guid_tag_writing,guid_media_library; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(preferences_page); }; class NOVTABLE preferences_page_v2 : public preferences_page { public: //! Allows custom sorting order of preferences pages. Return lower value for higher priority (lower resulting index in the list). When sorting priority of two items matches, alphabetic sorting is used. Return 0 to use default alphabetic sorting without overriding priority. virtual double get_sort_priority() = 0; FB2K_MAKE_SERVICE_INTERFACE(preferences_page_v2,preferences_page); }; template<class T> class preferences_page_factory_t : public service_factory_single_t<T> {}; //! Creates a preferences branch - an empty page that only serves as a parent for other pages and is hidden when no child pages exist. Instead of implementing this, simply use preferences_branch_factory class to declare a preferences branch with specified parameters. class NOVTABLE preferences_branch : public service_base { public: //! Retrieves name of the preferences branch. virtual const char * get_name() = 0; //! Retrieves GUID of the preferences branch. Use this GUID as parent GUID for pages/branches nested in this branch. virtual GUID get_guid() = 0; //! Retrieves GUID of parent page/branch of this branch. See preferences_page::guid_* constants for list of standard parent GUIDs. Can also be a GUID of another branch or a page. virtual GUID get_parent_guid() = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(preferences_branch); }; class preferences_branch_v2 : public preferences_branch { public: //! Allows custom sorting order of preferences pages. Return lower value for higher priority (lower resulting index in the list). When sorting priority of two items matches, alphabetic sorting is used. Return 0 to use default alphabetic sorting without overriding priority. virtual double get_sort_priority() = 0; FB2K_MAKE_SERVICE_INTERFACE(preferences_branch_v2,preferences_branch); }; class preferences_branch_impl : public preferences_branch_v2 { public: preferences_branch_impl(const GUID & p_guid,const GUID & p_parent,const char * p_name,double p_sort_priority = 0) : m_guid(p_guid), m_parent(p_parent), m_name(p_name), m_sort_priority(p_sort_priority) {} const char * get_name() {return m_name;} GUID get_guid() {return m_guid;} GUID get_parent_guid() {return m_parent;} double get_sort_priority() {return m_sort_priority;} private: const GUID m_guid,m_parent; const pfc::string8 m_name; const double m_sort_priority; }; typedef service_factory_single_t<preferences_branch_impl> __preferences_branch_factory; //! Instantiating this class declares a preferences branch with specified parameters.\n //! Usage: static preferences_branch_factory g_mybranch(mybranchguid,parentbranchguid,"name of my preferences branch goes here"); class preferences_branch_factory : public __preferences_branch_factory { public: preferences_branch_factory(const GUID & p_guid,const GUID & p_parent,const char * p_name,double p_sort_priority = 0) : __preferences_branch_factory(p_guid,p_parent,p_name,p_sort_priority) {} }; #endif //_FOOBAR2000_SDK_PREFERENCES_PAGE_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/replaygain.cpp ================================================ #include "foobar2000.h" void t_replaygain_config::reset() { m_source_mode = source_mode_none; m_processing_mode = processing_mode_none; m_preamp_without_rg = 0; m_preamp_with_rg = 0; } audio_sample t_replaygain_config::query_scale(const file_info & p_info) const { const audio_sample peak_margin = 1.0;//used to be 0.999 but it must not trigger on lossless audio_sample peak = peak_margin; audio_sample gain = 0; bool have_rg_gain = false, have_rg_peak = false; if (m_source_mode == source_mode_track || m_source_mode == source_mode_album) { replaygain_info info = p_info.get_replaygain(); float gain_select = replaygain_info::gain_invalid, peak_select = replaygain_info::peak_invalid; if (m_source_mode == source_mode_track) { if (info.is_track_gain_present()) {gain = info.m_track_gain; have_rg_gain = true; } else if (info.is_album_gain_present()) {gain = info.m_album_gain; have_rg_gain = true; } if (info.is_track_peak_present()) {peak = info.m_track_peak; have_rg_peak = true; } else if (info.is_album_peak_present()) {peak = info.m_album_peak; have_rg_peak = true; } } else { if (info.is_album_gain_present()) {gain = info.m_album_gain; have_rg_gain = true; } else if (info.is_track_gain_present()) {gain = info.m_track_gain; have_rg_gain = true; } if (info.is_album_peak_present()) {peak = info.m_album_peak; have_rg_peak = true; } else if (info.is_track_peak_present()) {peak = info.m_track_peak; have_rg_peak = true; } } } gain += have_rg_gain ? m_preamp_with_rg : m_preamp_without_rg; audio_sample scale = 1.0; if (m_processing_mode == processing_mode_gain || m_processing_mode == processing_mode_gain_and_peak) { scale *= audio_math::gain_to_scale(gain); } if (m_processing_mode == processing_mode_peak || m_processing_mode == processing_mode_gain_and_peak) { if (scale * peak > peak_margin) scale = (audio_sample)(peak_margin / peak); } return scale; } audio_sample t_replaygain_config::query_scale(const metadb_handle_ptr & p_object) const { audio_sample rv = 1.0; p_object->metadb_lock(); const file_info * info; if (p_object->get_info_async_locked(info)) rv = query_scale(*info); p_object->metadb_unlock(); return rv; } audio_sample replaygain_manager::core_settings_query_scale(const file_info & p_info) { t_replaygain_config temp; get_core_settings(temp); return temp.query_scale(p_info); } audio_sample replaygain_manager::core_settings_query_scale(const metadb_handle_ptr & info) { t_replaygain_config temp; get_core_settings(temp); return temp.query_scale(info); } //enum t_source_mode {source_mode_none,source_mode_track,source_mode_album}; //enum t_processing_mode {processing_mode_none,processing_mode_gain,processing_mode_gain_and_peak,processing_mode_peak}; namespace { class format_dbdelta { public: format_dbdelta(double p_val); operator const char*() const {return m_buffer;} private: pfc::string_fixed_t<128> m_buffer; }; static const char * querysign(int val) { return val<0 ? "-" : val>0 ? "+" : "\xc2\xb1"; } format_dbdelta::format_dbdelta(double p_val) { int val = (int)(p_val * 10); m_buffer << querysign(val) << (abs(val)/10) << "." << (abs(val)%10) << "dB"; } } void t_replaygain_config::format_name(pfc::string_base & p_out) const { switch(m_processing_mode) { case processing_mode_none: p_out = "None."; break; case processing_mode_gain: switch(m_source_mode) { case source_mode_none: if (m_preamp_without_rg == 0) p_out = "None."; else p_out = pfc::string_formatter() << "Preamp : " << format_dbdelta(m_preamp_without_rg); break; case source_mode_track: { pfc::string_formatter fmt; fmt << "Apply track gain"; if (m_preamp_without_rg != 0 || m_preamp_with_rg != 0) fmt << ", with preamp"; fmt << "."; p_out = fmt; } break; case source_mode_album: { pfc::string_formatter fmt; fmt << "Apply album gain"; if (m_preamp_without_rg != 0 || m_preamp_with_rg != 0) fmt << ", with preamp"; fmt << "."; p_out = fmt; } break; }; break; case processing_mode_gain_and_peak: switch(m_source_mode) { case source_mode_none: if (m_preamp_without_rg >= 0) p_out = "None."; else p_out = pfc::string_formatter() << "Preamp : " << format_dbdelta(m_preamp_without_rg); break; case source_mode_track: { pfc::string_formatter fmt; fmt << "Apply track gain"; if (m_preamp_without_rg != 0 || m_preamp_with_rg != 0) fmt << ", with preamp"; fmt << ", prevent clipping according to track peak."; p_out = fmt; } break; case source_mode_album: { pfc::string_formatter fmt; fmt << "Apply album gain"; if (m_preamp_without_rg != 0 || m_preamp_with_rg != 0) fmt << ", with preamp"; fmt << ", prevent clipping according to album peak."; p_out = fmt; } break; }; break; case processing_mode_peak: switch(m_source_mode) { case source_mode_none: p_out = "None."; break; case source_mode_track: p_out = "Prevent clipping according to track peak."; break; case source_mode_album: p_out = "Prevent clipping according to album peak."; break; }; break; } } bool t_replaygain_config::is_active() const { switch(m_processing_mode) { case processing_mode_none: return false; case processing_mode_gain: switch(m_source_mode) { case source_mode_none: return m_preamp_without_rg != 0; case source_mode_track: return true; case source_mode_album: return true; }; return false; case processing_mode_gain_and_peak: switch(m_source_mode) { case source_mode_none: return m_preamp_without_rg < 0; case source_mode_track: return true; case source_mode_album: return true; }; return false; case processing_mode_peak: switch(m_source_mode) { case source_mode_none: return false; case source_mode_track: return true; case source_mode_album: return true; }; return false; default: return false; } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/replaygain.h ================================================ #ifndef _FOOBAR2000_SDK_REPLAYGAIN_H_ #define _FOOBAR2000_SDK_REPLAYGAIN_H_ //! Structure storing ReplayGain configuration: album/track source data modes, gain/peak processing modes and preamp values. struct t_replaygain_config { enum t_source_mode {source_mode_none,source_mode_track,source_mode_album}; enum t_processing_mode {processing_mode_none,processing_mode_gain,processing_mode_gain_and_peak,processing_mode_peak}; t_replaygain_config() {reset();} t_replaygain_config(const t_replaygain_config & p_source) {*this = p_source;} t_replaygain_config(t_source_mode p_source_mode,t_processing_mode p_processing_mode,float p_preamp_without_rg, float p_preamp_with_rg) : m_source_mode(p_source_mode), m_processing_mode(p_processing_mode), m_preamp_without_rg(p_preamp_without_rg), m_preamp_with_rg(p_preamp_with_rg) {} t_source_mode m_source_mode; t_processing_mode m_processing_mode; float m_preamp_without_rg, m_preamp_with_rg;//preamp values in dB void reset(); audio_sample query_scale(const file_info & info) const; audio_sample query_scale(const metadb_handle_ptr & info) const; void format_name(pfc::string_base & p_out) const; bool is_active() const; }; //! Core service providing methods to retrieve/alter playback ReplayGain settings, as well as use ReplayGain configuration dialog. class NOVTABLE replaygain_manager : public service_base { public: //! Retrieves playback ReplayGain settings. virtual void get_core_settings(t_replaygain_config & p_out) = 0; //! Creates embedded version of ReplayGain settings dialog. Note that embedded dialog sends WM_COMMAND with id/BN_CLICKED to parent window when user makes changes to settings. virtual HWND configure_embedded(const t_replaygain_config & p_initdata,HWND p_parent,unsigned p_id,bool p_from_modal) = 0; //! Retrieves settings from embedded version of ReplayGain settings dialog. virtual void configure_embedded_retrieve(HWND wnd,t_replaygain_config & p_data) = 0; //! Shows popup/modal version of ReplayGain settings dialog. Returns true when user changed the settings, false when user cancelled the operation. Title parameter can be null to use default one. virtual bool configure_popup(t_replaygain_config & p_data,HWND p_parent,const char * p_title) = 0; //! Alters playback ReplayGain settings. virtual void set_core_settings(const t_replaygain_config & p_config) = 0; //! Helper; queries scale value for specified item according to core playback settings. audio_sample core_settings_query_scale(const file_info & p_info); //! Helper; queries scale value for specified item according to core playback settings. audio_sample core_settings_query_scale(const metadb_handle_ptr & info); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(replaygain_manager); }; #endif //_FOOBAR2000_SDK_REPLAYGAIN_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/replaygain_info.cpp ================================================ #include "foobar2000.h" bool replaygain_info::g_format_gain(float p_value,char p_buffer[text_buffer_size]) { fpu_control_roundnearest bah; if (p_value == gain_invalid) { p_buffer[0] = 0; return false; } else { pfc::float_to_string(p_buffer,text_buffer_size - 4,p_value,2,true); strcat(p_buffer," dB"); return true; } } bool replaygain_info::g_format_peak(float p_value,char p_buffer[text_buffer_size]) { fpu_control_roundnearest bah; if (p_value == peak_invalid) { p_buffer[0] = 0; return false; } else { pfc::float_to_string(p_buffer,text_buffer_size,p_value,6,false); return true; } } void replaygain_info::reset() { m_album_gain = gain_invalid; m_track_gain = gain_invalid; m_album_peak = peak_invalid; m_track_peak = peak_invalid; } static const char meta_album_gain[] = "replaygain_album_gain", meta_album_peak[] = "replaygain_album_peak", meta_track_gain[] = "replaygain_track_gain", meta_track_peak[] = "replaygain_track_peak"; bool replaygain_info::g_is_meta_replaygain(const char * p_name,t_size p_name_len) { return stricmp_utf8_ex(p_name,p_name_len,meta_album_gain,infinite) == 0 || stricmp_utf8_ex(p_name,p_name_len,meta_album_peak,infinite) == 0 || stricmp_utf8_ex(p_name,p_name_len,meta_track_gain,infinite) == 0 || stricmp_utf8_ex(p_name,p_name_len,meta_track_peak,infinite) == 0; } bool replaygain_info::set_from_meta_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len) { fpu_control_roundnearest bah; if (stricmp_utf8_ex(p_name,p_name_len,meta_album_gain,infinite) == 0) { m_album_gain = (float)pfc::string_to_float(p_value,p_value_len); return true; } else if (stricmp_utf8_ex(p_name,p_name_len,meta_album_peak,infinite) == 0) { m_album_peak = (float)pfc::string_to_float(p_value,p_value_len); if (m_album_peak < 0) m_album_peak = 0; return true; } else if (stricmp_utf8_ex(p_name,p_name_len,meta_track_gain,infinite) == 0) { m_track_gain = (float)pfc::string_to_float(p_value,p_value_len); return true; } else if (stricmp_utf8_ex(p_name,p_name_len,meta_track_peak,infinite) == 0) { m_track_peak = (float)pfc::string_to_float(p_value,p_value_len); if (m_track_peak < 0) m_track_peak = 0; return true; } else return false; } t_size replaygain_info::get_value_count() { t_size ret = 0; if (is_album_gain_present()) ret++; if (is_album_peak_present()) ret++; if (is_track_gain_present()) ret++; if (is_track_peak_present()) ret++; return ret; } void replaygain_info::set_album_gain_text(const char * p_text,t_size p_text_len) { fpu_control_roundnearest bah; if (p_text != 0 && p_text_len > 0 && *p_text != 0) m_album_gain = (float)pfc::string_to_float(p_text,p_text_len); else remove_album_gain(); } void replaygain_info::set_track_gain_text(const char * p_text,t_size p_text_len) { fpu_control_roundnearest bah; if (p_text != 0 && p_text_len > 0 && *p_text != 0) m_track_gain = (float)pfc::string_to_float(p_text,p_text_len); else remove_track_gain(); } void replaygain_info::set_album_peak_text(const char * p_text,t_size p_text_len) { fpu_control_roundnearest bah; if (p_text != 0 && p_text_len > 0 && *p_text != 0) m_album_peak = (float)pfc::string_to_float(p_text,p_text_len); else remove_album_peak(); } void replaygain_info::set_track_peak_text(const char * p_text,t_size p_text_len) { fpu_control_roundnearest bah; if (p_text != 0 && p_text_len > 0 && *p_text != 0) m_track_peak = (float)pfc::string_to_float(p_text,p_text_len); else remove_track_peak(); } replaygain_info replaygain_info::g_merge(replaygain_info r1,replaygain_info r2) { replaygain_info ret = r1; if (!ret.is_album_gain_present()) ret.m_album_gain = r2.m_album_gain; if (!ret.is_album_peak_present()) ret.m_album_peak = r2.m_album_peak; if (!ret.is_track_gain_present()) ret.m_track_gain = r2.m_track_gain; if (!ret.is_track_peak_present()) ret.m_track_peak = r2.m_track_peak; return ret; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/resampler.h ================================================ class NOVTABLE resampler_entry : public dsp_entry { public: virtual bool is_conversion_supported(unsigned p_srate_from,unsigned p_srate_to) = 0; virtual bool create_preset(dsp_preset & p_out,unsigned p_target_srate,float p_qualityscale) = 0;//p_qualityscale is 0...1 virtual float get_priority() = 0;//value is 0...1, where high-quality (SSRC etc) has 1 static bool g_get_interface(service_ptr_t<resampler_entry> & p_out,unsigned p_srate_from,unsigned p_srate_to); static bool g_create(service_ptr_t<dsp> & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale); static bool g_create_preset(dsp_preset & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale); FB2K_MAKE_SERVICE_INTERFACE(resampler_entry,dsp_entry); }; template<typename T> class resampler_entry_impl_t : public dsp_entry_impl_t<T,resampler_entry> { public: bool is_conversion_supported(unsigned p_srate_from,unsigned p_srate_to) {return T::g_is_conversion_supported(p_srate_from,p_srate_to);} bool create_preset(dsp_preset & p_out,unsigned p_target_srate,float p_qualityscale) {return T::g_create_preset(p_out,p_target_srate,p_qualityscale);} float get_priority() {return T::g_get_priority();} }; template<typename T> class resampler_factory_t : public service_factory_single_t<resampler_entry_impl_t<T> > {}; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/service.cpp ================================================ #include "foobar2000.h" #include "component.h" foobar2000_api * g_api; service_class_ref service_factory_base::enum_find_class(const GUID & p_guid) { PFC_ASSERT(core_api::are_services_available() && g_api); return g_api->service_enum_find_class(p_guid); } bool service_factory_base::enum_create(service_ptr_t<service_base> & p_out,service_class_ref p_class,t_size p_index) { PFC_ASSERT(core_api::are_services_available() && g_api); return g_api->service_enum_create(p_out,p_class,p_index); } t_size service_factory_base::enum_get_count(service_class_ref p_class) { PFC_ASSERT(core_api::are_services_available() && g_api); return g_api->service_enum_get_count(p_class); } service_factory_base * service_factory_base::__internal__list = NULL; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/service.h ================================================ #ifndef _SERVICE_H_ #define _SERVICE_H_ typedef const void* service_class_ref; PFC_DECLARE_EXCEPTION(exception_service_not_found,pfc::exception,"Service not found"); PFC_DECLARE_EXCEPTION(exception_service_extension_not_found,pfc::exception,"Service extension not found"); PFC_DECLARE_EXCEPTION(exception_service_duplicated,pfc::exception,"Service duplicated"); #ifdef _MSC_VER #define FOOGUIDDECL __declspec(selectany) #else #define FOOGUIDDECL #endif #define DECLARE_GUID(NAME,A,S,D,F,G,H,J,K,L,Z,X) FOOGUIDDECL const GUID NAME = {A,S,D,{F,G,H,J,K,L,Z,X}}; #define DECLARE_CLASS_GUID(NAME,A,S,D,F,G,H,J,K,L,Z,X) FOOGUIDDECL const GUID NAME::class_guid = {A,S,D,{F,G,H,J,K,L,Z,X}}; //! Special hack to ensure errors when someone tries to ->service_add_ref()/->service_release() on a service_ptr_t template<typename T> class service_obscure_refcounting : public T { private: int service_add_ref() throw(); int service_release() throw(); }; //! Converts a service interface pointer to a pointer that obscures service counter functionality. template<typename T> static inline service_obscure_refcounting<T>* service_obscure_refcounting_cast(T * p_source) throw() {return static_cast<service_obscure_refcounting<T>*>(p_source);} //! Multiple inheritance SNAFU fix. In some cases, old service_release_safe method of service_base would not get a NULL this pointer when called on a NULL pointer. template<typename T> static void service_release_safe(T * p_ptr) throw() { if (p_ptr != NULL) p_ptr->service_release(); } //! Multiple inheritance SNAFU fix. In some cases, old service_add_ref_safe method of service_base would not get a NULL this pointer when called on a NULL pointer. template<typename T> static void service_add_ref_safe(T * p_ptr) throw() { if (p_ptr != NULL) p_ptr->service_add_ref(); } //! Autopointer class to be used with all services. Manages reference counter calls behind-the-scenes. template<typename T> class service_ptr_t { private: typedef service_ptr_t<T> t_self; public: inline service_ptr_t() throw() : m_ptr(NULL) {} inline service_ptr_t(T* p_ptr) throw() : m_ptr(NULL) {copy(p_ptr);} inline service_ptr_t(const t_self & p_source) throw() : m_ptr(NULL) {copy(p_source);} template<typename t_source> inline service_ptr_t(t_source * p_ptr) throw() : m_ptr(NULL) {copy(p_ptr);} template<typename t_source> inline service_ptr_t(const service_ptr_t<t_source> & p_source) throw() : m_ptr(NULL) {copy(p_source);} inline ~service_ptr_t() throw() {service_release_safe(m_ptr);} template<typename t_source> void copy(t_source * p_ptr) throw() { service_release_safe(m_ptr); m_ptr = pfc::safe_ptr_cast<T>(p_ptr); service_add_ref_safe(m_ptr); } template<typename t_source> inline void copy(const service_ptr_t<t_source> & p_source) throw() {copy(p_source.get_ptr());} inline const t_self & operator=(const t_self & p_source) throw() {copy(p_source); return *this;} inline const t_self & operator=(T * p_ptr) throw() {copy(p_ptr); return *this;} template<typename t_source> inline t_self & operator=(const service_ptr_t<t_source> & p_source) throw() {copy(p_source); return *this;} template<typename t_source> inline t_self & operator=(t_source * p_ptr) throw() {copy(p_ptr); return *this;} inline void release() throw() { service_release_safe(m_ptr); m_ptr = NULL; } inline service_obscure_refcounting<T>* operator->() const throw() {PFC_ASSERT(m_ptr != NULL);return service_obscure_refcounting_cast(m_ptr);} inline T* get_ptr() const throw() {return m_ptr;} inline bool is_valid() const throw() {return m_ptr != NULL;} inline bool is_empty() const throw() {return m_ptr == NULL;} inline bool operator==(const t_self & p_item) const throw() {return m_ptr == p_item.get_ptr();} inline bool operator!=(const t_self & p_item) const throw() {return m_ptr != p_item.get_ptr();} inline bool operator>(const t_self & p_item) const throw() {return m_ptr > p_item.get_ptr();} inline bool operator<(const t_self & p_item) const throw() {return m_ptr < p_item.get_ptr();} template<typename t_other> inline t_self & operator<<(service_ptr_t<t_other> & p_source) throw() {__unsafe_set(p_source.__unsafe_detach());return *this;} template<typename t_other> inline t_self & operator>>(service_ptr_t<t_other> & p_dest) throw() {p_dest.__unsafe_set(__unsafe_detach());return *this;} inline T* __unsafe_duplicate() const throw() {//should not be used ! temporary ! service_add_ref_safe(m_ptr); return m_ptr; } inline T* __unsafe_detach() throw() {//should not be used ! temporary ! return pfc::replace_null_t(m_ptr); } template<typename t_source> inline void __unsafe_set(t_source * p_ptr) throw() {//should not be used ! temporary ! service_release_safe(m_ptr); m_ptr = pfc::safe_ptr_cast<T>(p_ptr); } private: T* m_ptr; }; namespace pfc { template<typename T> class traits_t<service_ptr_t<T> > : public traits_default { public: enum { realloc_safe = true, constructor_may_fail = false}; }; } template<typename T, template<typename> class t_alloc = pfc::alloc_fast> class service_list_t : public pfc::list_t<service_ptr_t<T>, t_alloc > { }; //! Helper macro for use when defining a service class. Generates standard features of a service, without ability to register using service_factory / enumerate using service_enum_t. \n //! This is used for declaring services that are meant to be instantiated by means other than service_enum_t (or non-entrypoint services), or extensions of services (including extension of entrypoint services). \n //! Sample non-entrypoint declaration: class myclass : public service_base {...; FB2K_MAKE_SERVICE_INTERFACE(myclass, service_base); }; \n //! Sample extension declaration: class myclass : public myotherclass {...; FB2K_MAKE_SERVICE_INTERFACE(myclass, myotherclass); }; \n //! This macro is intended for use ONLY WITH INTERFACE CLASSES, not with implementation classes. #define FB2K_MAKE_SERVICE_INTERFACE(THISCLASS,PARENTCLASS) \ public: \ typedef THISCLASS t_interface; \ typedef PARENTCLASS t_interface_parent; \ \ static const GUID class_guid; \ \ virtual bool service_query(service_ptr_t<service_base> & p_out,const GUID & p_guid) { \ if (p_guid == class_guid) {p_out = this; return true;} \ else return PARENTCLASS::service_query(p_out,p_guid); \ } \ protected: \ THISCLASS() {} \ ~THISCLASS() {} \ private: \ const THISCLASS & operator=(const THISCLASS &) {throw pfc::exception_not_implemented();} \ THISCLASS(const THISCLASS &) {throw pfc::exception_not_implemented();} \ private: \ void __private__service_declaration_selftest() { \ pfc::assert_same_type<PARENTCLASS,PARENTCLASS::t_interface>(); /*parentclass must be an interface*/ \ __validate_service_class_helper<THISCLASS>(); /*service_base must be reachable by walking t_interface_parent*/ \ pfc::safe_cast<service_base*>(this); /*this class must derive from service_base, directly or indirectly, and be implictly castable to it*/ \ } //! Helper macro for use when defining an entrypoint service class. Generates standard features of a service, including ability to register using service_factory and enumerate using service_enum. \n //! Sample declaration: class myclass : public service_base {...; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(myclass); }; \n //! Note that entrypoint service classes must directly derive from service_base, and not from another service class. //! This macro is intended for use ONLY WITH INTERFACE CLASSES, not with implementation classes. #define FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(THISCLASS) \ public: \ typedef THISCLASS t_interface_entrypoint; \ FB2K_MAKE_SERVICE_INTERFACE(THISCLASS,service_base) #define FB2K_DECLARE_SERVICE_BEGIN(THISCLASS,BASECLASS) \ class NOVTABLE THISCLASS : public BASECLASS { \ FB2K_MAKE_SERVICE_INTERFACE(THISCLASS,BASECLASS); \ public: #define FB2K_DECLARE_SERVICE_END() \ }; #define FB2K_DECLARE_SERVICE_ENTRYPOINT_BEGIN(THISCLASS) \ class NOVTABLE THISCLASS : public service_base { \ FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(THISCLASS) \ public: //! Base class for all service classes.\n //! Provides interfaces for reference counter and querying for different interfaces supported by the object.\n class NOVTABLE service_base { public: //! Decrements reference count; deletes the object if reference count reaches zero. This is normally not called directly but managed by service_ptr_t<> template. //! @returns New reference count. For debug purposes only, in certain conditions return values may be unreliable. virtual int service_release() throw() = 0; //! Increments reference count. This is normally not called directly but managed by service_ptr_t<> template. //! @returns New reference count. For debug purposes only, in certain conditions return values may be unreliable. virtual int service_add_ref() throw() = 0; //! Queries whether the object supports specific interface and retrieves a pointer to that interface. This is normally not called directly but managed by service_query_t<> function template. //! Typical implementation checks the parameter against GUIDs of interfaces supported by this object, if the GUID is one of supported interfaces, p_out is set to service_base pointer that can be static_cast<>'ed to queried interface and the method returns true; otherwise the method returns false. virtual bool service_query(service_ptr_t<service_base> & p_out,const GUID & p_guid) {return false;} //! Queries whether the object supports specific interface and retrieves a pointer to that interface. //! @param p_out Receives pointer to queried interface on success. //! returns true on success, false on failure (interface not supported by the object). template<class T> bool service_query_t(service_ptr_t<T> & p_out) { pfc::assert_same_type<T,T::t_interface>(); service_ptr_t<service_base> temp; if (!service_query(temp,T::class_guid)) return false; p_out.__unsafe_set(static_cast<T*>(temp.__unsafe_detach())); return true; } typedef service_base t_interface; protected: service_base() {} ~service_base() {} private: service_base(const service_base&) {throw pfc::exception_not_implemented();} const service_base & operator=(const service_base&) {throw pfc::exception_not_implemented();} }; template<typename T> static void __validate_service_class_helper() { __validate_service_class_helper<T::t_interface_parent>(); } template<> static void __validate_service_class_helper<service_base>() {} #include "service_impl.h" class NOVTABLE service_factory_base { protected: inline service_factory_base(const GUID & p_guid) : m_guid(p_guid) {PFC_ASSERT(!core_api::are_services_available());__internal__next=__internal__list;__internal__list=this;} inline ~service_factory_base() {PFC_ASSERT(!core_api::are_services_available());} public: inline const GUID & get_class_guid() const {return m_guid;} static service_class_ref enum_find_class(const GUID & p_guid); static bool enum_create(service_ptr_t<service_base> & p_out,service_class_ref p_class,t_size p_index); static t_size enum_get_count(service_class_ref p_class); inline static bool is_service_present(const GUID & g) {return enum_get_count(enum_find_class(g))>0;} //! Throws std::bad_alloc or another exception on failure. virtual void instance_create(service_ptr_t<service_base> & p_out) = 0; //! FOR INTERNAL USE ONLY static service_factory_base *__internal__list; //! FOR INTERNAL USE ONLY service_factory_base * __internal__next; private: const GUID & m_guid; }; template<typename B> class service_factory_base_t : public service_factory_base { public: service_factory_base_t() : service_factory_base(B::class_guid) { pfc::assert_same_type<B,B::t_interface_entrypoint>(); } }; template<class T> static bool service_enum_create_t(service_ptr_t<T> & p_out,t_size p_index) { pfc::assert_same_type<T,T::t_interface_entrypoint>(); service_ptr_t<service_base> ptr; if (service_factory_base::enum_create(ptr,service_factory_base::enum_find_class(T::class_guid),p_index)) { p_out = static_cast<T*>(ptr.get_ptr()); return true; } else { p_out.release(); return false; } } template<typename T> class service_class_helper_t { public: service_class_helper_t() : m_class(service_factory_base::enum_find_class(T::class_guid)) { pfc::assert_same_type<T,T::t_interface_entrypoint>(); } t_size get_count() const { return service_factory_base::enum_get_count(m_class); } bool create(service_ptr_t<T> & p_out,t_size p_index) const { service_ptr_t<service_base> temp; if (!service_factory_base::enum_create(temp,m_class,p_index)) return false; p_out.__unsafe_set(static_cast<T*>(temp.__unsafe_detach())); return true; } service_ptr_t<T> create(t_size p_index) const { service_ptr_t<T> temp; if (!create(temp,p_index)) throw pfc::exception_bug_check(); return temp; } private: service_class_ref m_class; }; template<typename T> static void standard_api_create_t(service_ptr_t<T> & p_out) { if (pfc::is_same_type<T,T::t_interface_entrypoint>::value) { service_class_helper_t<T::t_interface_entrypoint> helper; switch(helper.get_count()) { case 0: throw exception_service_not_found(); case 1: if (!helper.create(reinterpret_cast<service_ptr_t<T::t_interface_entrypoint>&>(p_out),0)) throw pfc::exception_bug_check(); break; default: throw exception_service_duplicated(); } } else { service_ptr_t<T::t_interface_entrypoint> temp; standard_api_create_t(temp); if (!temp->service_query_t(p_out)) throw exception_service_extension_not_found(); } } template<typename T> static service_ptr_t<T> standard_api_create_t() { service_ptr_t<T> temp; standard_api_create_t(temp); return temp; } //! Helper template used to easily access core services. \n //! Usage: static_api_ptr_t<myclass> api; api->dosomething(); //! Can be used at any point of code, WITH EXCEPTION of static objects that are initialized during DLL load before service system is initialized; such as static static_api_ptr_t objects or having static_api_ptr_t as members of statically created objects. //! Throws exception_service_not_found if service could not be reached (which can be ignored for core APIs that are always present unless there is some kind of bug in the code). template<typename t_interface> class static_api_ptr_t { public: static_api_ptr_t() { standard_api_create_t(m_ptr); } service_obscure_refcounting<t_interface>* operator->() const {return service_obscure_refcounting_cast(m_ptr.get_ptr());} t_interface* get_ptr() const {return m_ptr.get_ptr();} private: service_ptr_t<t_interface> m_ptr; }; //! Helper; simulates array with instance of each available implementation of given service class. template<typename T> class service_instance_array_t { public: typedef service_ptr_t<T> t_ptr; service_instance_array_t() { service_class_helper_t<T> helper; const t_size count = helper.get_count(); m_data.set_size(count); for(t_size n=0;n<count;n++) m_data[n] = helper.create(n); } t_size get_size() const {return m_data.get_size();} const t_ptr & operator[](t_size p_index) const {return m_data[p_index];} //nonconst version to allow sorting/bsearching; do not abuse t_ptr & operator[](t_size p_index) {return m_data[p_index];} private: pfc::array_t<t_ptr> m_data; }; template<typename t_interface> class service_enum_t { public: service_enum_t() : m_index(0) { pfc::assert_same_type<t_interface,typename t_interface::t_interface_entrypoint>(); } void reset() {m_index = 0;} template<typename t_query> bool first(service_ptr_t<t_query> & p_out) { reset(); return next(p_out); } template<typename t_query> bool next(service_ptr_t<t_query> & p_out) { pfc::assert_same_type<typename t_query::t_interface_entrypoint,t_interface>(); if (pfc::is_same_type<t_query,t_interface>::value) { return __next(reinterpret_cast<service_ptr_t<t_interface>&>(p_out)); } else { service_ptr_t<t_interface> temp; while(__next(temp)) { if (temp->service_query_t(p_out)) return true; } return false; } } private: bool __next(service_ptr_t<t_interface> & p_out) { return m_helper.create(p_out,m_index++); } unsigned m_index; service_class_helper_t<t_interface> m_helper; }; template<typename T> class service_factory_t : public service_factory_base_t<typename T::t_interface_entrypoint> { public: void instance_create(service_ptr_t<service_base> & p_out) { p_out = pfc::safe_cast<service_base*>(pfc::safe_cast<typename T::t_interface_entrypoint*>(pfc::safe_cast<T*>( new service_impl_t<T> ))); } }; template<typename T> class service_factory_single_t : public service_factory_base_t<typename T::t_interface_entrypoint> { service_impl_single_t<T> g_instance; public: TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD(service_factory_single_t,g_instance) void instance_create(service_ptr_t<service_base> & p_out) { p_out = pfc::safe_cast<service_base*>(pfc::safe_cast<typename T::t_interface_entrypoint*>(pfc::safe_cast<T*>(&g_instance))); } inline T& get_static_instance() {return g_instance;} }; template<typename T> class service_factory_single_ref_t : public service_factory_base_t<typename T::t_interface_entrypoint> { private: T & instance; public: service_factory_single_ref_t(T& param) : instance(param) {} void instance_create(service_ptr_t<service_base> & p_out) { p_out = pfc::safe_cast<service_base*>(pfc::safe_cast<typename T::t_interface_entrypoint*>(pfc::safe_cast<T*>(&instance))); } inline T& get_static_instance() {return instance;} }; template<typename T> class service_factory_single_transparent_t : public service_factory_base_t<typename T::t_interface_entrypoint>, public service_impl_single_t<T> { public: TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD(service_factory_single_transparent_t,service_impl_single_t<T>) void instance_create(service_ptr_t<service_base> & p_out) { p_out = pfc::safe_cast<service_base*>(pfc::safe_cast<typename T::t_interface_entrypoint*>(pfc::safe_cast<T*>(this))); } inline T& get_static_instance() {return *(T*)this;} }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/service_impl.h ================================================ //! Template implementing reference-counting features of service_base. Intended for dynamic instantiation: "new service_impl_t<someclass>(param1,param2);"; should not be instantiated otherwise (no local/static/member objects) because it does a "delete this;" when reference count reaches zero.\n //! Note that some constructor parameters such as NULL will need explicit typecasts to ensure correctly guessed types for constructor function template (null string needs to be (const char*)NULL rather than just NULL, etc). template<typename T> class service_impl_t : public T { public: int FB2KAPI service_release() throw() { int ret = --m_counter; if (ret == 0) try { delete this; } catch(...) {PFC_ASSERT(0);} return ret;} int FB2KAPI service_add_ref() throw() {return ++m_counter;} TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD(service_impl_t,T) private: pfc::refcounter m_counter; }; //! Template implementing dummy version of reference-counting features of service_base. Intended for static/local/member instantiation: "static service_impl_single_t<someclass> myvar(params);". Because reference counting features are disabled (dummy reference counter), code instantiating it is responsible for deleting it as well as ensuring that no references are active when the object gets deleted.\n //! Note that some constructor parameters such as NULL will need explicit typecasts to ensure correctly guessed types for constructor function template (null string needs to be (const char*)NULL rather than just NULL, etc). template<typename T> class service_impl_single_t : public T { public: int FB2KAPI service_release() throw() {return 1;} int FB2KAPI service_add_ref() throw() {return 1;} TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD(service_impl_single_t,T) }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/shared.h ================================================ //DEPRECATED #ifndef _FOOBAR2000_SHARED_H_ #define _FOOBAR2000_SHARED_H_ #include "../shared/shared.h" HWND uCreateDialog(UINT id,HWND parent,DLGPROC proc,LPARAM param=0); int uDialogBox(UINT id,HWND parent,DLGPROC proc,LPARAM param=0); #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/shortcut_actions.h ================================================ #error DEPRECATED ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/stdafx.cpp ================================================ //cpp used to generate precompiled header #include "foobar2000.h" ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/tag_processor.cpp ================================================ #include "foobar2000.h" void tag_processor_trailing::write_id3v1(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort) { write(p_file,p_info,flag_id3v1,p_abort); } void tag_processor_trailing::write_apev2(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort) { write(p_file,p_info,flag_apev2,p_abort); } void tag_processor_trailing::write_apev2_id3v1(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort) { write(p_file,p_info,flag_id3v1|flag_apev2,p_abort); } enum { g_flag_id3v1 = 1<<0, g_flag_id3v2 = 1<<1, g_flag_apev2 = 1<<2 }; static void tagtype_list_append(pfc::string_base & p_out,const char * p_name) { if (!p_out.is_empty()) p_out += "|"; p_out += p_name; } static void g_write_tags_ex(tag_write_callback & p_callback,unsigned p_flags,const service_ptr_t<file> & p_file,const file_info * p_info,abort_callback & p_abort) { PFC_ASSERT( p_flags == 0 || p_info != 0 ); if (p_flags & (g_flag_id3v1 | g_flag_apev2)) { switch(p_flags & (g_flag_id3v1 | g_flag_apev2)) { case g_flag_id3v1 | g_flag_apev2: static_api_ptr_t<tag_processor_trailing>()->write_apev2_id3v1(p_file,*p_info,p_abort); break; case g_flag_id3v1: static_api_ptr_t<tag_processor_trailing>()->write_id3v1(p_file,*p_info,p_abort); break; case g_flag_apev2: static_api_ptr_t<tag_processor_trailing>()->write_apev2(p_file,*p_info,p_abort); break; default: throw exception_io_data(); } } else { static_api_ptr_t<tag_processor_trailing>()->remove(p_file,p_abort); } if (p_flags & g_flag_id3v2) { static_api_ptr_t<tag_processor_id3v2>()->write_ex(p_callback,p_file,*p_info,p_abort); } else { t_uint64 dummy; tag_processor_id3v2::g_remove_ex(p_callback,p_file,dummy,p_abort); } } static void g_write_tags(unsigned p_flags,const service_ptr_t<file> & p_file,const file_info * p_info,abort_callback & p_abort) { g_write_tags_ex(tag_write_callback_dummy(),p_flags,p_file,p_info,p_abort); } void tag_processor::write_multi(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort,bool p_write_id3v1,bool p_write_id3v2,bool p_write_apev2) { unsigned flags = 0; if (p_write_id3v1) flags |= g_flag_id3v1; if (p_write_id3v2) flags |= g_flag_id3v2; if (p_write_apev2) flags |= g_flag_apev2; g_write_tags(flags,p_file,&p_info,p_abort); } void tag_processor::write_multi_ex(tag_write_callback & p_callback,const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort,bool p_write_id3v1,bool p_write_id3v2,bool p_write_apev2) { unsigned flags = 0; if (p_write_id3v1) flags |= g_flag_id3v1; if (p_write_id3v2) flags |= g_flag_id3v2; if (p_write_apev2) flags |= g_flag_apev2; g_write_tags_ex(p_callback,flags,p_file,&p_info,p_abort); } void tag_processor::write_id3v1(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort) { g_write_tags(g_flag_id3v1,p_file,&p_info,p_abort); } void tag_processor::write_apev2(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort) { g_write_tags(g_flag_apev2,p_file,&p_info,p_abort); } void tag_processor::write_apev2_id3v1(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort) { g_write_tags(g_flag_apev2|g_flag_id3v1,p_file,&p_info,p_abort); } void tag_processor::write_id3v2(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort) { g_write_tags(g_flag_id3v2,p_file,&p_info,p_abort); } void tag_processor::write_id3v2_id3v1(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort) { g_write_tags(g_flag_id3v2|g_flag_id3v1,p_file,&p_info,p_abort); } void tag_processor::remove_trailing(const service_ptr_t<file> & p_file,abort_callback & p_abort) { return static_api_ptr_t<tag_processor_trailing>()->remove(p_file,p_abort); } void tag_processor::remove_id3v2(const service_ptr_t<file> & p_file,abort_callback & p_abort) { t_uint64 dummy; tag_processor_id3v2::g_remove(p_file,dummy,p_abort); } void tag_processor::remove_id3v2_trailing(const service_ptr_t<file> & p_file,abort_callback & p_abort) { remove_id3v2(p_file,p_abort); remove_trailing(p_file,p_abort); } void tag_processor::read_trailing(const service_ptr_t<file> & p_file,file_info & p_info,abort_callback & p_abort) { static_api_ptr_t<tag_processor_trailing>()->read(p_file,p_info,p_abort); } void tag_processor::read_trailing_ex(const service_ptr_t<file> & p_file,file_info & p_info,t_uint64 & p_tagoffset,abort_callback & p_abort) { static_api_ptr_t<tag_processor_trailing>()->read_ex(p_file,p_info,p_tagoffset,p_abort); } void tag_processor::read_id3v2(const service_ptr_t<file> & p_file,file_info & p_info,abort_callback & p_abort) { static_api_ptr_t<tag_processor_id3v2>()->read(p_file,p_info,p_abort); } void tag_processor::read_id3v2_trailing(const service_ptr_t<file> & p_file,file_info & p_info,abort_callback & p_abort) { file_info_i temp_infos[2]; bool have_id3v2 = true, have_trailing = true; try { read_id3v2(p_file,temp_infos[0],p_abort); } catch(exception_io_data) { have_id3v2 = false; } try { read_trailing(p_file,temp_infos[1],p_abort); } catch(exception_io_data) { have_trailing = false; } if (!have_id3v2 && !have_trailing) throw exception_tag_not_found(); else { pfc::ptr_list_t<const file_info> blargh; if (have_id3v2) blargh.add_item(&temp_infos[0]); if (have_trailing) blargh.add_item(&temp_infos[1]); p_info.merge(blargh); } } void tag_processor::skip_id3v2(const service_ptr_t<file> & p_file,t_uint64 & p_size_skipped,abort_callback & p_abort) { tag_processor_id3v2::g_skip(p_file,p_size_skipped,p_abort); } bool tag_processor::is_id3v1_sufficient(const file_info & p_info) { return static_api_ptr_t<tag_processor_trailing>()->is_id3v1_sufficient(p_info); } void tag_processor::truncate_to_id3v1(file_info & p_info) { static_api_ptr_t<tag_processor_trailing>()->truncate_to_id3v1(p_info); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/tag_processor.h ================================================ #ifndef _TAG_PROCESSOR_H_ #define _TAG_PROCESSOR_H_ PFC_DECLARE_EXCEPTION(exception_tag_not_found,exception_io_data,"Tag not found"); //! Callback interface for write-tags-to-temp-file-and-swap scheme, used for ID3v2 tag updates and such where entire file needs to be rewritten. //! As a speed optimization, file content can be copied to a temporary file in the same directory as the file being updated, and then source file can be swapped for the newly created file with updated tags. //! This also gives better security against data loss on crash compared to rewriting the file in place and using memory or generic temporary file APIs to store content being rewritten. class NOVTABLE tag_write_callback { public: //! Called only once per operation (or not called at all when operation being performed can be done in-place). //! Requests a temporary file to be created in the same directory virtual bool open_temp_file(service_ptr_t<file> & p_out,abort_callback & p_abort) = 0; protected: tag_write_callback() {} ~tag_write_callback() {} private: tag_write_callback(const tag_write_callback &) {throw pfc::exception_not_implemented();} const tag_write_callback & operator=(const tag_write_callback &) {throw pfc::exception_not_implemented();} }; class tag_write_callback_dummy : public tag_write_callback { public: bool open_temp_file(service_ptr_t<file> & p_out,abort_callback & p_abort) {return false;} }; class NOVTABLE tag_processor_id3v2 : public service_base { public: virtual void read(const service_ptr_t<file> & p_file,file_info & p_info,abort_callback & p_abort) = 0; virtual void write(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort) = 0; virtual void write_ex(tag_write_callback & p_callback,const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort) = 0; static bool g_get(service_ptr_t<tag_processor_id3v2> & p_out); static void g_skip(const service_ptr_t<file> & p_file,t_filesize & p_size_skipped,abort_callback & p_abort); static void g_remove(const service_ptr_t<file> & p_file,t_filesize & p_size_removed,abort_callback & p_abort); static void g_remove_ex(tag_write_callback & p_callback,const service_ptr_t<file> & p_file,t_filesize & p_size_removed,abort_callback & p_abort); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(tag_processor_id3v2); }; class NOVTABLE tag_processor_trailing : public service_base { public: enum { flag_apev2 = 1, flag_id3v1 = 2, }; virtual void read(const service_ptr_t<file> & p_file,file_info & p_info,abort_callback & p_abort) = 0; virtual void write(const service_ptr_t<file> & p_file,const file_info & p_info,unsigned p_flags,abort_callback & p_abort) = 0; virtual void remove(const service_ptr_t<file> & p_file,abort_callback & p_abort) = 0; virtual bool is_id3v1_sufficient(const file_info & p_info) = 0; virtual void truncate_to_id3v1(file_info & p_info) = 0; virtual void read_ex(const service_ptr_t<file> & p_file,file_info & p_info,t_filesize & p_tagoffset,abort_callback & p_abort) = 0; void write_id3v1(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort); void write_apev2(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort); void write_apev2_id3v1(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(tag_processor_trailing); }; namespace tag_processor { //! Strips all recognized tags from the file and writes an ID3v1 tag with specified info. void write_id3v1(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort); //! Strips all recognized tags from the file and writes an APEv2 tag with specified info. void write_apev2(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort); //! Strips all recognized tags from the file and writes ID3v1+APEv2 tags with specified info. void write_apev2_id3v1(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort); //! Strips all recognized tags from the file and writes an ID3v2 tag with specified info. void write_id3v2(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort); //! Strips all recognized tags from the file and writes ID3v1+ID3v2 tags with specified info. void write_id3v2_id3v1(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort); //! Strips all recognized tags from the file and writes new tags with specified info according to parameters. void write_multi(const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort,bool p_write_id3v1,bool p_write_id3v2,bool p_write_apev2); //! Strips all recognized tags from the file and writes new tags with specified info according to parameters. Extended to allow write-tags-to-temp-file-and-swap scheme. void write_multi_ex(tag_write_callback & p_callback,const service_ptr_t<file> & p_file,const file_info & p_info,abort_callback & p_abort,bool p_write_id3v1,bool p_write_id3v2,bool p_write_apev2); //! Removes trailing tags from the file. void remove_trailing(const service_ptr_t<file> & p_file,abort_callback & p_abort); //! Removes ID3v2 tags from the file. void remove_id3v2(const service_ptr_t<file> & p_file,abort_callback & p_abort); //! Removes ID3v2 and trailing tags from specified file (not to be confused with trailing ID3v2 which are not yet supported). void remove_id3v2_trailing(const service_ptr_t<file> & p_file,abort_callback & p_abort); //! Reads trailing tags from the file. void read_trailing(const service_ptr_t<file> & p_file,file_info & p_info,abort_callback & p_abort); //! Reads trailing tags from the file. Extended version, returns offset at which parsed tags start. void read_trailing_ex(const service_ptr_t<file> & p_file,file_info & p_info,t_filesize & p_tagoffset,abort_callback & p_abort); //! Reads ID3v2 tags from specified file. void read_id3v2(const service_ptr_t<file> & p_file,file_info & p_info,abort_callback & p_abort); //! Reads ID3v2 and trailing tags from specified file (not to be confused with trailing ID3v2 which are not yet supported). void read_id3v2_trailing(const service_ptr_t<file> & p_file,file_info & p_info,abort_callback & p_abort); void skip_id3v2(const service_ptr_t<file> & p_file,t_filesize & p_size_skipped,abort_callback & p_abort); bool is_id3v1_sufficient(const file_info & p_info); void truncate_to_id3v1(file_info & p_info); }; #endif //_TAG_PROCESSOR_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/tag_processor_id3v2.cpp ================================================ #include "foobar2000.h" bool tag_processor_id3v2::g_get(service_ptr_t<tag_processor_id3v2> & p_out) { return service_enum_t<tag_processor_id3v2>().first(p_out); } void tag_processor_id3v2::g_remove(const service_ptr_t<file> & p_file,t_uint64 & p_size_removed,abort_callback & p_abort) { g_remove_ex(tag_write_callback_dummy(),p_file,p_size_removed,p_abort); } void tag_processor_id3v2::g_remove_ex(tag_write_callback & p_callback,const service_ptr_t<file> & p_file,t_uint64 & p_size_removed,abort_callback & p_abort) { p_file->ensure_seekable(); t_filesize len; len = p_file->get_size(p_abort); if (len == filesize_invalid) throw exception_io_no_length();; p_file->seek(0,p_abort); t_uint64 offset; g_skip(p_file,offset,p_abort); if (offset>0 && offset<len) { len-=offset; service_ptr_t<file> temp; if (p_callback.open_temp_file(temp,p_abort)) { file::g_transfer_object(p_file,temp,len,p_abort); } else { if (len > 16*1024*1024) filesystem::g_open_temp(temp,p_abort); else filesystem::g_open_tempmem(temp,p_abort); file::g_transfer_object(p_file,temp,len,p_abort); p_file->seek(0,p_abort); p_file->set_eof(p_abort); temp->seek(0,p_abort); file::g_transfer_object(temp,p_file,len,p_abort); } } p_size_removed = offset; } void tag_processor_id3v2::g_skip(const service_ptr_t<file> & p_file,t_uint64 & p_size_skipped,abort_callback & p_abort) { unsigned char tmp[10]; t_size io_bytes_done; p_file->seek ( 0, p_abort ); io_bytes_done = p_file->read( tmp, sizeof(tmp), p_abort); if (io_bytes_done != sizeof(tmp)) { p_file->seek ( 0, p_abort ); p_size_skipped = 0; return; } if ( 0 != memcmp ( tmp, "ID3", 3) ) { p_file->seek ( 0, p_abort ); p_size_skipped = 0; return; } int Unsynchronisation = tmp[5] & 0x80; int ExtHeaderPresent = tmp[5] & 0x40; int ExperimentalFlag = tmp[5] & 0x20; int FooterPresent = tmp[5] & 0x10; if ( tmp[5] & 0x0F ) { p_file->seek ( 0, p_abort ); p_size_skipped = 0; return; } if ( (tmp[6] | tmp[7] | tmp[8] | tmp[9]) & 0x80 ) { p_file->seek ( 0, p_abort ); p_size_skipped = 0; return; } t_uint32 ret; ret = tmp[6] << 21; ret += tmp[7] << 14; ret += tmp[8] << 7; ret += tmp[9] ; ret += 10; if ( FooterPresent ) ret += 10; p_file->seek ( ret, p_abort ); p_size_skipped = ret; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/threaded_process.cpp ================================================ #include "foobar2000.h" void threaded_process_status::set_progress(t_size p_state,t_size p_max) { set_progress( progress_min + MulDiv_Size(p_state,progress_max-progress_min,p_max) ); } void threaded_process_status::set_progress_secondary(t_size p_state,t_size p_max) { set_progress_secondary( progress_min + MulDiv_Size(p_state,progress_max-progress_min,p_max) ); } void threaded_process_status::set_progress_float(double p_state) { if (p_state < 0.0) set_progress(progress_min); else if (p_state < 1.0) set_progress( progress_min + (t_size)(p_state * (progress_max - progress_min))); else set_progress(progress_max); } void threaded_process_status::set_progress_secondary_float(double p_state) { if (p_state < 0.0) set_progress_secondary(progress_min); else if (p_state < 1.0) set_progress_secondary( progress_min + (t_size)(p_state * (progress_max - progress_min))); else set_progress_secondary(progress_max); } bool threaded_process::g_run_modal(service_ptr_t<threaded_process_callback> p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len) { return static_api_ptr_t<threaded_process>()->run_modal(p_callback,p_flags,p_parent,p_title,p_title_len); } bool threaded_process::g_run_modeless(service_ptr_t<threaded_process_callback> p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len) { return static_api_ptr_t<threaded_process>()->run_modeless(p_callback,p_flags,p_parent,p_title,p_title_len); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/threaded_process.h ================================================ #ifndef _foobar2000_sdk_threaded_process_h_ #define _foobar2000_sdk_threaded_process_h_ class NOVTABLE threaded_process_status { public: enum {progress_min = 0, progress_max = 5000}; virtual void set_progress(t_size p_state) = 0; virtual void set_progress_secondary(t_size p_state) = 0; virtual void set_item(const char * p_item,t_size p_item_len = ~0) = 0; virtual void set_item_path(const char * p_item,t_size p_item_len = ~0) = 0; virtual void set_title(const char * p_title,t_size p_title_len = ~0) = 0; virtual void force_update() = 0; virtual bool is_paused() = 0; virtual bool process_pause() = 0;//checks if process is paused and sleeps if needed; returns false when process should be aborted, true on success void set_progress(t_size p_state,t_size p_max); void set_progress_secondary(t_size p_state,t_size p_max); void set_progress_float(double p_state); void set_progress_secondary_float(double p_state); protected: threaded_process_status() {} ~threaded_process_status() {} }; class NOVTABLE threaded_process_callback : public service_base { public: virtual void on_init(HWND p_wnd) {} virtual void run(threaded_process_status & p_status,abort_callback & p_abort) = 0; virtual void on_done(HWND p_wnd,bool p_was_aborted) {} FB2K_MAKE_SERVICE_INTERFACE(threaded_process_callback,service_base); }; class NOVTABLE threaded_process : public service_base { public: enum { flag_show_abort = 1, flag_show_minimize = 1 << 1, flag_show_progress = 1 << 2, flag_show_progress_dual = 1 << 3,//implies flag_show_progress flag_show_item = 1 << 4, flag_show_pause = 1 << 5, flag_high_priority = 1 << 6, flag_show_delayed = 1 << 7,//modeless-only flag_no_focus = 1 << 8,//new (0.9.3) }; virtual bool run_modal(service_ptr_t<threaded_process_callback> p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len) = 0; virtual bool run_modeless(service_ptr_t<threaded_process_callback> p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len) = 0; static bool g_run_modal(service_ptr_t<threaded_process_callback> p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len = infinite); static bool g_run_modeless(service_ptr_t<threaded_process_callback> p_callback,unsigned p_flags,HWND p_parent,const char * p_title,t_size p_title_len = infinite); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(threaded_process); }; #endif //_foobar2000_sdk_threaded_process_h_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/titleformat.cpp ================================================ #include "foobar2000.h" void titleformat_compiler::remove_color_marks(const char * src,pfc::string_base & out)//helper { out.reset(); while(*src) { if (*src==3) { src++; while(*src && *src!=3) src++; if (*src==3) src++; } else out.add_byte(*src++); } } static bool test_for_bad_char(const char * source,t_size source_char_len,const char * reserved) { return pfc::strstr_ex(reserved,(t_size)(-1),source,source_char_len) != (t_size)(-1); } void titleformat_compiler::remove_forbidden_chars(titleformat_text_out * p_out,const GUID & p_inputtype,const char * p_source,t_size p_source_len,const char * p_reserved_chars) { if (p_reserved_chars == 0 || *p_reserved_chars == 0) { p_out->write(p_inputtype,p_source,p_source_len); } else { p_source_len = pfc::strlen_max(p_source,p_source_len); t_size index = 0; t_size good_byte_count = 0; while(index < p_source_len) { t_size delta = pfc::utf8_char_len(p_source + index,p_source_len - index); if (delta == 0) break; if (test_for_bad_char(p_source+index,delta,p_reserved_chars)) { if (good_byte_count > 0) {p_out->write(p_inputtype,p_source+index-good_byte_count,good_byte_count);good_byte_count=0;} p_out->write(p_inputtype,"_",1); } else { good_byte_count += delta; } index += delta; } if (good_byte_count > 0) {p_out->write(p_inputtype,p_source+index-good_byte_count,good_byte_count);good_byte_count=0;} } } void titleformat_compiler::remove_forbidden_chars_string_append(pfc::string_receiver & p_out,const char * p_source,t_size p_source_len,const char * p_reserved_chars) { remove_forbidden_chars(&titleformat_text_out_impl_string(p_out),pfc::guid_null,p_source,p_source_len,p_reserved_chars); } void titleformat_compiler::remove_forbidden_chars_string(pfc::string_base & p_out,const char * p_source,t_size p_source_len,const char * p_reserved_chars) { p_out.reset(); remove_forbidden_chars_string_append(p_out,p_source,p_source_len,p_reserved_chars); } void titleformat_hook_impl_file_info::process_codec(titleformat_text_out * p_out) { pfc::string8 temp; const char * val = m_info->info_get("codec"); if (val) { p_out->write(titleformat_inputtypes::meta,val); } else { val = m_info->info_get("referenced_file"); if (val) uAddStringUpper(temp,pfc::string_extension(val)); else uAddStringUpper(temp,pfc::string_extension(m_location.get_path())); p_out->write(titleformat_inputtypes::meta,temp); } } bool titleformat_hook_impl_file_info::remap_meta(t_size & p_meta_index, const char * p_name, t_size p_name_length) { p_meta_index = infinite; if (!stricmp_utf8_ex(p_name, p_name_length, "album", infinite)) { p_meta_index = m_info->meta_find("album"); if (p_meta_index != infinite) return true; p_meta_index = m_info->meta_find("venue"); if (p_meta_index != infinite) return true; return false; } else if (!stricmp_utf8_ex(p_name, p_name_length, "artist", infinite)) { p_meta_index = m_info->meta_find("artist"); if (p_meta_index != infinite) return true; p_meta_index = m_info->meta_find("album artist"); if (p_meta_index != infinite) return true; p_meta_index = m_info->meta_find("composer"); if (p_meta_index != infinite) return true; p_meta_index = m_info->meta_find("performer"); if (p_meta_index != infinite) return true; return false; } else if (!stricmp_utf8_ex(p_name, p_name_length, "album artist", infinite)) { p_meta_index = m_info->meta_find("album artist"); if (p_meta_index != infinite) return true; p_meta_index = m_info->meta_find("artist"); if (p_meta_index != infinite) return true; p_meta_index = m_info->meta_find("composer"); if (p_meta_index != infinite) return true; p_meta_index = m_info->meta_find("performer"); if (p_meta_index != infinite) return true; return false; } else if (!stricmp_utf8_ex(p_name,p_name_length,"track artist",infinite)) { t_size index_artist, index_album_artist; index_artist = m_info->meta_find("artist"); if (index_artist == infinite) return false; index_album_artist = m_info->meta_find("album artist"); if (index_album_artist == infinite) return false; if (m_info->are_meta_fields_identical(index_artist, index_album_artist)) return false; p_meta_index = index_artist; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"track",infinite) || !stricmp_utf8_ex(p_name,p_name_length,"tracknumber",infinite)) { p_meta_index = m_info->meta_find("tracknumber"); if (p_meta_index != infinite) return true; p_meta_index = m_info->meta_find("track"); if (p_meta_index != infinite) return true; return false; } else if (!stricmp_utf8_ex(p_name,p_name_length,"disc",infinite) || !stricmp_utf8_ex(p_name,p_name_length,"discnumber",infinite)) { p_meta_index = m_info->meta_find("discnumber"); if (p_meta_index != infinite) return true; p_meta_index = m_info->meta_find("disc"); if (p_meta_index != infinite) return true; return false; } else { p_meta_index = m_info->meta_find_ex(p_name,p_name_length); return p_meta_index != infinite; } } bool titleformat_hook_impl_file_info::process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) { p_found_flag = false; //todo make this bsearch someday if (stricmp_utf8_ex(p_name,p_name_length,"filename",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"_filename",infinite) == 0) { pfc::string8 temp; filesystem::g_get_display_path(m_location.get_path(),temp); p_out->write(titleformat_inputtypes::unknown,pfc::string_filename(temp),infinite); p_found_flag = true; return true; } else if (stricmp_utf8_ex(p_name,p_name_length,"filename_ext",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"_filename_ext",infinite) == 0) { pfc::string8 temp; filesystem::g_get_display_path(m_location.get_path(),temp); p_out->write(titleformat_inputtypes::unknown,pfc::string_filename_ext(temp),infinite); p_found_flag = true; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"filename_sort",infinite)) { pfc::string8 temp; filesystem::g_get_display_path(m_location.get_path(),temp); p_out->write(titleformat_inputtypes::unknown,pfc::string_filename(temp),infinite); p_out->write(titleformat_inputtypes::unknown,"|",infinite); p_out->write(titleformat_inputtypes::unknown,pfc::format_uint(m_location.get_subsong(),10),infinite); p_found_flag = true; return true; } else if (stricmp_utf8_ex(p_name,p_name_length,"path",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"_path",infinite) == 0) { pfc::string8 temp; filesystem::g_get_display_path(m_location.get_path(),temp); p_out->write(titleformat_inputtypes::unknown,temp.is_empty() ? "n/a" : temp.get_ptr(),infinite); p_found_flag = true; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"path_sort",infinite)) { pfc::string8_fastalloc temp; filesystem::g_get_display_path(m_location.get_path(),temp); p_out->write(titleformat_inputtypes::unknown,temp,infinite); p_out->write(titleformat_inputtypes::unknown,"|",infinite); p_out->write(titleformat_inputtypes::unknown,pfc::format_uint(m_location.get_subsong(),10),infinite); p_found_flag = true; return true; } else if (stricmp_utf8_ex(p_name,p_name_length,"directoryname",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"_directoryname",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"directory",infinite) == 0) { int count = 1; if (count > 0) { pfc::string8_fastalloc temp; filesystem::g_get_display_path(m_location.get_path(),temp); for(;count;count--) { t_size ptr = temp.scan_filename(); if (ptr==0) {temp.reset();break;} ptr--; temp.truncate(ptr); } if (temp.is_empty()) { p_found_flag = false; } else { p_out->write(titleformat_inputtypes::meta,temp + temp.scan_filename(),infinite); p_found_flag = true; } } return true; } else if (stricmp_utf8_ex(p_name,p_name_length,"subsong",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"_subsong",infinite) == 0) { p_out->write_int(titleformat_inputtypes::unknown,m_location.get_subsong()); p_found_flag = true; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"channels",infinite)) { unsigned val = (unsigned)m_info->info_get_int("channels"); switch(val) { case 0: p_out->write(titleformat_inputtypes::meta,"N/A",infinite); break; case 1: p_out->write(titleformat_inputtypes::meta,"mono",infinite); p_found_flag = true; break; case 2: p_out->write(titleformat_inputtypes::meta,"stereo",infinite); p_found_flag = true; break; default: p_out->write_int(titleformat_inputtypes::meta,val); p_out->write(titleformat_inputtypes::meta,"ch",infinite); p_found_flag = true; break; } return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"bitrate",infinite)) { const char * value = m_info->info_get("bitrate_dynamic"); if (value == 0 || *value == 0) value = m_info->info_get("bitrate"); if (value == 0 || *value == 0) return false; p_out->write(titleformat_inputtypes::meta,value); p_found_flag = true; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"samplerate",infinite)) { const char * value = m_info->info_get("samplerate"); if (value == 0 || *value == 0) return false; p_out->write(titleformat_inputtypes::meta,value); p_found_flag = true; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"title",infinite)) { if (process_meta(p_out,p_name,p_name_length,", ",2,", ",2)) { p_found_flag = true; return true; } else { pfc::string8 temp; filesystem::g_get_display_path(m_location.get_path(),temp); pfc::string_filename fn(temp); if (fn.is_empty()) p_out->write(titleformat_inputtypes::meta,temp); else p_out->write(titleformat_inputtypes::meta,fn); p_found_flag = true; return true; } } else if (!stricmp_utf8_ex(p_name,p_name_length,"codec",infinite)) { process_codec(p_out); p_found_flag = true; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"codec_profile",infinite)) { const char * profile = m_info->info_get("codec_profile"); if (profile == NULL) return false; p_out->write(titleformat_inputtypes::meta,profile,infinite); p_found_flag = true; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"track",infinite) || !stricmp_utf8_ex(p_name,p_name_length,"tracknumber",infinite)) { const t_size pad = 2; const char * val = m_info->meta_get_ex("tracknumber",infinite,0); if (val == 0) m_info->meta_get_ex("track",infinite,0); if (val != 0) { p_found_flag = true; t_size val_len = strlen(val); if (val_len < pad) { t_size n = pad - val_len; do { p_out->write(titleformat_inputtypes::meta,"0",1); n--; } while(n > 0); } p_out->write(titleformat_inputtypes::meta,val); } return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"disc",infinite) || !stricmp_utf8_ex(p_name,p_name_length,"discnumber",infinite)) { const t_size pad = 1; const char * val = m_info->meta_get_ex("discnumber",infinite,0); if (val == 0) val = m_info->meta_get_ex("disc",infinite,0); if (val != 0) { p_found_flag = true; t_size val_len = strlen(val); if (val_len < pad) { t_size n = pad - val_len; do { p_out->write(titleformat_inputtypes::meta,"0"); n--; } while(n > 0); } p_out->write(titleformat_inputtypes::meta,val); } return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"totaltracks",infinite)) { const t_size pad = 2; const char * val = m_info->meta_get_ex("totaltracks",infinite,0); if (val != NULL) { p_found_flag = true; t_size val_len = strlen(val); if (val_len < pad) { t_size n = pad - val_len; do { p_out->write(titleformat_inputtypes::meta,"0",1); n--; } while(n > 0); } p_out->write(titleformat_inputtypes::meta,val); } return true; } else if (stricmp_utf8_ex(p_name,p_name_length,"length",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"_length",infinite) == 0) { double len = m_info->get_length(); if (len>0) { p_out->write(titleformat_inputtypes::unknown,pfc::format_time(pfc::rint64(len))); p_found_flag = true; return true; } else return false; } else if (stricmp_utf8_ex(p_name,p_name_length,"length_ex",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"_length_ex",infinite) == 0) { double len = m_info->get_length(); if (len>0) { p_out->write(titleformat_inputtypes::unknown,pfc::format_time_ex(len),infinite); p_found_flag = true; return true; } else return false; } else if (stricmp_utf8_ex(p_name,p_name_length,"length_seconds",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"_length_seconds",infinite) == 0) { double len = m_info->get_length(); if (len>0) { p_out->write_int(titleformat_inputtypes::unknown,(t_uint64)len); p_found_flag = true; return true; } else return false; } else if (stricmp_utf8_ex(p_name,p_name_length,"length_seconds_fp",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"_length_seconds_fp",infinite) == 0) { double len = m_info->get_length(); if (len>0) { p_out->write(titleformat_inputtypes::unknown,pfc::string_fixed_t<64>()<<len); p_found_flag = true; return true; } else return false; } else if (stricmp_utf8_ex(p_name,p_name_length,"length_samples",infinite) == 0 || stricmp_utf8_ex(p_name,p_name_length,"_length_samples",infinite) == 0) { t_int64 val = m_info->info_get_length_samples(); if (val>0) { p_out->write_int(titleformat_inputtypes::unknown,val); p_found_flag = true; return true; } else return false; } else if (p_name_length > 2 && p_name[0] == '_' && p_name[1] == '_') {//info if (!stricmp_utf8_ex(p_name,p_name_length,"__replaygain_album_gain",infinite)) { char rgtemp[replaygain_info::text_buffer_size]; m_info->get_replaygain().format_album_gain(rgtemp); if (rgtemp[0] == 0) return false; p_out->write(titleformat_inputtypes::meta,rgtemp); p_found_flag = true; return true; } if (!stricmp_utf8_ex(p_name,p_name_length,"__replaygain_album_peak",infinite)) { char rgtemp[replaygain_info::text_buffer_size]; m_info->get_replaygain().format_album_peak(rgtemp); if (rgtemp[0] == 0) return false; p_out->write(titleformat_inputtypes::meta,rgtemp); p_found_flag = true; return true; } if (!stricmp_utf8_ex(p_name,p_name_length,"__replaygain_track_gain",infinite)) { char rgtemp[replaygain_info::text_buffer_size]; m_info->get_replaygain().format_track_gain(rgtemp); if (rgtemp[0] == 0) return false; p_out->write(titleformat_inputtypes::meta,rgtemp); p_found_flag = true; return true; } if (!stricmp_utf8_ex(p_name,p_name_length,"__replaygain_track_peak",infinite)) { char rgtemp[replaygain_info::text_buffer_size]; m_info->get_replaygain().format_track_peak(rgtemp); if (rgtemp[0] == 0) return false; p_out->write(titleformat_inputtypes::meta,rgtemp); p_found_flag = true; return true; } const char * value = m_info->info_get_ex(p_name+2,p_name_length-2); if (value == 0 || *value == 0) return false; p_out->write(titleformat_inputtypes::meta,value); p_found_flag = true; return true; } else if (p_name_length > 1 && p_name[0] == '_') {//special field bool found = process_extra(p_out,p_name+1,p_name_length-1); p_found_flag = found; return found; } else {//meta t_size index; if (remap_meta(index, p_name, p_name_length)) { bool status = process_meta(p_out,index,", ",2,", ",2); p_found_flag = status; return status; } return false; } } bool titleformat_hook_impl_file_info::process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) { p_found_flag = false; if (!stricmp_utf8_ex(p_name,p_name_length,"meta",infinite)) { switch(p_params->get_param_count()) { case 1: { const char * name; t_size name_length; p_params->get_param(0,name,name_length); bool status = process_meta(p_out,name,name_length,", ",2,", ",2); p_found_flag = status; return true; } case 2: { const char * name; t_size name_length; p_params->get_param(0,name,name_length); t_size index_val = p_params->get_param_uint(1); const char * value = m_info->meta_get_ex(name,name_length,index_val); if (value != 0) { p_found_flag = true; p_out->write(titleformat_inputtypes::meta,value,infinite); } return true; } default: return false; } } else if (!stricmp_utf8_ex(p_name,p_name_length,"meta_sep",infinite)) { switch(p_params->get_param_count()) { case 2: { const char * name, * sep1; t_size name_length, sep1_length; p_params->get_param(0,name,name_length); p_params->get_param(1,sep1,sep1_length); bool status = process_meta(p_out,name,name_length,sep1,sep1_length,sep1,sep1_length); p_found_flag = status; return true; } case 3: { const char * name, * sep1, * sep2; t_size name_length, sep1_length, sep2_length; p_params->get_param(0,name,name_length); p_params->get_param(1,sep1,sep1_length); p_params->get_param(2,sep2,sep2_length); bool status = process_meta(p_out,name,name_length,sep1,sep1_length,sep2,sep2_length); p_found_flag = status; return true; } default: return false; } } else if (!stricmp_utf8_ex(p_name,p_name_length,"meta_test",infinite)) { t_size n, count = p_params->get_param_count(); if (count == 0) return false; bool found_all = true; for(n=0;n<count;n++) { const char * name; t_size name_length; p_params->get_param(n,name,name_length); if (!m_info->meta_exists_ex(name,name_length)) { found_all = false; break; } } if (found_all) { p_found_flag = true; p_out->write_int(titleformat_inputtypes::meta,1); } return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"meta_num",infinite)) { if (p_params->get_param_count() != 1) return false; const char * name; t_size name_length; p_params->get_param(0,name,name_length); t_size count = m_info->meta_get_count_by_name_ex(name,name_length); p_out->write_int(titleformat_inputtypes::meta,count); if (count > 0) p_found_flag = true; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"info",infinite)) { if (p_params->get_param_count() != 1) return false; const char * name; t_size name_length; p_params->get_param(0,name,name_length); const char * value = m_info->info_get_ex(name,name_length); if (value != 0) { p_found_flag = true; p_out->write(titleformat_inputtypes::meta,value); } return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"extra",infinite)) { if (p_params->get_param_count() != 1) return false; const char * name; t_size name_length; p_params->get_param(0,name,name_length); if (process_extra(p_out,name,name_length)) p_found_flag = true; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"codec",infinite)) { if (p_params->get_param_count() != 0) return false; process_codec(p_out); p_found_flag = true; return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"channels",infinite)) { if (p_params->get_param_count() != 0) return false; unsigned val = (unsigned)m_info->info_get_int("channels"); switch(val) { case 0: p_out->write(titleformat_inputtypes::meta,"N/A",infinite); break; case 1: p_out->write(titleformat_inputtypes::meta,"mono",infinite); p_found_flag = true; break; case 2: p_out->write(titleformat_inputtypes::meta,"stereo",infinite); p_found_flag = true; break; default: p_out->write_int(titleformat_inputtypes::meta,val); p_out->write(titleformat_inputtypes::meta,"ch",infinite); p_found_flag = true; break; } return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"tracknumber",infinite)) { t_size pad = 2; t_size param_count = p_params->get_param_count(); if (param_count > 1) return false; if (param_count == 1) pad = (t_size)p_params->get_param_uint(0); const char * val = m_info->meta_get_ex("tracknumber",infinite,0); if (val != 0) { p_found_flag = true; t_size val_len = strlen(val); if (val_len < pad) { t_size n = pad - val_len; do { p_out->write(titleformat_inputtypes::meta,"0",1); n--; } while(n > 0); } p_out->write(titleformat_inputtypes::meta,val,infinite); } return true; } else return false; } bool titleformat_hook_impl_file_info::process_meta(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,const char * p_sep1,t_size p_sep1_length,const char * p_sep2,t_size p_sep2_length) { t_size index = m_info->meta_find_ex(p_name,p_name_length); return process_meta(p_out, index, p_sep1, p_sep1_length, p_sep2, p_sep2_length); } bool titleformat_hook_impl_file_info::process_meta(titleformat_text_out * p_out,t_size p_index,const char * p_sep1,t_size p_sep1_length,const char * p_sep2,t_size p_sep2_length) { if (p_index == infinite) return false; t_size n, m = m_info->meta_enum_value_count(p_index); for(n=0;n<m;n++) { if (n>0) { if (n+1 == m) p_out->write(titleformat_inputtypes::meta,p_sep2,p_sep2_length); else p_out->write(titleformat_inputtypes::meta,p_sep1,p_sep1_length); } p_out->write(titleformat_inputtypes::meta,m_info->meta_enum_value(p_index,n),infinite); } return true; } bool titleformat_hook_impl_file_info::process_extra(titleformat_text_out * p_out,const char * p_name,t_size p_name_length) { if (!stricmp_utf8_ex(p_name,p_name_length,"PATH_RAW",infinite)) { p_out->write(titleformat_inputtypes::unknown,m_location.get_path(),infinite); return true; } else if (!stricmp_utf8_ex(p_name,p_name_length,"FOOBAR2000_VERSION",infinite)) { p_out->write(titleformat_inputtypes::unknown,core_version_info::g_get_version_string(),infinite); return true; } else return false; } void titleformat_object::run_hook(const playable_location & p_location,const file_info * p_source,titleformat_hook * p_hook,pfc::string_base & p_out,titleformat_text_filter * p_filter) { if (p_hook) { run( &titleformat_hook_impl_splitter( &titleformat_hook_impl_file_info(p_location,p_source), p_hook), p_out,p_filter); } else { run( &titleformat_hook_impl_file_info(p_location,p_source), p_out,p_filter); } } void titleformat_object::run_simple(const playable_location & p_location,const file_info * p_source,pfc::string_base & p_out) { run(&titleformat_hook_impl_file_info(p_location,p_source),p_out,NULL); } t_size titleformat_hook_function_params::get_param_uint(t_size index) { const char * str; t_size str_len; get_param(index,str,str_len); return pfc::atoui_ex(str,str_len); } void titleformat_text_out_impl_filter_chars::write(const GUID & p_inputtype,const char * p_data,t_size p_data_length) { titleformat_compiler::remove_forbidden_chars(m_chain,p_inputtype,p_data,p_data_length,m_restricted_chars); } bool titleformat_hook_impl_splitter::process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) { p_found_flag = false; if (m_hook1 && m_hook1->process_field(p_out,p_name,p_name_length,p_found_flag)) return true; p_found_flag = false; if (m_hook2 && m_hook2->process_field(p_out,p_name,p_name_length,p_found_flag)) return true; p_found_flag = false; return false; } bool titleformat_hook_impl_splitter::process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) { p_found_flag = false; if (m_hook1 && m_hook1->process_function(p_out,p_name,p_name_length,p_params,p_found_flag)) return true; p_found_flag = false; if (m_hook2 && m_hook2->process_function(p_out,p_name,p_name_length,p_params,p_found_flag)) return true; p_found_flag = false; return false; } void titleformat_text_out::write_int_padded(const GUID & p_inputtype,t_int64 val,t_int64 maxval) { const t_size bufsize = 64; char temp[bufsize+1]; t_size len = 0; while(maxval) {maxval/=10;len++;} if (len == 0) len = 1; t_size n; for(n=0;n<bufsize;n++) temp[n] = '0'; temp[n] = 0; _i64toa(val,temp+bufsize/2,10); write(p_inputtype,temp + strlen(temp) - len,infinite); } void titleformat_text_out::write_int(const GUID & p_inputtype,t_int64 val) { write(p_inputtype,pfc::format_int(val)); } void titleformat_text_filter_impl_reserved_chars::write(const GUID & p_inputtype,pfc::string_receiver & p_out,const char * p_data,t_size p_data_length) { if (p_inputtype == titleformat_inputtypes::meta) titleformat_compiler::remove_forbidden_chars_string_append(p_out,p_data,p_data_length,m_reserved_chars); else p_out.add_string(p_data,p_data_length); } void titleformat_compiler::run(titleformat_hook * p_source,pfc::string_base & p_out,const char * p_spec) { service_ptr_t<titleformat_object> ptr; if (!compile(ptr,p_spec)) p_out = "[COMPILATION ERROR]"; else ptr->run(p_source,p_out,NULL); } void titleformat_compiler::compile_safe(service_ptr_t<titleformat_object> & p_out,const char * p_spec) { if (!compile(p_out,p_spec)) { if (!compile(p_out,"%filename%")) throw pfc::exception_bug_check(); } } namespace titleformat_inputtypes { const GUID meta = { 0xcd839c8e, 0x5c66, 0x4ae1, { 0x8d, 0xad, 0x71, 0x1f, 0x86, 0x0, 0xa, 0xe3 } }; const GUID unknown = { 0x673aa1cd, 0xa7a8, 0x40c8, { 0xbf, 0x9b, 0x34, 0x37, 0x99, 0x29, 0x16, 0x3b } }; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/titleformat.h ================================================ #ifndef _FOOBAR2000_TITLEFORMAT_H_ #define _FOOBAR2000_TITLEFORMAT_H_ namespace titleformat_inputtypes { extern const GUID meta, unknown; }; class NOVTABLE titleformat_text_out { public: virtual void write(const GUID & p_inputtype,const char * p_data,t_size p_data_length = infinite) = 0; void write_int(const GUID & p_inputtype,t_int64 val); void write_int_padded(const GUID & p_inputtype,t_int64 val,t_int64 maxval); protected: titleformat_text_out() {} ~titleformat_text_out() {} }; class NOVTABLE titleformat_text_filter { public: virtual void write(const GUID & p_inputtype,pfc::string_receiver & p_out,const char * p_data,t_size p_data_length) = 0; protected: titleformat_text_filter() {} ~titleformat_text_filter() {} }; class NOVTABLE titleformat_hook_function_params { public: virtual t_size get_param_count() = 0; virtual void get_param(t_size index,const char * & p_string,t_size & p_string_len) = 0;//warning: not a null-terminated string //helper t_size get_param_uint(t_size index); }; class NOVTABLE titleformat_hook { public: virtual bool process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) = 0; virtual bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) = 0; }; //! Represents precompiled executable title-formatting script. Use titleformat_compiler to instantiate; do not reimplement. class NOVTABLE titleformat_object : public service_base { public: virtual void run(titleformat_hook * p_source,pfc::string_base & p_out,titleformat_text_filter * p_filter)=0; void run_hook(const playable_location & p_location,const file_info * p_source,titleformat_hook * p_hook,pfc::string_base & p_out,titleformat_text_filter * p_filter); void run_simple(const playable_location & p_location,const file_info * p_source,pfc::string_base & p_out); FB2K_MAKE_SERVICE_INTERFACE(titleformat_object,service_base); }; //! Standard service for instantiating titleformat_object. Implemented by the core; do not reimplement. //! To instantiate, use static_api_ptr_t<titleformat_compiler>. class NOVTABLE titleformat_compiler : public service_base { public: //! Returns false in case of a compilation error. virtual bool compile(service_ptr_t<titleformat_object> & p_out,const char * p_spec) = 0; //! Helper; void run(titleformat_hook * p_source,pfc::string_base & p_out,const char * p_spec); //! Should never fail, falls back to %filename% in case of failure. void compile_safe(service_ptr_t<titleformat_object> & p_out,const char * p_spec); //! Throws bug check when script can't be compiled. For use with hardcoded scripts only. void compile_force(service_ptr_t<titleformat_object> & p_out,const char * p_spec) {if (!compile(p_out,p_spec)) throw pfc::exception_bug_check();} static void remove_color_marks(const char * src,pfc::string_base & out);//helper static void remove_forbidden_chars(titleformat_text_out * p_out,const GUID & p_inputtype,const char * p_source,t_size p_source_len,const char * p_forbidden_chars); static void remove_forbidden_chars_string_append(pfc::string_receiver & p_out,const char * p_source,t_size p_source_len,const char * p_forbidden_chars); static void remove_forbidden_chars_string(pfc::string_base & p_out,const char * p_source,t_size p_source_len,const char * p_forbidden_chars); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(titleformat_compiler); }; class titleformat_object_wrapper { public: titleformat_object_wrapper(const char * p_script) { static_api_ptr_t<titleformat_compiler>()->compile_force(m_script,p_script); } operator const service_ptr_t<titleformat_object> &() const {return m_script;} private: service_ptr_t<titleformat_object> m_script; }; //helpers class titleformat_text_out_impl_filter_chars : public titleformat_text_out { public: inline titleformat_text_out_impl_filter_chars(titleformat_text_out * p_chain,const char * p_restricted_chars) : m_chain(p_chain), m_restricted_chars(p_restricted_chars) {} void write(const GUID & p_inputtype,const char * p_data,t_size p_data_length); private: titleformat_text_out * m_chain; const char * m_restricted_chars; }; class titleformat_text_out_impl_string : public titleformat_text_out { public: titleformat_text_out_impl_string(pfc::string_receiver & p_string) : m_string(p_string) {} void write(const GUID & p_inputtype,const char * p_data,t_size p_data_length) {m_string.add_string(p_data,p_data_length);} private: pfc::string_receiver & m_string; }; class titleformat_hook_impl_file_info : public titleformat_hook { public: titleformat_hook_impl_file_info(const playable_location & p_location,const file_info * p_info) : m_location(p_location), m_info(p_info) {}//caller must ensure that referenced file_info object is alive as long as the titleformat_hook_impl_file_info instance bool process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag); bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag); protected: bool process_meta(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,const char * p_sep1,t_size p_sep1_length,const char * p_sep2,t_size p_sep2_length); bool process_meta(titleformat_text_out * p_out,t_size p_index,const char * p_sep1,t_size p_sep1_length,const char * p_sep2,t_size p_sep2_length); bool process_extra(titleformat_text_out * p_out,const char * p_name,t_size p_name_length); bool remap_meta(t_size & p_index, const char * p_name, t_size p_name_length); const file_info * m_info; private: void process_codec(titleformat_text_out * p_out); const playable_location & m_location; }; class titleformat_hook_impl_splitter : public titleformat_hook { public: inline titleformat_hook_impl_splitter(titleformat_hook * p_hook1,titleformat_hook * p_hook2) : m_hook1(p_hook1), m_hook2(p_hook2) {} bool process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag); bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag); private: titleformat_hook * m_hook1, * m_hook2; }; class titleformat_text_filter_impl_reserved_chars : public titleformat_text_filter { public: titleformat_text_filter_impl_reserved_chars(const char * p_reserved_chars) : m_reserved_chars(p_reserved_chars) {} virtual void write(const GUID & p_inputtype,pfc::string_receiver & p_out,const char * p_data,t_size p_data_length); private: const char * m_reserved_chars; }; #endif //_FOOBAR2000_TITLEFORMAT_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/titleformat_config.cpp ================================================ #include "foobar2000.h" //void on_change(const char * p_name,const char * p_value,unsigned p_value_length) ; void titleformat_config_callback::g_on_change(const GUID & p_guid,const char * p_name,const char * p_value,t_size p_value_length) { service_ptr_t<titleformat_config_callback> ptr; service_enum_t<titleformat_config_callback> e; while(e.next(ptr)) ptr->on_change(p_guid,p_name,p_value,p_value_length); } const char * titleformat_config_impl::get_name() { return m_name; } void titleformat_config_impl::get_data(pfc::string_base & p_out) { insync(m_sync); p_out = m_value; } void titleformat_config_impl::set_data(const char * p_string,unsigned p_string_length) { core_api::ensure_main_thread(); { insync(m_sync); m_value.set_string(p_string,p_string_length); m_compilation_failed = false; m_instance.release(); } titleformat_config_callback::g_on_change(m_guid,m_name,p_string,p_string_length); } bool titleformat_config_impl::compile(service_ptr_t<titleformat_object> & p_out) { insync(m_sync); if (m_instance.is_empty()) { if (m_compilation_failed) return false; if (!static_api_ptr_t<titleformat_compiler>()->compile(m_instance,m_value)) { m_compilation_failed = true; return false; } } p_out = m_instance; return true; } void titleformat_config_impl::reset() { set_data(m_value_default,infinite); } void titleformat_config_impl::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { p_stream->write_string_raw(m_value,p_abort); } void titleformat_config_impl::set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { pfc::string8_fastalloc temp; p_stream->read_string_raw(temp,p_abort); m_instance.release(); m_compilation_failed = false; m_value = temp; } titleformat_config_impl::titleformat_config_impl(const GUID & p_guid,const char * p_name,const char * p_initvalue,double p_order) : cfg_var(p_guid), m_guid(p_guid), m_name(p_name), m_value(p_initvalue), m_value_default(p_initvalue), m_compilation_failed(false), m_order(p_order) {} GUID titleformat_config_impl::get_guid() {return m_guid;} bool titleformat_config::g_find(const GUID & p_guid,service_ptr_t<titleformat_config> & p_out) { service_ptr_t<titleformat_config> ptr; service_enum_t<titleformat_config> e; while(e.next(ptr)) { if (ptr->get_guid() == p_guid) { p_out = ptr; return true; } } return false; } bool titleformat_config::g_compile(const GUID & p_guid,service_ptr_t<titleformat_object> & p_out) { service_ptr_t<titleformat_config> ptr; if (!g_find(p_guid,ptr)) return false; return ptr->compile(p_out); } bool titleformat_config::g_get_data(const GUID & p_guid,pfc::string_base & p_out) { service_ptr_t<titleformat_config> ptr; if (!g_find(p_guid,ptr)) return false; ptr->get_data(p_out); return true; } double titleformat_config_impl::get_order_priority() {return m_order;} ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/titleformat_config.h ================================================ #ifndef _TITLEFORMAT_CONFIG_H_ #define _TITLEFORMAT_CONFIG_H_ class NOVTABLE titleformat_config : public service_base { public: virtual GUID get_guid() = 0; virtual const char * get_name() = 0; virtual void get_data(pfc::string_base & p_out) = 0; virtual void set_data(const char * p_string,unsigned p_string_length) = 0; virtual void reset() = 0; virtual bool compile(service_ptr_t<titleformat_object> & p_out) = 0; virtual double get_order_priority() = 0; static bool g_find(const GUID & p_guid,service_ptr_t<titleformat_config> & p_out); static bool g_get_data(const GUID & p_guid,pfc::string_base & p_out); static bool g_compile(const GUID & p_guid,service_ptr_t<titleformat_object> & p_out); static const GUID config_playlist,config_copy,config_statusbar,config_systray,config_windowtitle; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(titleformat_config); }; class titleformat_config_callback : public service_base { public: virtual void on_change(const GUID & p_guid,const char * p_name,const char * p_value,t_size p_value_length) = 0; static void g_on_change(const GUID & p_guid,const char * p_name,const char * p_value,t_size p_value_length); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(titleformat_config_callback); }; class titleformat_config_impl : public titleformat_config, private cfg_var { public: GUID get_guid(); const char * get_name(); void get_data(pfc::string_base & p_out); void set_data(const char * p_string,unsigned p_string_length); void reset(); bool compile(service_ptr_t<titleformat_object> & p_out); double get_order_priority(); titleformat_config_impl(const GUID &,const char * p_name,const char * p_initvalue,double p_order); private: void get_data_raw(stream_writer * p_stream,abort_callback & p_abort); void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort); pfc::string8 m_name,m_value,m_value_default; service_ptr_t<titleformat_object> m_instance; bool m_compilation_failed; GUID m_guid; double m_order; critical_section m_sync; }; typedef service_factory_single_transparent_t<titleformat_config_impl> titleformat_config_factory; //usage: //static titleformat_config_factory g_mytitleformatconfig("this will show up in titleformat config page","%blah%"); #endif //_TITLEFORMAT_CONFIG_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/track_property.h ================================================ //! Callback interface for track_property_provider::enumerate_properties(). class NOVTABLE track_property_callback { public: //! Sets a property list entry to display. Called by track_property_provider::enumerate_properties() implementation. //! @param p_group Name of group to put the entry in, case-sensitive. Note that non-standard groups are sorted alphabetically. //! @param p_sortpriority Sort priority of the property inside its group (smaller value means earlier in the list), pass 0 if you don't care (alphabetic order by name used when more than one item has same priority). //! @param p_name Name of the property. //! @param p_value Value of the property. virtual void set_property(const char * p_group,double p_sortpriority,const char * p_name,const char * p_value) = 0; protected: ~track_property_callback() {} }; //! Service for adding custom entries in "General Properties" section of the properties dialog. class NOVTABLE track_property_provider : public service_base { public: //! Enumerates properties of specified track list. //! @param p_tracks List of tracks to enumerate properties on. //! @param p_out Callback interface receiving enumerated properties. virtual void enumerate_properties(pfc::list_base_const_t<metadb_handle_ptr> const & p_tracks, track_property_callback & p_out) = 0; //! Returns whether specified tech info filed is processed by our service and should not be displayed among unknown fields. //! @param p_name Name of tech info field being queried. //! @returns True if the field is among fields processed by this track_property_provider implementation and should not be displayed among unknown fields, false otherwise. virtual bool is_our_tech_info(const char * p_name) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(track_property_provider); }; template<typename T> class track_property_provider_factory_t : public service_factory_single_t<T> {}; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/ui.cpp ================================================ #include "foobar2000.h" bool ui_drop_item_callback::g_on_drop(interface IDataObject * pDataObject) { service_enum_t<ui_drop_item_callback> e; service_ptr_t<ui_drop_item_callback> ptr; if (e.first(ptr)) do { if (ptr->on_drop(pDataObject)) return true; } while(e.next(ptr)); return false; } bool ui_drop_item_callback::g_is_accepted_type(interface IDataObject * pDataObject, DWORD * p_effect) { service_enum_t<ui_drop_item_callback> e; service_ptr_t<ui_drop_item_callback> ptr; if (e.first(ptr)) do { if (ptr->is_accepted_type(pDataObject,p_effect)) return true; } while(e.next(ptr)); return false; } bool user_interface::g_find(service_ptr_t<user_interface> & p_out,const GUID & p_guid) { service_enum_t<user_interface> e; service_ptr_t<user_interface> ptr; if (e.first(ptr)) do { if (ptr->get_guid() == p_guid) { p_out = ptr; return true; } } while(e.next(ptr)); return false; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/ui.h ================================================ #ifndef _FOOBAR2000_UI_H_ #define _FOOBAR2000_UI_H_ #include "service.h" #ifndef _WINDOWS #error PORTME #endif //! Entrypoint service for user interface modules. Implement when registering an UI module. Do not call existing implementations; only core enumerates / dispatches calls. To control UI behaviors from other components, use ui_control API. \n //! Use user_interface_factory_t<> to register, e.g static user_interface_factory_t<myclass> g_myclass_factory; class NOVTABLE user_interface : public service_base { public: //!HookProc usage: \n //! in your windowproc, call HookProc first, and if it returns true, return LRESULT value it passed to you typedef BOOL (WINAPI * HookProc_t)(HWND wnd,UINT msg,WPARAM wp,LPARAM lp,LRESULT * ret); //! Retrieves name (UTF-8 null-terminated string) of the UI module. virtual const char * get_name()=0; //! Initializes the UI module - creates the main app window, etc. Failure should be signaled by appropriate exception (std::exception or a derivative). virtual HWND init(HookProc_t hook)=0; //! Deinitializes the UI module - destroys the main app window, etc. virtual void shutdown()=0; //! Activates main app window. virtual void activate()=0; //! Minimizes/hides main app window. virtual void hide()=0; //! Returns whether main window is visible / not minimized. Used for activate/hide command. virtual bool is_visible() = 0; //! Retrieves GUID of your implementation, to be stored in configuration file etc. virtual GUID get_guid() = 0; //! Overrides statusbar text with specified string. The parameter is a null terminated UTF-8 string. The override is valid until another override_statusbar_text() call or revert_statusbar_text() call. virtual void override_statusbar_text(const char * p_text) = 0; //! Disables statusbar text override. virtual void revert_statusbar_text() = 0; //! Shows now-playing item somehow (e.g. system notification area popup). virtual void show_now_playing() = 0; static bool g_find(service_ptr_t<user_interface> & p_out,const GUID & p_guid); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(user_interface); }; template<typename T> class user_interface_factory : public service_factory_single_t<T> {}; //! Interface class allowing you to override UI statusbar text. There may be multiple callers trying to override statusbar text; backend decides which one succeeds so you will not always get what you want. Statusbar text override is automatically cancelled when the object is released.\n //! Use ui_control::override_status_text_create() to instantiate. //! Implemented by core. Do not reimplement. class NOVTABLE ui_status_text_override : public service_base { public: //! Sets statusbar text to specified UTF-8 null-terminated string. virtual void override_text(const char * p_message) = 0; //! Cancels statusbar text override. virtual void revert_text() = 0; FB2K_MAKE_SERVICE_INTERFACE(ui_status_text_override,service_base); }; //! Serivce providing various UI-related commands. Implemented by core; do not reimplement. //! Instantiation: use static_api_ptr_t<ui_control>. class NOVTABLE ui_control : public service_base { public: //! Returns whether primary UI is visible/unminimized. virtual bool is_visible()=0; //! Activates/unminimizes main UI. virtual void activate()=0; //! Hides/minimizese main UI. virtual void hide()=0; //! Retrieves main GUI icon, to use as window icon etc. Returned handle does not need to be freed. virtual HICON get_main_icon()=0; //! Loads main GUI icon, version with specified width/height. Returned handle needs to be freed with DestroyIcon when you are done using it. virtual HICON load_main_icon(unsigned width,unsigned height) = 0; //! Activates preferences dialog and navigates to specified page. See also: preference_page API. virtual void show_preferences(const GUID & p_page) = 0; //! Instantiates ui_status_text_override service, that can be used to display status messages. //! @param p_out receives new ui_status_text_override instance. //! @returns true on success, false on failure (out of memory / no GUI loaded / etc) virtual bool override_status_text_create(service_ptr_t<ui_status_text_override> & p_out) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(ui_control); }; //! Service called from the UI when some object is dropped into the UI. Usable for modifying drag&drop behaviors such as adding custom handlers for object types other than supported media files.\n //! Implement where needed; use ui_drop_item_callback_factory_t<> template to register, e.g. static ui_drop_item_callback_factory_t<myclass> g_myclass_factory. class NOVTABLE ui_drop_item_callback : public service_base { public: //! Called when an object was dropped; returns true if the object was processed and false if not. virtual bool on_drop(interface IDataObject * pDataObject) = 0; //! Tests whether specified object type is supported by this ui_drop_item_callback implementation. Returns true and sets p_effect when it's supported; returns false otherwise. \n //! See IDropTarget::DragEnter() documentation for more information about p_effect values. virtual bool is_accepted_type(interface IDataObject * pDataObject, DWORD * p_effect)=0; //! Static helper, calls all existing implementations appropriately. See on_drop(). static bool g_on_drop(interface IDataObject * pDataObject); //! Static helper, calls all existing implementations appropriately. See is_accepted_type(). static bool g_is_accepted_type(interface IDataObject * pDataObject, DWORD * p_effect); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(ui_drop_item_callback); }; template<class T> class ui_drop_item_callback_factory_t : public service_factory_single_t<T> {}; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/unpack.h ================================================ #ifndef _UNPACK_H_ #define _UNPACK_H_ //! Service providing "unpacker" functionality - processes "packed" file (such as a zip file containing a single media file inside) to allow its contents to be accessed transparently.\n //! To access existing unpacker implementations, use unpacker::g_open helper function.\n //! To register your own implementation, use unpacker_factory_t template. class NOVTABLE unpacker : public service_base { public: //! Attempts to open specified file for unpacking, creates interface to virtual file with uncompressed data on success. When examined file doesn't appear to be one of formats supported by this unpacker implementation, throws exception_io_data. //! @param p_out Receives interface to virtual file with uncompressed data on success. //! @param p_source Source file to process. //! @param p_abort abort_callback object signaling user aborting the operation. virtual void open(service_ptr_t<file> & p_out,const service_ptr_t<file> & p_source,abort_callback & p_abort) = 0; //! Static helper querying existing unpacker implementations until one that successfully opens specified file is found. Attempts to open specified file for unpacking, creates interface to virtual file with uncompressed data on success. When examined file doesn't appear to be one of formats supported by registered unpacker implementations, throws exception_io_data. //! @param p_out Receives interface to virtual file with uncompressed data on success. //! @param p_source Source file to process. //! @param p_abort abort_callback object signaling user aborting the operation. static void g_open(service_ptr_t<file> & p_out,const service_ptr_t<file> & p_source,abort_callback & p_abort); FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(unpacker); }; template<typename t_myclass> class unpacker_factory_t : public service_factory_single_t<t_myclass> {}; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/utf8api.cpp ================================================ #include "foobar2000.h" HWND uCreateDialog(UINT id,HWND parent,DLGPROC proc,LPARAM param) { return CreateDialogParam(core_api::get_my_instance(),MAKEINTRESOURCE(id),parent,proc,param); } int uDialogBox(UINT id,HWND parent,DLGPROC proc,LPARAM param) { return (int)DialogBoxParam(core_api::get_my_instance(),MAKEINTRESOURCE(id),parent,proc,param); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/SDK/vis.h ================================================ #ifndef _FOOBAR2000_VIS_H_ #define _FOOBAR2000_VIS_H_ //! This class provides abstraction for receiving visualisation data. Instances of visualisation_stream being created/released serve as an indication for visualisation backend to process currently played audio data or shut down when there are no visualisation clients active.\n //! Use visualisation_manager::create_stream to instantiate. class NOVTABLE visualisation_stream : public service_base { public: //! Retrieves absolute playback time since last playback start or seek. You typically pass value retrieved by this function - optionally with offset added - to other visualisation_stream methods. virtual bool get_absolute_time(double & p_value) = 0; //! Retrieves an audio chunk starting at specified offset (see get_absolute_time()), of specified length. //! @returns False when requested timestamp is out of available range, true on success. virtual bool get_chunk_absolute(audio_chunk & p_chunk,double p_offset,double p_requested_length) = 0; //! Retrieves spectrum for audio data at specified offset (see get_absolute_time()), with specified FFT size. //! @param p_chunk Receives spectrum data. audio_chunk type is used for consistency (since required functionality is identical to provided by audio_chunk), the data is *not* PCM. Returned sample count is equal to half of FFT size; channels and sample rate are as in audio stream the spectrum was generated from. //! @param p_offset Timestamp of spectrum to retrieve. See get_absolute_time(). //! @param p_fft_size FFT size to use for spectrum generation. Must be a power of 2. //! @returns False when requested timestamp is out of available range, true on success. virtual bool get_spectrum_absolute(audio_chunk & p_chunk,double p_offset,unsigned p_fft_size) = 0; //! Generates fake audio chunk to display when get_chunk_absolute() fails - e.g. shortly after visualisation_stream creation data for currently played audio might not be available yet. //! Throws std::exception derivatives on failure. virtual void make_fake_chunk_absolute(audio_chunk & p_chunk,double p_offset,double p_requested_length) = 0; //! Generates fake spectrum to display when get_spectrum_absolute() fails - e.g. shortly after visualisation_stream creation data for currently played audio might not be available yet. //! Throws std::exception derivatives on failure. virtual void make_fake_spectrum_absolute(audio_chunk & p_chunk,double p_offset,unsigned p_fft_size) = 0; FB2K_MAKE_SERVICE_INTERFACE(visualisation_stream,service_base); }; //! Entrypoint service for visualisation processing; use this to create visualisation_stream objects that can be used to retrieve properties of currently played audio. \n //! Implemented by core; do not reimplement.\n //! Use static_api_ptr_t to access it, e.g. static_api_ptr_t<visualisation_manager>()->create_stream(mystream,0); class NOVTABLE visualisation_manager : public service_base { public: //! Creates a visualisation_stream object. See visualisation_stream for more info. //! @param p_out Receives newly created visualisation_stream instance. //! @param p_flags Reserved for future use. Must be set to zero. virtual void create_stream(service_ptr_t<visualisation_stream> & p_out,unsigned p_flags) = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(visualisation_manager); }; #endif //_FOOBAR2000_VIS_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/foo_input_raw/foo_input_raw.cpp ================================================ #include "../SDK/foobar2000.h" enum { raw_bits_per_sample = 16, raw_channels = 2, raw_sample_rate = 44100, raw_bytes_per_sample = raw_bits_per_sample / 8, raw_total_sample_width = raw_bytes_per_sample * raw_channels, }; // No inheritance. Our methods get called over input framework templates. See input_singletrack_impl for descriptions of what each method does. class input_raw { public: void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) { if (p_reason == input_open_info_write) throw exception_io_unsupported_format();//our input does not support retagging. m_file = p_filehint;//p_filehint may be null, hence next line input_open_file_helper(m_file,p_path,p_reason,p_abort);//if m_file is null, opens file with appropriate privileges for our operation (read/write for writing tags, read-only otherwise). } void get_info(file_info & p_info,abort_callback & p_abort) { t_filesize size = m_file->get_size(p_abort); if (size != filesize_invalid) { //file size is known, let's set length p_info.set_length(audio_math::samples_to_time( size / raw_total_sample_width, raw_sample_rate)); } //note that the values below should be based on contents of the file itself, NOT on user-configurable variables for an example. To report info that changes independently from file contents, use get_dynamic_info/get_dynamic_info_track instead. p_info.info_set_int("samplerate",raw_sample_rate); p_info.info_set_int("channels",raw_channels); p_info.info_set_int("bitspersample",raw_bits_per_sample); p_info.info_set("encoding","lossless"); p_info.info_set_bitrate((raw_bits_per_sample * raw_channels * raw_sample_rate + 500 /* rounding for bps to kbps*/ ) / 1000 /* bps to kbps */); } t_filestats get_file_stats(abort_callback & p_abort) {return m_file->get_stats(p_abort);} void decode_initialize(unsigned p_flags,abort_callback & p_abort) { m_file->reopen(p_abort);//equivalent to seek to zero, except it also works on nonseekable streams } bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) { enum { deltaread = 1024, }; m_buffer.set_size(deltaread * raw_total_sample_width); t_size deltaread_done = m_file->read(m_buffer.get_ptr(),deltaread * raw_total_sample_width,p_abort) / raw_total_sample_width; if (deltaread_done == 0) return false;//EOF p_chunk.set_data_fixedpoint(m_buffer.get_ptr(),deltaread_done * raw_total_sample_width,raw_sample_rate,raw_channels,raw_bits_per_sample,audio_chunk::g_guess_channel_config(raw_channels)); //processed successfully, no EOF return true; } void decode_seek(double p_seconds,abort_callback & p_abort) { m_file->ensure_seekable();//throw exceptions if someone called decode_seek() despite of our input having reported itself as nonseekable. // IMPORTANT: convert time to sample offset with proper rounding! audio_math::time_to_samples does this properly for you. t_filesize target = audio_math::time_to_samples(p_seconds,raw_sample_rate) * raw_total_sample_width; // get_size_ex fails (throws exceptions) if size is not known (where get_size would return filesize_invalid). Should never fail on seekable streams (if it does it's not our problem anymore). t_filesize max = m_file->get_size_ex(p_abort); if (target > max) target = max;//clip seek-past-eof attempts to legal range (next decode_run() call will just signal EOF). m_file->seek(target,p_abort); } bool decode_can_seek() {return m_file->can_seek();} bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {return false;} bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {return false;} void decode_on_idle(abort_callback & p_abort) {m_file->on_idle(p_abort);} void retag(const file_info & p_info,abort_callback & p_abort) {throw exception_io_unsupported_format();} static bool g_is_our_content_type(const char * p_content_type) {return false;} static bool g_is_our_path(const char * p_path,const char * p_extension) {return stricmp_utf8(p_extension,"raw") == 0;} public: service_ptr_t<file> m_file; pfc::array_t<t_uint8> m_buffer; }; static input_singletrack_factory_t<input_raw> g_input_raw_factory; DECLARE_COMPONENT_VERSION("RAW input","0.1","about message goes here"); DECLARE_FILE_TYPE("Raw files","*.RAW"); ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/foo_input_raw/readme.txt ================================================ Foo_input_raw is a sample component demonstrating implementation of a simple audio input service, use of filesystem APIs and service registration. ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/foo_input_validator/readme.txt ================================================ Decoder Validator v1.0 readme 1) Usage. Select a single file handled by the decoder you want to test, select "Utils / Decoder Validator" or "Utils / Tag Writer Validator" from the context menu. WARNING: Never run Tag Writer Validator on files you don't have backup copies of. Tag Writer Validator will repeatedly rewrite file's tags, with randomly generated info. In case of success (found problems with relevant tag writer implementations), the file will likely be permanently damaged or left with nonsense in its tags. 2) What Decoder Validator does. Decoder Validator runs a series of automated decoding tests on the specified file, and reports any abnormal behaviors detected. It has been proven to be a highly useful tool for spotting certain kinds of bugs (especially inaccurate seeking). Some of the problems it detects are potentially harmful (such as inaccurate seeking on archiving-oriented audio formats) and are hard to detect or reliably reproduce using other means. Detected programming errors include: - Different decoding results when decoding the same file again from start. - Inaccurate seeking, noncompliant seeking-related behaviors. - File corruption during tag updates. - Incorrect/inconsistent behaviors of tag writer instance when asked to reread tags after a tag update. 3) What Decoder Validator does not do. Decoder Validator does not: - Entirely ensure that your code is bug-free. - Check correctness of your decoder's output - only reference data it uses for comparisons is result of first decode pass, if your decoder consistently returns incorrect results, they won't be reported as incorrect. - Fully replace user testing cycle. - Test for exception transparency and abortability - abortability test has been excluded for performance reasons (since it was O(n^2)), you don't need automated tests to know whether your decoder implementation relays exceptions properly or not. - Test for unicode support in tag writers. - Test whether output of your tag writer is correct, only whether your own tag reader implementation reads it back and reports the same info as what it was asked to write. - Test freeform metadata handling - tag writing test is restricted to specific hardcoded fields filled with random data. 4) But one of official components fails Decoder Validator tests! This might occur because: - Limitations of file format being tested break certain features. - Bugs in third party libraries we have no power over (WMA) - You have found a bug. - The file you are trying to decode is corrupted and prevents decoder from returning results that the Validator would accept. ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/foobar2000_component_client/component_client.cpp ================================================ #include "../SDK/foobar2000.h" #include "../SDK/component.h" static HINSTANCE g_hIns; static pfc::string_simple g_name,g_full_path; static bool g_services_available = false, g_initialized = false; namespace core_api { HINSTANCE get_my_instance() { return g_hIns; } HWND get_main_window() { return g_api->get_main_window(); } pcchar get_my_file_name() { return g_name; } pcchar get_my_full_path() { return g_full_path; } bool are_services_available() { return g_services_available; } bool assert_main_thread() { return (g_services_available && g_api) ? g_api->assert_main_thread() : true; } void ensure_main_thread() { if (!assert_main_thread()) throw exception_wrong_thread(); } bool is_main_thread() { return (g_services_available && g_api) ? g_api->is_main_thread() : true; } pcchar get_profile_path() { return (g_services_available && g_api) ? g_api->get_profile_path() : 0; } bool is_shutting_down() { return (g_services_available && g_api) ? g_api->is_shutting_down() : g_initialized; } bool is_initializing() { return (g_services_available && g_api) ? g_api->is_initializing() : !g_initialized; } } namespace { class foobar2000_client_impl : public foobar2000_client { public: t_uint32 get_version() {return FOOBAR2000_CLIENT_VERSION;} pservice_factory_base get_service_list() {return service_factory_base::__internal__list;} void get_config(stream_writer * p_stream,abort_callback & p_abort) { cfg_var::config_write_file(p_stream,p_abort); } void set_config(stream_reader * p_stream,abort_callback & p_abort) { cfg_var::config_read_file(p_stream,p_abort); } void set_library_path(const char * path,const char * name) { g_full_path = path; g_name = name; } void services_init(bool val) { if (val) g_initialized = true; g_services_available = val; } bool is_debug() { #ifdef _DEBUG return true; #else return false; #endif } }; } static foobar2000_client_impl g_client; extern "C" { __declspec(dllexport) foobar2000_client * _cdecl foobar2000_get_interface(foobar2000_api * p_api,HINSTANCE hIns) { g_hIns = hIns; g_api = p_api; return &g_client; } } #if 0 BOOLEAN WINAPI DllMain(IN HINSTANCE hDllHandle, IN DWORD nReason, IN LPVOID Reserved ) { BOOLEAN bSuccess = TRUE; switch ( nReason ) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls( hDllHandle ); break; case DLL_PROCESS_DETACH: break; } return TRUE; } #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/StdAfx.cpp ================================================ // stdafx.cpp : source file that includes just the standard includes // foobar2000_sdk_helpers.pch will be the pre-compiled header // stdafx.obj will contain the pre-compiled type information #include "stdafx.h" ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/StdAfx.h ================================================ // stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, but // are changed infrequently // #if !defined(AFX_STDAFX_H__6356EC2B_6DD1_4BE8_935C_87ECBA8697E4__INCLUDED_) #define AFX_STDAFX_H__6356EC2B_6DD1_4BE8_935C_87ECBA8697E4__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "../SDK/foobar2000.h" #include "helpers.h" // TODO: reference additional headers your program requires here //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_STDAFX_H__6356EC2B_6DD1_4BE8_935C_87ECBA8697E4__INCLUDED_) ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/bitreader_helper.h ================================================ namespace bitreader_helper { inline static size_t extract_bit(const t_uint8 * p_stream,size_t p_offset) { return (p_stream[p_offset>>3] >> (7-(p_offset&7)))&1; } static size_t extract_int(const t_uint8 * p_stream,size_t p_base,size_t p_width) { size_t ret = 0; size_t offset = p_base; for(size_t bit=0;bit<p_width;bit++) { ret <<= 1; ret |= extract_bit(p_stream,offset++); } return ret; } class bitreader { public: inline bitreader(const t_uint8 * p_ptr,t_size p_base) : m_ptr(p_ptr), m_bitptr(p_base) { } inline void skip(t_size p_bits) { m_bitptr += p_bits; } template<typename t_ret> t_ret read_t(t_size p_bits) { t_ret ret = 0; for(t_size bit=0;bit<p_bits;bit++) { ret <<= 1; ret |= (m_ptr[m_bitptr>>3] >> (7-(m_bitptr&7)))&1; m_bitptr++; } return ret; } t_size read(t_size p_bits) {return read_t<t_size>(p_bits);} inline t_size get_bitptr() const {return m_bitptr;} inline bool read_bit() { bool state = ( (m_ptr[m_bitptr>>3] >> (7-(m_bitptr&7)))&1 ) != 0; m_bitptr++; return state; } private: const t_uint8 * m_ptr; t_size m_bitptr; }; class bitreader_fromfile { public: inline bitreader_fromfile(service_ptr_t<file> const& p_file) : m_file(p_file), m_buffer_ptr(0) {} t_size read(t_size p_bits,abort_callback & p_abort) { t_size ret = 0; for(t_size bit=0;bit<p_bits;bit++) { if (m_buffer_ptr == 0) m_file->read_object(&m_buffer,1,p_abort); ret <<= 1; ret |= (m_buffer >> (7-m_buffer_ptr))&1; m_buffer_ptr = (m_buffer_ptr+1) & 7; } return ret; } void skip(t_size p_bits,abort_callback & p_abort) { for(t_size bit=0;bit<p_bits;bit++) { if (m_buffer_ptr == 0) m_file->read_object(&m_buffer,1,p_abort); m_buffer_ptr = (m_buffer_ptr+1) & 7; } } inline void byte_align() {m_buffer_ptr = 0;} private: service_ptr_t<file> m_file; t_size m_buffer_ptr; t_uint8 m_buffer; }; class bitreader_limited { public: inline bitreader_limited(const t_uint8 * p_ptr,t_size p_base,t_size p_remaining) : m_reader(p_ptr,p_base), m_remaining(p_remaining) {} inline t_size get_bitptr() const {return m_reader.get_bitptr();} inline t_size get_remaining() const {return m_remaining;} inline void skip(t_size p_bits) { if (p_bits > m_remaining) throw exception_io_data_truncation(); m_remaining -= p_bits; m_reader.skip(p_bits); } t_size read(t_size p_bits) { if (p_bits > m_remaining) throw exception_io_data_truncation(); m_remaining -= p_bits; return m_reader.read(p_bits); } private: bitreader m_reader; t_size m_remaining; }; inline static t_size extract_bits(const t_uint8 * p_buffer,t_size p_base,t_size p_count) { return bitreader(p_buffer,p_base).read(p_count); } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/cfg_guidlist.h ================================================ class cfg_guidlist : public cfg_var, public pfc::list_t<GUID> { public: void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { t_uint32 n, m = pfc::downcast_guarded<t_uint32>(get_count()); p_stream->write_lendian_t(m,p_abort); for(n=0;n<m;n++) p_stream->write_lendian_t(get_item(n),p_abort); } void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { t_uint32 n,count; p_stream->read_lendian_t(count,p_abort); m_buffer.set_size(count); for(n=0;n<count;n++) { try { p_stream->read_lendian_t(m_buffer[n],p_abort); } catch(...) {m_buffer.set_size(0); throw;} } } void sort() {sort_t(pfc::guid_compare);} bool have_item_bsearch(const GUID & p_item) { t_size dummy; return bsearch_t(pfc::guid_compare,p_item,dummy); } public: cfg_guidlist(const GUID & p_guid) : cfg_var(p_guid) {} }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/cfg_structlist.h ================================================ template<typename T> class cfg_structlist_t : public cfg_var, public pfc::list_t<T> { public: void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { t_uint32 n, m = get_count(); p_stream->write_lendian_t(m,p_abort); for(n=0;n<m;n++) { p_stream->write_object(&m_buffer[n],sizeof(T),p_abort); } } void set_data_raw(stream_reader * p_stream,t_size,abort_callback & p_abort) { t_uint32 n,count; p_stream->read_lendian_t(count,p_abort); m_buffer.set_size_e(count); for(n=0;n<count;n++) { try { p_stream->read_object(&m_buffer[n],sizeof(T),p_abort); } catch(...) {m_buffer.set_size(0); throw;} } } public: cfg_structlist_t(const GUID & p_guid) : cfg_var(p_guid) {} }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/create_directory_helper.cpp ================================================ #include "stdafx.h" #include "create_directory_helper.h" namespace create_directory_helper { static void create_path_internal(const char * p_path,t_size p_base,abort_callback & p_abort) { pfc::string8_fastalloc temp; for(t_size walk = p_base; p_path[walk]; walk++) { if (p_path[walk] == '\\') { temp.set_string(p_path,walk); try {filesystem::g_create_directory(temp.get_ptr(),p_abort);} catch(exception_io_already_exists) {} } } } static bool is_valid_netpath_char(char p_char) { return pfc::char_is_ascii_alphanumeric(p_char) || p_char == '_' || p_char == '-'; } static bool test_localpath(const char * p_path) { if (pfc::strcmp_partial(p_path,"file://") == 0) p_path += strlen("file://"); return pfc::char_is_ascii_alpha(p_path[0]) && p_path[1] == ':' && p_path[2] == '\\'; } static bool test_netpath(const char * p_path) { if (pfc::strcmp_partial(p_path,"file://") == 0) p_path += strlen("file://"); if (*p_path != '\\') return false; p_path++; if (*p_path != '\\') return false; p_path++; if (!is_valid_netpath_char(*p_path)) return false; p_path++; while(is_valid_netpath_char(*p_path)) p_path++; if (*p_path != '\\') return false; return true; } void create_path(const char * p_path,abort_callback & p_abort) { if (test_localpath(p_path)) { t_size walk = 0; if (pfc::strcmp_partial(p_path,"file://") == 0) walk += strlen("file://"); create_path_internal(p_path,walk + 3,p_abort); } else if (test_netpath(p_path)) { t_size walk = 0; if (pfc::strcmp_partial(p_path,"file://") == 0) walk += strlen("file://"); while(p_path[walk] == '\\') walk++; while(p_path[walk] != 0 && p_path[walk] != '\\') walk++; while(p_path[walk] == '\\') walk++; create_path_internal(p_path,walk,p_abort); } else { throw exception_io("Could not create directory structure; unknown path format"); } } static bool is_bad_dirchar(char c) { return c==' ' || c=='.'; } void make_path(const char * parent,const char * filename,const char * extension,bool allow_new_dirs,pfc::string8 & out,bool really_create_dirs,abort_callback & p_abort) { out.reset(); if (parent && *parent) { out = parent; out.fix_dir_separator('\\'); } bool last_char_is_dir_sep = true; while(*filename) { #ifdef WIN32 if (allow_new_dirs && is_bad_dirchar(*filename)) { const char * ptr = filename+1; while(is_bad_dirchar(*ptr)) ptr++; if (*ptr!='\\' && *ptr!='/') out.add_string(filename,ptr-filename); filename = ptr; if (*filename==0) break; } #endif if (pfc::is_path_bad_char(*filename)) { if (allow_new_dirs && (*filename=='\\' || *filename=='/')) { if (!last_char_is_dir_sep) { if (really_create_dirs) try{filesystem::g_create_directory(out,p_abort);}catch(exception_io_already_exists){} out.add_char('\\'); last_char_is_dir_sep = true; } } else out.add_char('_'); } else { out.add_byte(*filename); last_char_is_dir_sep = false; } filename++; } if (out.length()>0 && out[out.length()-1]=='\\') { out.add_string("noname"); } if (extension && *extension) { out.add_char('.'); out.add_string(extension); } } } namespace { class titleformat_text_filter_impl_createdir : public titleformat_text_filter { public: void on_new_field() {} void write(const GUID & p_inputtype,pfc::string_receiver & p_out,const char * p_data,t_size p_data_length) {//not "UTF-8 aware" but coded not to clash with UTF-8, since only filtered chars are lower ASCII if (p_inputtype == titleformat_inputtypes::meta) { t_size index = 0; t_size good_bytes = 0; while(index < p_data_length && p_data[index] != 0) { unsigned char c = (unsigned char)p_data[index]; if (c < ' ' || c == '\\' || c=='/' || c=='|' || c==':') { if (good_bytes > 0) {p_out.add_string(p_data+index-good_bytes,good_bytes);good_bytes=0;} p_out.add_string("_",1); } else good_bytes++; index++; } if (good_bytes > 0) {p_out.add_string(p_data+index-good_bytes,good_bytes);good_bytes=0;} } else { p_out.add_string(p_data,p_data_length); } } }; } void create_directory_helper::format_filename(const metadb_handle_ptr & handle,titleformat_hook * p_hook,const char * spec,pfc::string8 & out) { pfc::string8 temp; handle->format_title_legacy(p_hook,temp,spec,&titleformat_text_filter_impl_createdir()); temp.replace_char('/','\\'); temp.fix_filename_chars('_','\\'); out = temp; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/create_directory_helper.h ================================================ #ifndef _CREATE_DIRECTORY_HELPER_H_ #define _CREATE_DIRECTORY_HELPER_H_ namespace create_directory_helper { void create_path(const char * p_path,abort_callback & p_abort); void make_path(const char * parent,const char * filename,const char * extension,bool allow_new_dirs,pfc::string8 & out,bool b_really_create_dirs,abort_callback & p_dir_create_abort); void format_filename(const metadb_handle_ptr & handle,titleformat_hook * p_hook,const char * spec,pfc::string8 & out); }; #endif//_CREATE_DIRECTORY_HELPER_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/cue_creator.cpp ================================================ #include "stdafx.h" namespace { class format_meta { public: format_meta(const file_info & p_source,const char * p_name,bool p_allow_space = true) { p_source.meta_format(p_name,m_buffer); m_buffer.replace_byte('\"','\''); uReplaceString(m_buffer,pfc::string8(m_buffer),infinite,"\x0d\x0a",2,"\\",1,false); if (!p_allow_space) m_buffer.replace_byte(' ','_'); m_buffer.replace_nontext_chars(); } inline operator const char*() const {return m_buffer;} private: pfc::string8_fastalloc m_buffer; }; } static bool is_meta_same_everywhere(const cue_creator::t_entry_list & p_list,const char * p_meta) { pfc::string8_fastalloc reference,temp; cue_creator::t_entry_list::const_iterator iter; iter = p_list.first(); if (!iter.is_valid()) return false; if (!iter->m_infos.meta_format(p_meta,reference)) return false; for(;iter.is_valid();++iter) { if (!iter->m_infos.meta_format(p_meta,temp)) return false; if (strcmp(temp,reference)!=0) return false; } return true; } static const char g_eol[] = "\r\n"; namespace cue_creator { void create(pfc::string_formatter & p_out,const t_entry_list & p_data) { if (p_data.get_count() == 0) return; bool album_artist_global = is_meta_same_everywhere(p_data,"album artist"), artist_global = is_meta_same_everywhere(p_data,"artist"), album_global = is_meta_same_everywhere(p_data,"album"), genre_global = is_meta_same_everywhere(p_data,"genre"), date_global = is_meta_same_everywhere(p_data,"date"), discid_global = is_meta_same_everywhere(p_data,"discid"), comment_global = is_meta_same_everywhere(p_data,"comment"), catalog_global = is_meta_same_everywhere(p_data,"catalog"), songwriter_global = is_meta_same_everywhere(p_data,"songwriter"); if (genre_global) { p_out << "REM GENRE " << format_meta(p_data.first()->m_infos,"genre") << g_eol; } if (date_global) { p_out << "REM DATE " << format_meta(p_data.first()->m_infos,"date") << g_eol; } if (discid_global) { p_out << "REM DISCID " << format_meta(p_data.first()->m_infos,"discid") << g_eol; } if (comment_global) { p_out << "REM COMMENT " << format_meta(p_data.first()->m_infos,"comment") << g_eol; } if (catalog_global) { p_out << "CATALOG " << format_meta(p_data.first()->m_infos,"catalog") << g_eol; } if (songwriter_global) { p_out << "SONGWRITER \"" << format_meta(p_data.first()->m_infos,"songwriter") << "\"" << g_eol; } if (album_artist_global) { p_out << "PERFORMER \"" << format_meta(p_data.first()->m_infos,"album artist") << "\"" << g_eol; artist_global = false; } else if (artist_global) { p_out << "PERFORMER \"" << format_meta(p_data.first()->m_infos,"artist") << "\"" << g_eol; } if (album_global) { p_out << "TITLE \"" << format_meta(p_data.first()->m_infos,"album") << "\"" << g_eol; } { replaygain_info::t_text_buffer rgbuffer; replaygain_info rg = p_data.first()->m_infos.get_replaygain(); if (rg.format_album_gain(rgbuffer)) p_out << "REM REPLAYGAIN_ALBUM_GAIN " << rgbuffer << g_eol; if (rg.format_album_peak(rgbuffer)) p_out << "REM REPLAYGAIN_ALBUM_PEAK " << rgbuffer << g_eol; } pfc::string8 last_file; for(t_entry_list::const_iterator iter = p_data.first();iter.is_valid();++iter) { if (strcmp(last_file,iter->m_file) != 0) { p_out << "FILE \"" << iter->m_file << "\" WAVE" << g_eol; last_file = iter->m_file; } p_out << " TRACK " << pfc::format_int(iter->m_track_number,2) << " AUDIO" << g_eol; if (iter->m_infos.meta_find("title") != infinite) p_out << " TITLE \"" << format_meta(iter->m_infos,"title") << "\"" << g_eol; if (!artist_global && iter->m_infos.meta_find("artist") != infinite) p_out << " PERFORMER \"" << format_meta(iter->m_infos,"artist") << "\"" << g_eol; if (!songwriter_global && iter->m_infos.meta_find("songwriter") != infinite) { p_out << " SONGWRITER \"" << format_meta(iter->m_infos,"songwriter") << "\"" << g_eol; } if (iter->m_infos.meta_find("isrc") != infinite) { p_out << " ISRC " << format_meta(iter->m_infos,"isrc") << g_eol; } if (!date_global && iter->m_infos.meta_find("date") != infinite) { p_out << " REM DATE " << format_meta(iter->m_infos,"date") << g_eol; } { replaygain_info::t_text_buffer rgbuffer; replaygain_info rg = iter->m_infos.get_replaygain(); if (rg.format_track_gain(rgbuffer)) p_out << " REM REPLAYGAIN_TRACK_GAIN " << rgbuffer << g_eol; if (rg.format_track_peak(rgbuffer)) p_out << " REM REPLAYGAIN_TRACK_PEAK " << rgbuffer << g_eol; } if (!iter->m_flags.is_empty()) { p_out << " FLAGS " << iter->m_flags << g_eol; } if (iter->m_index_list.m_positions[0] < iter->m_index_list.m_positions[1]) { if (iter->m_index_list.m_positions[0] < 0) p_out << " PREGAP " << cuesheet_format_index_time(iter->m_index_list.m_positions[1] - iter->m_index_list.m_positions[0]) << g_eol; else p_out << " INDEX 00 " << cuesheet_format_index_time(iter->m_index_list.m_positions[0]) << g_eol; } p_out << " INDEX 01 " << cuesheet_format_index_time(iter->m_index_list.m_positions[1]) << g_eol; for(unsigned n=2;n<t_cuesheet_index_list::count && iter->m_index_list.m_positions[n] > 0;n++) { p_out << " INDEX " << pfc::format_uint(n,2) << " " << cuesheet_format_index_time(iter->m_index_list.m_positions[n]) << g_eol; } // p_out << " INDEX 01 " << cuesheet_format_index_time(iter->m_offset) << g_eol; } } void t_entry::set_simple_index(double p_time) { m_index_list.reset(); m_index_list.m_positions[0] = m_index_list.m_positions[1] = p_time; } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/cue_creator.h ================================================ namespace cue_creator { struct t_entry { file_info_impl m_infos; pfc::string8 m_file,m_flags; unsigned m_track_number; t_cuesheet_index_list m_index_list; void set_simple_index(double p_time); }; typedef pfc::chain_list_t<t_entry> t_entry_list; void create(pfc::string_formatter & p_out,const pfc::chain_list_t<t_entry> & p_list); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/cue_parser.cpp ================================================ #include "stdafx.h" namespace { PFC_DECLARE_EXCEPTION(exception_cue,pfc::exception,"Invalid cuesheet"); } static bool is_numeric(char c) {return c>='0' && c<='9';} static bool is_spacing(char c) { return c == ' ' || c == '\t'; } static bool is_linebreak(char c) { return c == '\n' || c == '\r'; } static void validate_file_type(const char * p_type,t_size p_type_length) { if ( //standard types stricmp_utf8_ex(p_type,p_type_length,"WAVE",infinite) != 0 && stricmp_utf8_ex(p_type,p_type_length,"MP3",infinite) != 0 && stricmp_utf8_ex(p_type,p_type_length,"AIFF",infinite) != 0 && //common user-entered types stricmp_utf8_ex(p_type,p_type_length,"APE",infinite) != 0 && stricmp_utf8_ex(p_type,p_type_length,"FLAC",infinite) != 0 && stricmp_utf8_ex(p_type,p_type_length,"WV",infinite) != 0 && stricmp_utf8_ex(p_type,p_type_length,"WAVPACK",infinite) != 0 ) throw exception_cue(pfc::string_formatter() << "expected WAVE, MP3 or AIFF, got : \"" << pfc::string8(p_type,p_type_length) << "\""); } namespace { class NOVTABLE cue_parser_callback { public: virtual void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) = 0; virtual void on_track(unsigned p_index,const char * p_type,t_size p_type_length) = 0; virtual void on_pregap(unsigned p_value) = 0; virtual void on_index(unsigned p_index,unsigned p_value) = 0; virtual void on_title(const char * p_title,t_size p_title_length) = 0; virtual void on_performer(const char * p_performer,t_size p_performer_length) = 0; virtual void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) = 0; virtual void on_isrc(const char * p_isrc,t_size p_isrc_length) = 0; virtual void on_catalog(const char * p_catalog,t_size p_catalog_length) = 0; virtual void on_comment(const char * p_comment,t_size p_comment_length) = 0; virtual void on_flags(const char * p_flags,t_size p_flags_length) = 0; }; class NOVTABLE cue_parser_callback_meta : public cue_parser_callback { public: virtual void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) = 0; virtual void on_track(unsigned p_index,const char * p_type,t_size p_type_length) = 0; virtual void on_pregap(unsigned p_value) = 0; virtual void on_index(unsigned p_index,unsigned p_value) = 0; virtual void on_meta(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0; protected: static bool is_known_meta(const char * p_name,t_size p_length) { static const char * metas[] = {"genre","date","discid","comment","replaygain_track_gain","replaygain_track_peak","replaygain_album_gain","replaygain_album_peak"}; for(t_size n=0;n<tabsize(metas);n++) { if (!stricmp_utf8_ex(p_name,p_length,metas[n],infinite)) return true; } return false; } void on_comment(const char * p_comment,t_size p_comment_length) { unsigned ptr = 0; while(ptr < p_comment_length && !is_spacing(p_comment[ptr])) ptr++; if (is_known_meta(p_comment, ptr)) { unsigned name_length = ptr; while(ptr < p_comment_length && is_spacing(p_comment[ptr])) ptr++; if (ptr < p_comment_length) { if (p_comment[ptr] == '\"') { ptr++; unsigned value_base = ptr; while(ptr < p_comment_length && p_comment[ptr] != '\"') ptr++; if (ptr == p_comment_length) throw exception_cue("invalid REM syntax",0); if (ptr > value_base) on_meta(p_comment,name_length,p_comment + value_base,ptr - value_base); } else { unsigned value_base = ptr; while(ptr < p_comment_length /*&& !is_spacing(p_comment[ptr])*/) ptr++; if (ptr > value_base) on_meta(p_comment,name_length,p_comment + value_base,ptr - value_base); } } } } void on_title(const char * p_title,t_size p_title_length) { on_meta("title",infinite,p_title,p_title_length); } void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) { on_meta("songwriter",infinite,p_songwriter,p_songwriter_length); } void on_performer(const char * p_performer,t_size p_performer_length) { on_meta("artist",infinite,p_performer,p_performer_length); } void on_isrc(const char * p_isrc,t_size p_isrc_length) { on_meta("isrc",infinite,p_isrc,p_isrc_length); } void on_catalog(const char * p_catalog,t_size p_catalog_length) { on_meta("catalog",infinite,p_catalog,p_catalog_length); } void on_flags(const char * p_flags,t_size p_flags_length) {} }; class cue_parser_callback_retrievelist : public cue_parser_callback { public: cue_parser_callback_retrievelist(pfc::chain_list_t<cue_parser::cue_entry> & p_out) : m_out(p_out), m_track(0), m_pregap(0), m_index0_set(false), m_index1_set(false) { } void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) { validate_file_type(p_type,p_type_length); m_file.set_string(p_file,p_file_length); } void on_track(unsigned p_index,const char * p_type,t_size p_type_length) { if (stricmp_utf8_ex(p_type,p_type_length,"audio",infinite)) throw exception_cue("only tracks of type AUDIO supported",0); //if (p_index != m_track + 1) throw exception_cue("cuesheet tracks out of order"); if (m_track != 0) finalize_track(); if (m_file.is_empty()) throw exception_cue("declaring a track with no file set",0); m_trackfile = m_file; m_track = p_index; } void on_pregap(unsigned p_value) {m_pregap = (double) p_value / 75.0;} void on_index(unsigned p_index,unsigned p_value) { if (p_index < t_cuesheet_index_list::count) { switch(p_index) { case 0: m_index0_set = true; break; case 1: m_index1_set = true; break; } m_index_list.m_positions[p_index] = (double) p_value / 75.0; } } void on_title(const char * p_title,t_size p_title_length) {} void on_performer(const char * p_performer,t_size p_performer_length) {} void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) {} void on_isrc(const char * p_isrc,t_size p_isrc_length) {} void on_catalog(const char * p_catalog,t_size p_catalog_length) {} void on_comment(const char * p_comment,t_size p_comment_length) {} void on_flags(const char * p_flags,t_size p_flags_length) {} void finalize() { if (m_track != 0) { finalize_track(); m_track = 0; } } private: void finalize_track() { if (!m_index1_set) throw exception_cue("INDEX 01 not set",0); if (!m_index0_set) m_index_list.m_positions[0] = m_index_list.m_positions[1] - m_pregap; if (!m_index_list.is_valid()) throw exception_cue("invalid index list"); pfc::chain_list_t<cue_parser::cue_entry>::iterator iter; iter = m_out.insert_last(); if (m_trackfile.is_empty()) throw exception_cue("track has no file assigned",0); iter->m_file = m_trackfile; iter->m_track_number = m_track; iter->m_indexes = m_index_list; m_index_list.reset(); m_index0_set = false; m_index1_set = false; m_pregap = 0; } bool m_index0_set,m_index1_set; t_cuesheet_index_list m_index_list; double m_pregap; unsigned m_track; pfc::string8 m_file,m_trackfile; pfc::chain_list_t<cue_parser::cue_entry> & m_out; }; class cue_parser_callback_retrieveinfo : public cue_parser_callback_meta { public: cue_parser_callback_retrieveinfo(file_info & p_out,unsigned p_wanted_track) : m_out(p_out), m_wanted_track(p_wanted_track), m_track(0), m_is_va(false), m_index0_set(false), m_index1_set(false), m_pregap(0), m_totaltracks(0) {} void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {} void on_track(unsigned p_index,const char * p_type,t_size p_type_length) { if (p_index == 0) throw exception_cue("invalid TRACK index",0); if (p_index == m_wanted_track) { if (stricmp_utf8_ex(p_type,p_type_length,"audio",infinite)) throw exception_cue("only tracks of type AUDIO supported",0); } m_track = p_index; m_totaltracks++; } void on_pregap(unsigned p_value) {if (m_track == m_wanted_track) m_pregap = (double) p_value / 75.0;} void on_index(unsigned p_index,unsigned p_value) { if (m_track == m_wanted_track && p_index < t_cuesheet_index_list::count) { switch(p_index) { case 0: m_index0_set = true; break; case 1: m_index1_set = true; break; } m_indexes.m_positions[p_index] = (double) p_value / 75.0; } } void on_meta(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) { t_meta_list::iterator iter; if (m_track == 0) //globals { //convert global title to album if (!stricmp_utf8_ex(p_name,p_name_length,"title",infinite)) { p_name = "album"; p_name_length = 5; } else if (!stricmp_utf8_ex(p_name,p_name_length,"artist",infinite)) { m_album_artist.set_string(p_value,p_value_length); } iter = m_globals.insert_last(); } else { if (!m_is_va) { if (!stricmp_utf8_ex(p_name,p_name_length,"artist",infinite)) { if (!m_album_artist.is_empty()) { if (stricmp_utf8_ex(p_value,p_value_length,m_album_artist,m_album_artist.length())) m_is_va = true; } } } if (m_track == m_wanted_track) //locals { iter = m_locals.insert_last(); } } if (iter.is_valid()) { iter->m_name.set_string(p_name,p_name_length); iter->m_value.set_string(p_value,p_value_length); } } void finalize() { if (!m_index1_set) throw exception_cue("INDEX 01 not set",0); if (!m_index0_set) m_indexes.m_positions[0] = m_indexes.m_positions[1] - m_pregap; m_indexes.to_infos(m_out); replaygain_info rg; rg.reset(); t_meta_list::const_iterator iter; if (m_is_va) { //clean up VA mess t_meta_list::const_iterator iter_global,iter_local; iter_global = find_first_field(m_globals,"artist"); iter_local = find_first_field(m_locals,"artist"); if (iter_global.is_valid()) { m_out.meta_set("album artist",iter_global->m_value); if (iter_local.is_valid()) m_out.meta_set("artist",iter_local->m_value); else m_out.meta_set("artist",iter_global->m_value); } else { if (iter_local.is_valid()) m_out.meta_set("artist",iter_local->m_value); } wipe_field(m_globals,"artist"); wipe_field(m_locals,"artist"); } for(iter=m_globals.first();iter.is_valid();iter++) { if (!rg.set_from_meta(iter->m_name,iter->m_value)) m_out.meta_set(iter->m_name,iter->m_value); } for(iter=m_locals.first();iter.is_valid();iter++) { if (!rg.set_from_meta(iter->m_name,iter->m_value)) m_out.meta_set(iter->m_name,iter->m_value); } m_out.meta_set("tracknumber",pfc::string_formatter() << m_wanted_track); m_out.meta_set("totaltracks", pfc::string_formatter() << m_totaltracks); m_out.set_replaygain(rg); } private: struct t_meta_entry { pfc::string8 m_name,m_value; }; typedef pfc::chain_list_t<t_meta_entry> t_meta_list; static t_meta_list::const_iterator find_first_field(t_meta_list const & p_list,const char * p_field) { t_meta_list::const_iterator iter; for(iter=p_list.first();iter.is_valid();++iter) { if (!stricmp_utf8(p_field,iter->m_name)) return iter; } return t_meta_list::const_iterator();//null iterator } static void wipe_field(t_meta_list & p_list,const char * p_field) { t_meta_list::iterator iter; for(iter=p_list.first();iter.is_valid();) { if (!stricmp_utf8(p_field,iter->m_name)) { t_meta_list::iterator temp = iter; ++temp; p_list.remove_single(iter); iter = temp; } else { ++iter; } } } t_meta_list m_globals,m_locals; file_info & m_out; unsigned m_wanted_track, m_track,m_totaltracks; pfc::string8 m_album_artist; bool m_is_va; t_cuesheet_index_list m_indexes; bool m_index0_set,m_index1_set; double m_pregap; }; }; static void g_parse_cue_line(const char * p_line,t_size p_line_length,cue_parser_callback & p_callback) { t_size ptr = 0; while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++; if (!stricmp_utf8_ex(p_line,ptr,"file",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; t_size file_base,file_length, type_base,type_length; if (p_line[ptr] == '\"') { ptr++; file_base = ptr; while(ptr < p_line_length && p_line[ptr] != '\"') ptr++; if (ptr == p_line_length) throw exception_cue("invalid FILE syntax",0); file_length = ptr - file_base; ptr++; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; } else { file_base = ptr; while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++; file_length = ptr - file_base; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; } type_base = ptr; while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++; type_length = ptr - type_base; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr != p_line_length || file_length == 0 || type_length == 0) throw exception_cue("invalid FILE syntax",0); p_callback.on_file(p_line + file_base, file_length, p_line + type_base, type_length); } else if (!stricmp_utf8_ex(p_line,ptr,"track",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; t_size track_base = ptr, track_length; while(ptr < p_line_length && !is_spacing(p_line[ptr])) { if (!is_numeric(p_line[ptr])) throw exception_cue("invalid TRACK syntax",0); ptr++; } track_length = ptr - track_base; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; t_size type_base = ptr, type_length; while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++; type_length = ptr - type_base; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr != p_line_length || type_length == 0) throw exception_cue("invalid TRACK syntax",0); unsigned track = pfc::atoui_ex(p_line+track_base,track_length); if (track < 1 || track > 99) throw exception_cue("invalid track number",0); p_callback.on_track(track,p_line + type_base, type_length); } else if (!stricmp_utf8_ex(p_line,ptr,"index",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; t_size index_base,index_length, time_base,time_length; index_base = ptr; while(ptr < p_line_length && !is_spacing(p_line[ptr])) { if (!is_numeric(p_line[ptr])) throw exception_cue("invalid INDEX syntax",0); ptr++; } index_length = ptr - index_base; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; time_base = ptr; while(ptr < p_line_length && !is_spacing(p_line[ptr])) { if (!is_numeric(p_line[ptr]) && p_line[ptr] != ':') throw exception_cue("invalid INDEX syntax",0); ptr++; } time_length = ptr - time_base; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr != p_line_length || index_length == 0 || time_length == 0) throw exception_cue("invalid INDEX syntax",0); unsigned index = pfc::atoui_ex(p_line+index_base,index_length); if (index > 99) throw exception_cue("invalid INDEX syntax",0); unsigned time = cuesheet_parse_index_time_ticks_e(p_line + time_base,time_length); p_callback.on_index(index,time); } else if (!stricmp_utf8_ex(p_line,ptr,"pregap",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; t_size time_base, time_length; time_base = ptr; while(ptr < p_line_length && !is_spacing(p_line[ptr])) { if (!is_numeric(p_line[ptr]) && p_line[ptr] != ':') throw exception_cue("invalid PREGAP syntax",0); ptr++; } time_length = ptr - time_base; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr != p_line_length || time_length == 0) throw exception_cue("invalid PREGAP syntax",0); unsigned time = cuesheet_parse_index_time_ticks_e(p_line + time_base,time_length); p_callback.on_pregap(time); } else if (!stricmp_utf8_ex(p_line,ptr,"title",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr == p_line_length) throw exception_cue("invalid TITLE syntax",0); if (p_line[ptr] == '\"') { ptr++; t_size base = ptr; while(ptr < p_line_length && p_line[ptr] != '\"') ptr++; if (ptr == p_line_length) throw exception_cue("invalid TITLE syntax",0); t_size length = ptr-base; ptr++; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr != p_line_length) throw exception_cue("invalid TITLE syntax",0); p_callback.on_title(p_line+base,length); } else { p_callback.on_title(p_line+ptr,p_line_length-ptr); } } else if (!stricmp_utf8_ex(p_line,ptr,"performer",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr == p_line_length) throw exception_cue("invalid PERFORMER syntax",0); if (p_line[ptr] == '\"') { ptr++; t_size base = ptr; while(ptr < p_line_length && p_line[ptr] != '\"') ptr++; if (ptr == p_line_length) throw exception_cue("invalid PERFORMER syntax",0); t_size length = ptr-base; ptr++; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr != p_line_length) throw exception_cue("invalid PERFORMER syntax",0); p_callback.on_performer(p_line+base,length); } else { p_callback.on_performer(p_line+ptr,p_line_length-ptr); } } else if (!stricmp_utf8_ex(p_line,ptr,"songwriter",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr == p_line_length) throw exception_cue("invalid SONGWRITER syntax",0); if (p_line[ptr] == '\"') { ptr++; t_size base = ptr; while(ptr < p_line_length && p_line[ptr] != '\"') ptr++; if (ptr == p_line_length) throw exception_cue("invalid SONGWRITER syntax",0); t_size length = ptr-base; ptr++; while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr != p_line_length) throw exception_cue("invalid SONGWRITER syntax",0); p_callback.on_songwriter(p_line+base,length); } else { p_callback.on_songwriter(p_line+ptr,p_line_length-ptr); } } else if (!stricmp_utf8_ex(p_line,ptr,"isrc",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; t_size length = p_line_length - ptr; if (length == 0) throw exception_cue("invalid ISRC syntax",0); p_callback.on_isrc(p_line+ptr,length); } else if (!stricmp_utf8_ex(p_line,ptr,"catalog",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; t_size length = p_line_length - ptr; if (length == 0) throw exception_cue("invalid CATALOG syntax",0); p_callback.on_catalog(p_line+ptr,length); } else if (!stricmp_utf8_ex(p_line,ptr,"flags",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr < p_line_length) p_callback.on_flags(p_line + ptr, p_line_length - ptr); } else if (!stricmp_utf8_ex(p_line,ptr,"rem",infinite)) { while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++; if (ptr < p_line_length) p_callback.on_comment(p_line + ptr, p_line_length - ptr); } else if (!stricmp_utf8_ex(p_line,ptr,"postgap",infinite)) { throw exception_cue("POSTGAP is not supported",0); } else if (!stricmp_utf8_ex(p_line,ptr,"cdtextfile",infinite)) { //do nothing } else throw exception_cue("unknown cuesheet item",0); } static void g_parse_cue(const char * p_cuesheet,cue_parser_callback & p_callback) { const char * parseptr = p_cuesheet; t_size lineidx = 1; while(*parseptr) { while(is_spacing(*parseptr)) *parseptr++; if (*parseptr) { t_size length = 0; while(parseptr[length] && !is_linebreak(parseptr[length])) length++; if (length > 0) { try { g_parse_cue_line(parseptr,length,p_callback); } catch(exception_cue const & e) {//rethrow with line info throw exception_cue(pfc::string_formatter() << e.what() << " (line " << lineidx << ")"); } } parseptr += length; while(is_linebreak(*parseptr)) { if (*parseptr == '\n') lineidx++; parseptr++; } } } } void cue_parser::parse(const char *p_cuesheet,pfc::chain_list_t<cue_entry> & p_out) { try { cue_parser_callback_retrievelist callback(p_out); g_parse_cue(p_cuesheet,callback); callback.finalize(); } catch(exception_cue const & e) { throw exception_bad_cuesheet(pfc::string_formatter() << "Error parsing cuesheet: " << e.what()); } } void cue_parser::parse_info(const char * p_cuesheet,file_info & p_info,unsigned p_index) { try { cue_parser_callback_retrieveinfo callback(p_info,p_index); g_parse_cue(p_cuesheet,callback); callback.finalize(); } catch(exception_cue const & e) { throw exception_bad_cuesheet(pfc::string_formatter() << "Error parsing cuesheet: " << e.what()); } } namespace { class cue_parser_callback_retrievecount : public cue_parser_callback { public: cue_parser_callback_retrievecount() : m_count(0) {} unsigned get_count() const {return m_count;} void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {} void on_track(unsigned p_index,const char * p_type,t_size p_type_length) {m_count++;} void on_pregap(unsigned p_value) {} void on_index(unsigned p_index,unsigned p_value) {} void on_title(const char * p_title,t_size p_title_length) {} void on_performer(const char * p_performer,t_size p_performer_length) {} void on_isrc(const char * p_isrc,t_size p_isrc_length) {} void on_catalog(const char * p_catalog,t_size p_catalog_length) {} void on_comment(const char * p_comment,t_size p_comment_length) {} void on_flags(const char * p_flags,t_size p_flags_length) {} private: unsigned m_count; }; class cue_parser_callback_retrievecreatorentries : public cue_parser_callback { public: cue_parser_callback_retrievecreatorentries(cue_creator::t_entry_list & p_out) : m_out(p_out), m_track(0), m_pregap(0), m_index0_set(false), m_index1_set(false) {} void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) { validate_file_type(p_type,p_type_length); m_file.set_string(p_file,p_file_length); } void on_track(unsigned p_index,const char * p_type,t_size p_type_length) { if (stricmp_utf8_ex(p_type,p_type_length,"audio",infinite)) throw exception_cue("only tracks of type AUDIO supported",0); //if (p_index != m_track + 1) throw exception_cue("cuesheet tracks out of order",0); if (m_track != 0) finalize_track(); if (m_file.is_empty()) throw exception_cue("declaring a track with no file set",0); m_trackfile = m_file; m_track = p_index; } void on_pregap(unsigned p_value) { m_pregap = (double) p_value / 75.0; } void on_index(unsigned p_index,unsigned p_value) { if (p_index < t_cuesheet_index_list::count) { switch(p_index) { case 0: m_index0_set = true; break; case 1: m_index1_set = true; break; } m_indexes.m_positions[p_index] = (double) p_value / 75.0; } } void on_title(const char * p_title,t_size p_title_length) {} void on_performer(const char * p_performer,t_size p_performer_length) {} void on_songwriter(const char * p_performer,t_size p_performer_length) {} void on_isrc(const char * p_isrc,t_size p_isrc_length) {} void on_catalog(const char * p_catalog,t_size p_catalog_length) {} void on_comment(const char * p_comment,t_size p_comment_length) {} void finalize() { if (m_track != 0) { finalize_track(); m_track = 0; } } void on_flags(const char * p_flags,t_size p_flags_length) { m_flags.set_string(p_flags,p_flags_length); } private: void finalize_track() { if (m_track < 1 || m_track > 99) throw exception_cue("track number out of range",0); if (!m_index1_set) throw exception_cue("INDEX 01 not set",0); if (!m_index0_set) m_indexes.m_positions[0] = m_indexes.m_positions[1] - m_pregap; if (!m_indexes.is_valid()) throw exception_cue("invalid index list"); cue_creator::t_entry_list::iterator iter; iter = m_out.insert_last(); iter->m_track_number = m_track; iter->m_file = m_trackfile; iter->m_index_list = m_indexes; iter->m_flags = m_flags; m_pregap = 0; m_indexes.reset(); m_index0_set = m_index1_set = false; m_flags.reset(); } bool m_index0_set,m_index1_set; double m_pregap; unsigned m_track; cue_creator::t_entry_list & m_out; pfc::string8 m_file,m_trackfile,m_flags; t_cuesheet_index_list m_indexes; }; } void cue_parser::parse_full(const char * p_cuesheet,cue_creator::t_entry_list & p_out) { try { { cue_parser_callback_retrievecreatorentries callback(p_out); g_parse_cue(p_cuesheet,callback); callback.finalize(); } { cue_creator::t_entry_list::iterator iter; for(iter=p_out.first();iter.is_valid();++iter) { cue_parser_callback_retrieveinfo callback(iter->m_infos,iter->m_track_number); g_parse_cue(p_cuesheet,callback); callback.finalize(); } } } catch(exception_cue const & e) { throw exception_bad_cuesheet(pfc::string_formatter() << "Error parsing cuesheet: " << e.what()); } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/cue_parser.h ================================================ //HINT: for info on how to generate an embedded cuesheet enabled input, see the end of this header. //to be moved somewhere else later namespace file_info_record_helper { class __file_info_record__info__enumerator { public: __file_info_record__info__enumerator(file_info & p_out) : m_out(p_out) {} void operator() (const char * p_name,const char * p_value) {m_out.__info_add_unsafe(p_name,p_value);} private: file_info & m_out; }; class __file_info_record__meta__enumerator { public: __file_info_record__meta__enumerator(file_info & p_out) : m_out(p_out) {} template<typename t_value> void operator() (const char * p_name,const t_value & p_value) { t_size index = infinite; for(typename t_value::const_iterator iter = p_value.first(); iter.is_valid(); ++iter) { if (index == infinite) index = m_out.__meta_add_unsafe(p_name,*iter); else m_out.meta_add_value(index,*iter); } } private: file_info & m_out; }; class file_info_record { public: typedef pfc::chain_list_t<pfc::string8> t_meta_value; typedef pfc::map_t<pfc::string8,t_meta_value,file_info::field_name_comparator> t_meta_map; typedef pfc::map_t<pfc::string8,pfc::string8,file_info::field_name_comparator> t_info_map; file_info_record() : m_replaygain(replaygain_info_invalid), m_length(0) {} replaygain_info get_replaygain() const {return m_replaygain;} void set_replaygain(const replaygain_info & p_replaygain) {m_replaygain = p_replaygain;} double get_length() const {return m_length;} void set_length(double p_length) {m_length = p_length;} void reset() { m_meta.remove_all(); m_info.remove_all(); m_length = 0; m_replaygain = replaygain_info_invalid; } void from_info_overwrite_info(const file_info & p_info) { for(t_size infowalk = 0, infocount = p_info.info_get_count(); infowalk < infocount; ++infowalk) { m_info.set(p_info.info_enum_name(infowalk),p_info.info_enum_value(infowalk)); } } void from_info_overwrite_meta(const file_info & p_info) { for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) { const t_size valuecount = p_info.meta_enum_value_count(metawalk); if (valuecount > 0) { t_meta_value & entry = m_meta.find_or_add(p_info.meta_enum_name(metawalk)); entry.remove_all(); for(t_size valuewalk = 0; valuewalk < valuecount; ++valuewalk) { entry.insert_last(p_info.meta_enum_value(metawalk,valuewalk)); } } } } void from_info_overwrite_rg(const file_info & p_info) { m_replaygain = replaygain_info::g_merge(m_replaygain,p_info.get_replaygain()); } template<typename t_source> void overwrite_meta(const t_source & p_meta) { m_meta.overwrite(p_meta); } template<typename t_source> void overwrite_info(const t_source & p_info) { m_info.overwrite(p_info); } void merge_overwrite(const file_info & p_info) { from_info_overwrite_info(p_info); from_info_overwrite_meta(p_info); from_info_overwrite_rg(p_info); } void transfer_meta_entry(const char * p_name,const file_info & p_info,t_size p_index) { const t_size count = p_info.meta_enum_value_count(p_index); if (count == 0) { m_meta.remove(p_name); } else { t_meta_value & val = m_meta.find_or_add(p_name); val.remove_all(); for(t_size walk = 0; walk < count; ++walk) { val.insert_last(p_info.meta_enum_value(p_index,walk)); } } } void meta_set(const char * p_name,const char * p_value) { m_meta.find_or_add(p_name).set_single(p_value); } const t_meta_value * meta_query_ptr(const char * p_name) const { return m_meta.query_ptr(p_name); } void from_info_set_meta(const file_info & p_info) { m_meta.remove_all(); from_info_overwrite_meta(p_info); } void from_info(const file_info & p_info) { reset(); m_length = p_info.get_length(); m_replaygain = p_info.get_replaygain(); from_info_overwrite_meta(p_info); from_info_overwrite_info(p_info); } void to_info(file_info & p_info) const { p_info.reset(); p_info.set_length(m_length); p_info.set_replaygain(m_replaygain); m_info.enumerate(__file_info_record__info__enumerator(p_info)); m_meta.enumerate(__file_info_record__meta__enumerator(p_info)); } template<typename t_callback> void enumerate_meta(t_callback & p_callback) const {m_meta.enumerate(p_callback);} template<typename t_callback> void enumerate_meta(t_callback & p_callback) {m_meta.enumerate(p_callback);} //private: t_meta_map m_meta; t_info_map m_info; replaygain_info m_replaygain; double m_length; }; }//namespace file_info_record_helper namespace cue_parser { struct cue_entry { pfc::string8 m_file; unsigned m_track_number; t_cuesheet_index_list m_indexes; }; PFC_DECLARE_EXCEPTION(exception_bad_cuesheet,exception_io_data,"Invalid cuesheet"); //! Throws exception_bad_cuesheet on failure. void parse(const char *p_cuesheet,pfc::chain_list_t<cue_entry> & p_out); //! Throws exception_bad_cuesheet on failure. void parse_info(const char *p_cuesheet,file_info & p_info,unsigned p_index); //! Throws exception_bad_cuesheet on failure. void parse_full(const char * p_cuesheet,cue_creator::t_entry_list & p_out); struct track_record { file_info_record_helper::file_info_record m_info; pfc::string8 m_file,m_flags; t_cuesheet_index_list m_index_list; }; typedef pfc::map_t<unsigned,track_record> track_record_list; class embeddedcue_metadata_manager { public: void get_tag(file_info & p_info) const; void set_tag(file_info const & p_info); void get_track_info(unsigned p_track,file_info & p_info) const; void set_track_info(unsigned p_track,file_info const & p_info); void query_track_offsets(unsigned p_track,double & p_begin,double & p_length) const; bool have_cuesheet() const; unsigned remap_trackno(unsigned p_index) const; t_size get_cue_track_count() const; private: track_record_list m_content; }; class __decoder_wrapper { public: virtual bool run(audio_chunk & p_chunk,abort_callback & p_abort) = 0; virtual void seek(double p_seconds,abort_callback & p_abort) = 0; virtual bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) = 0; virtual bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) = 0; virtual void on_idle(abort_callback & p_abort) = 0; virtual ~__decoder_wrapper() {} }; template<typename t_input> class __decoder_wrapper_simple : public __decoder_wrapper { public: void initialize(service_ptr_t<file> p_filehint,const char * p_path,unsigned p_flags,abort_callback & p_abort) { m_input.open(p_filehint,p_path,input_open_decode,p_abort); m_input.decode_initialize(p_flags,p_abort); } bool run(audio_chunk & p_chunk,abort_callback & p_abort) {return m_input.decode_run(p_chunk,p_abort);} void seek(double p_seconds,abort_callback & p_abort) {m_input.decode_seek(p_seconds,p_abort);} bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {return m_input.decode_get_dynamic_info(p_out,p_timestamp_delta);} bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {return m_input.decode_get_dynamic_info_track(p_out,p_timestamp_delta);} void on_idle(abort_callback & p_abort) {m_input.decode_on_idle(p_abort);} private: t_input m_input; }; class __decoder_wrapper_cue : public __decoder_wrapper { public: void open(service_ptr_t<file> p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,double p_start,double p_length) { m_input.open(p_filehint,p_location,p_flags,p_abort,p_start,p_length); } bool run(audio_chunk & p_chunk,abort_callback & p_abort) {return m_input.run(p_chunk,p_abort);} void seek(double p_seconds,abort_callback & p_abort) {m_input.seek(p_seconds,p_abort);} bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {return m_input.get_dynamic_info(p_out,p_timestamp_delta);} bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {return m_input.get_dynamic_info_track(p_out,p_timestamp_delta);} void on_idle(abort_callback & p_abort) {m_input.on_idle(p_abort);} private: input_helper_cue m_input; }; template<typename t_base> class input_wrapper_cue_t { public: input_wrapper_cue_t() {} ~input_wrapper_cue_t() {} void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) { m_path = p_path; m_file = p_filehint; input_open_file_helper(m_file,p_path,p_reason,p_abort); { file_info_impl info; t_base instance; instance.open(m_file,p_path,p_reason,p_abort); instance.get_info(info,p_abort); m_meta.set_tag(info); } } t_uint32 get_subsong_count() { return m_meta.have_cuesheet() ? m_meta.get_cue_track_count() : 1; } t_uint32 get_subsong(t_uint32 p_index) { return m_meta.have_cuesheet() ? m_meta.remap_trackno(p_index) : 0; } void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) { if (p_subsong == 0) { m_meta.get_tag(p_info); } else { m_meta.get_track_info(p_subsong,p_info); } } t_filestats get_file_stats(abort_callback & p_abort) {return m_file->get_stats(p_abort);} void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) { m_decoder.release(); if (p_subsong == 0) { pfc::rcptr_t<__decoder_wrapper_simple<t_base> > temp; temp.new_t(); m_file->reopen(p_abort); temp->initialize(m_file,m_path,p_flags,p_abort); m_decoder = temp; } else { double start,length; m_meta.query_track_offsets(p_subsong,start,length); pfc::rcptr_t<__decoder_wrapper_cue> temp; temp.new_t(); m_file->reopen(p_abort); temp->open(m_file,make_playable_location(m_path,0),p_flags & ~input_flag_no_seeking,p_abort,start,length); m_decoder = temp; } } bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) { return m_decoder->run(p_chunk,p_abort); } void decode_seek(double p_seconds,abort_callback & p_abort) { m_decoder->seek(p_seconds,p_abort); } bool decode_can_seek() {return true;} bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) { return m_decoder->get_dynamic_info(p_out,p_timestamp_delta); } bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) { return m_decoder->get_dynamic_info_track(p_out,p_timestamp_delta); } void decode_on_idle(abort_callback & p_abort) { m_decoder->on_idle(p_abort); } void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) { pfc::dynamic_assert(m_decoder.is_empty()); if (p_subsong == 0) { m_meta.set_tag(p_info); } else { m_meta.set_track_info(p_subsong,p_info); } } void retag_commit(abort_callback & p_abort) { pfc::dynamic_assert(m_decoder.is_empty()); file_info_impl info; m_meta.get_tag(info); { t_base instance; m_file->reopen(p_abort); instance.open(m_file,m_path,input_open_info_write,p_abort); instance.retag(pfc::safe_cast<const file_info&>(info),p_abort); info.reset(); instance.get_info(info,p_abort); m_meta.set_tag(info); } } inline static bool g_is_our_content_type(const char * p_content_type) {return t_base::g_is_our_content_type(p_content_type);} inline static bool g_is_our_path(const char * p_path,const char * p_extension) {return t_base::g_is_our_path(p_path,p_extension);} private: pfc::rcptr_t<__decoder_wrapper> m_decoder; file_info_impl m_info; pfc::string8 m_path; service_ptr_t<file> m_file; embeddedcue_metadata_manager m_meta; }; template<typename I> class chapterizer_impl_t : public chapterizer { public: bool is_our_file(const char * p_path,abort_callback & p_abort) { return I::g_is_our_path(p_path,pfc::string_extension(p_path)); } void set_chapters(const char * p_path,chapter_list const & p_list,abort_callback & p_abort) { input_wrapper_cue_t<I> instance; instance.open(0,p_path,input_open_info_write,p_abort); //stamp the cuesheet first { file_info_impl info; instance.get_info(0,info,p_abort); pfc::string_formatter cuesheet; { cue_creator::t_entry_list entries; t_size n, m = p_list.get_chapter_count(); double offset_acc = 0; for(n=0;n<m;n++) { cue_creator::t_entry_list::iterator entry; entry = entries.insert_last(); entry->m_infos = p_list.get_info(n); entry->m_file = "CDImage.wav"; entry->m_track_number = (unsigned)(n+1); entry->m_index_list.from_infos(entry->m_infos,offset_acc); offset_acc += entry->m_infos.get_length(); } cue_creator::create(cuesheet,entries); } info.meta_set("cuesheet",cuesheet); instance.retag_set_info(0,info,p_abort); } //stamp per-chapter infos for(t_size walk = 0, total = p_list.get_chapter_count(); walk < total; ++walk) { instance.retag_set_info(walk + 1, p_list.get_info(walk),p_abort); } instance.retag_commit(p_abort); } void get_chapters(const char * p_path,chapter_list & p_list,abort_callback & p_abort) { input_wrapper_cue_t<I> instance; instance.open(0,p_path,input_open_info_read,p_abort); const t_uint32 total = instance.get_subsong_count(); p_list.set_chapter_count(total); for(t_uint32 walk = 0; walk < total; ++walk) { file_info_impl info; instance.get_info(instance.get_subsong(walk),info,p_abort); p_list.set_info(walk,info); } } }; }; //! Wrapper template for generating embedded cuesheet enabled inputs. //! t_input_impl is a singletrack input implementation (see input_singletrack_impl for method declarations). //! To declare an embedded cuesheet enabled input, change your input declaration from input_singletrack_factory_t<myinput> to input_cuesheet_factory_t<myinput>. template<typename t_input_impl> class input_cuesheet_factory_t { public: input_factory_t<cue_parser::input_wrapper_cue_t<t_input_impl> > m_input_factory; service_factory_single_t<cue_parser::chapterizer_impl_t<t_input_impl> > m_chapterizer_factory; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/cue_parser_embedding.cpp ================================================ #include "stdafx.h" using namespace cue_parser; using namespace file_info_record_helper; static void build_cue_meta_name(const char * p_name,unsigned p_tracknumber,pfc::string_base & p_out) { p_out.reset(); p_out << "cue_track" << pfc::format_uint(p_tracknumber % 100,2) << "_" << p_name; } static bool is_reserved_meta_entry(const char * p_name) { return file_info::field_name_comparator::compare(p_name,"cuesheet") == 0; } static bool is_global_meta_entry(const char * p_name) { static const char header[] = "cue_track"; return pfc::stricmp_ascii_ex(p_name,strlen(header),header,infinite) != 0; } static bool is_allowed_field(const char * p_name) { return !is_reserved_meta_entry(p_name) && is_global_meta_entry(p_name); } namespace { class __get_tag_cue_track_list_builder { public: __get_tag_cue_track_list_builder(cue_creator::t_entry_list & p_entries) : m_entries(p_entries) {} void operator() (unsigned p_trackno,const track_record & p_record) { if (p_trackno > 0) { cue_creator::t_entry_list::iterator iter = m_entries.insert_last(); iter->m_file = p_record.m_file; iter->m_flags = p_record.m_flags; iter->m_index_list = p_record.m_index_list; iter->m_track_number = p_trackno; p_record.m_info.to_info(iter->m_infos); } } private: cue_creator::t_entry_list & m_entries; }; typedef pfc::avltree_t<pfc::string8,file_info::field_name_comparator> field_name_list; class __get_tag__enum_fields_enumerator { public: __get_tag__enum_fields_enumerator(field_name_list & p_out) : m_out(p_out) {} void operator() (unsigned p_trackno,const track_record & p_record) { if (p_trackno > 0) p_record.m_info.enumerate_meta(*this); } template<typename t_value> void operator() (const char * p_name,const t_value & p_value) { m_out.add(p_name); } private: field_name_list & m_out; }; class __get_tag__is_field_global_check { private: typedef file_info_record::t_meta_value t_value; public: __get_tag__is_field_global_check(const char * p_field) : m_field(p_field), m_value(NULL), m_state(true) {} void operator() (unsigned p_trackno,const track_record & p_record) { if (p_trackno > 0 && m_state) { const t_value * val = p_record.m_info.meta_query_ptr(m_field); if (val == NULL) {m_state = false; return;} if (m_value == NULL) { m_value = val; } else { if (pfc::comparator_chainlist<pfc::comparator_strcmp>::compare(*m_value,*val) != 0) { m_state = false; return; } } } } void finalize(file_info_record::t_meta_map & p_globals) { if (m_state && m_value != NULL) { p_globals.set(m_field,*m_value); } } private: const char * const m_field; const t_value * m_value; bool m_state; }; class __get_tag__filter_globals { public: __get_tag__filter_globals(track_record_list const & p_tracks,file_info_record::t_meta_map & p_globals) : m_tracks(p_tracks), m_globals(p_globals) {} void operator() (const char * p_field) { if (is_allowed_field(p_field)) { __get_tag__is_field_global_check wrapper(p_field); m_tracks.enumerate(wrapper); wrapper.finalize(m_globals); } } private: const track_record_list & m_tracks; file_info_record::t_meta_map & m_globals; }; class __get_tag__local_field_filter { public: __get_tag__local_field_filter(const file_info_record::t_meta_map & p_globals,file_info_record::t_meta_map & p_output) : m_globals(p_globals), m_output(p_output), m_currenttrack(0) {} void operator() (unsigned p_trackno,const track_record & p_track) { if (p_trackno > 0) { m_currenttrack = p_trackno; p_track.m_info.enumerate_meta(*this); } } void operator() (const char * p_name,const file_info_record::t_meta_value & p_value) { PFC_ASSERT(m_currenttrack > 0); if (!m_globals.have_item(p_name)) { build_cue_meta_name(p_name,m_currenttrack,m_buffer); m_output.set(m_buffer,p_value); } } private: unsigned m_currenttrack; pfc::string8_fastalloc m_buffer; const file_info_record::t_meta_map & m_globals; file_info_record::t_meta_map & m_output; }; }; static void strip_redundant_track_meta(unsigned p_tracknumber,const file_info & p_cueinfo,file_info_record::t_meta_map & p_meta,const char * p_metaname) { t_size metaindex = p_cueinfo.meta_find(p_metaname); if (metaindex == infinite) return; pfc::string_formatter namelocal; build_cue_meta_name(p_metaname,p_tracknumber,namelocal); { const file_info_record::t_meta_value * val = p_meta.query_ptr(namelocal); if (val == NULL) return; file_info_record::t_meta_value::const_iterator iter = val->first(); for(t_size valwalk = 0, valcount = p_cueinfo.meta_enum_value_count(metaindex); valwalk < valcount; ++valwalk) { if (iter.is_empty()) return; if (strcmp(*iter,p_cueinfo.meta_enum_value(metaindex,valwalk)) != 0) return; ++iter; } if (!iter.is_empty()) return; } //success p_meta.remove(namelocal); } void embeddedcue_metadata_manager::get_tag(file_info & p_info) const { if (!have_cuesheet()) { m_content.query_ptr((unsigned)0)->m_info.to_info(p_info); p_info.meta_remove_field("cuesheet"); } else { cue_creator::t_entry_list entries; m_content.enumerate(__get_tag_cue_track_list_builder(entries)); pfc::string_formatter cuesheet; cue_creator::create(cuesheet,entries); entries.remove_all(); //parse it back to see what info got stored in the cuesheet and what needs to be stored outside cuesheet in the tags cue_parser::parse_full(cuesheet,entries); file_info_record output; { file_info_record::t_meta_map globals; //1. find global infos and forward them { field_name_list fields; m_content.enumerate(__get_tag__enum_fields_enumerator(fields)); fields.enumerate(__get_tag__filter_globals(m_content,globals)); } output.overwrite_meta(globals); //2. find local infos m_content.enumerate(__get_tag__local_field_filter(globals,output.m_meta)); } //strip redundant titles and tracknumbers that the cuesheet already contains for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ++iter) { strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"tracknumber"); strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"title"); } //add tech infos etc { const track_record * rec = m_content.query_ptr((unsigned)0); if (rec != NULL) { output.set_length(rec->m_info.get_length()); output.set_replaygain(rec->m_info.get_replaygain()); output.overwrite_info(rec->m_info.m_info); } } output.meta_set("cuesheet",cuesheet); output.to_info(p_info); } } static bool resolve_cue_meta_name(const char * p_name,pfc::string_base & p_outname,unsigned & p_tracknumber) { //"cue_trackNN_fieldname" static const char header[] = "cue_track"; if (pfc::stricmp_ascii_ex(p_name,strlen(header),header,infinite) != 0) return false; p_name += strlen(header); if (!pfc::char_is_numeric(p_name[0]) || !pfc::char_is_numeric(p_name[1]) || p_name[2] != '_') return false; unsigned tracknumber = pfc::atoui_ex(p_name,2); if (tracknumber == 0) return false; p_name += 3; p_tracknumber = tracknumber; p_outname = p_name; return true; } namespace { class __set_tag_global_field_relay { public: __set_tag_global_field_relay(const file_info & p_info,t_size p_index) : m_info(p_info), m_index(p_index) {} void operator() (unsigned p_trackno,track_record & p_record) { if (p_trackno > 0) { p_record.m_info.transfer_meta_entry(m_info.meta_enum_name(m_index),m_info,m_index); } } private: const file_info & m_info; const t_size m_index; }; } void embeddedcue_metadata_manager::set_tag(file_info const & p_info) { m_content.remove_all(); { track_record & track0 = m_content.find_or_add((unsigned)0); track0.m_info.from_info(p_info); track0.m_info.m_info.set("cue_embedded","no"); } const char * cuesheet = p_info.meta_get("cuesheet",0); if (cuesheet == NULL) { return; } //processing order //1. cuesheet content //2. overwrite with global metadata from the tag //2. overwrite with local metadata from the tag { cue_creator::t_entry_list entries; try { cue_parser::parse_full(cuesheet,entries); } catch(exception_io_data const & e) { console::formatter() << e; return; } for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ) { cue_creator::t_entry_list::const_iterator next = iter; ++next; track_record & entry = m_content.find_or_add(iter->m_track_number); entry.m_file = iter->m_file; entry.m_flags = iter->m_flags; entry.m_index_list = iter->m_index_list; entry.m_info.from_info(iter->m_infos); entry.m_info.from_info_overwrite_info(p_info); entry.m_info.m_info.set("cue_embedded","yes"); double begin = entry.m_index_list.start(), end = next.is_valid() ? next->m_index_list.start() : p_info.get_length(); if (end <= begin) throw exception_io_data(); entry.m_info.set_length(end - begin); iter = next; } } for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) { const char * name = p_info.meta_enum_name(metawalk); const t_size valuecount = p_info.meta_enum_value_count(metawalk); if (valuecount > 0 && !is_reserved_meta_entry(name) && is_global_meta_entry(name)) { __set_tag_global_field_relay relay(p_info,metawalk); m_content.enumerate(relay); } } { pfc::string8_fastalloc namebuffer; for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) { const char * name = p_info.meta_enum_name(metawalk); const t_size valuecount = p_info.meta_enum_value_count(metawalk); unsigned trackno; if (valuecount > 0 && !is_reserved_meta_entry(name) && resolve_cue_meta_name(name,namebuffer,trackno)) { track_record * rec = m_content.query_ptr(trackno); if (rec != NULL) { rec->m_info.transfer_meta_entry(namebuffer,p_info,metawalk); } } } } } void embeddedcue_metadata_manager::get_track_info(unsigned p_track,file_info & p_info) const { const track_record * rec = m_content.query_ptr(p_track); if (rec == NULL) throw exception_io_data(); rec->m_info.to_info(p_info); } void embeddedcue_metadata_manager::set_track_info(unsigned p_track,file_info const & p_info) { track_record * rec = m_content.query_ptr(p_track); if (rec == NULL) throw exception_io_data(); rec->m_info.from_info_set_meta(p_info); rec->m_info.set_replaygain(p_info.get_replaygain()); } void embeddedcue_metadata_manager::query_track_offsets(unsigned p_track,double & p_begin,double & p_length) const { const track_record * rec = m_content.query_ptr(p_track); if (rec == NULL) throw exception_io_data(); p_begin = rec->m_index_list.start(); p_length = rec->m_info.get_length(); } bool embeddedcue_metadata_manager::have_cuesheet() const { return m_content.get_count() > 1; } namespace { class __remap_trackno_enumerator { public: __remap_trackno_enumerator(unsigned p_index) : m_countdown(p_index), m_result(0) {} template<typename t_blah> void operator() (unsigned p_trackno,const t_blah&) { if (p_trackno > 0 && m_result == 0) { if (m_countdown == 0) { m_result = p_trackno; } else { --m_countdown; } } } unsigned result() const {return m_result;} private: unsigned m_countdown; unsigned m_result; }; }; unsigned embeddedcue_metadata_manager::remap_trackno(unsigned p_index) const { if (have_cuesheet()) { __remap_trackno_enumerator wrapper(p_index); m_content.enumerate(wrapper); return wrapper.result(); } else { return 0; } } t_size embeddedcue_metadata_manager::get_cue_track_count() const { return m_content.get_count() - 1; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/cuesheet_index_list.cpp ================================================ #include "stdafx.h" static bool is_numeric(char c) {return c>='0' && c<='9';} bool t_cuesheet_index_list::is_valid() const { if (m_positions[1] < m_positions[0]) return false; for(t_size n = 2; n < count && m_positions[n] > 0; n++) { if (m_positions[n] < m_positions[n-1]) return false; } return true; } void t_cuesheet_index_list::to_infos(file_info & p_out) const { double base = m_positions[1]; if (base > 0) { p_out.info_set("referenced_offset",cuesheet_format_index_time(base)); } if (m_positions[0] < base) p_out.info_set("pregap",cuesheet_format_index_time(base - m_positions[0])); else p_out.info_remove("pregap"); p_out.info_remove("index 00"); p_out.info_remove("index 01"); for(unsigned n=2;n<count;n++) { char namebuffer[16]; sprintf(namebuffer,"index %02u",n); double position = m_positions[n] - base; if (position > 0) p_out.info_set(namebuffer,cuesheet_format_index_time(position)); else p_out.info_remove(namebuffer); } } static bool parse_value(const char * p_name,double & p_out) { if (p_name == NULL) return false; try { p_out = cuesheet_parse_index_time_e(p_name,strlen(p_name)); } catch(std::exception const &) {return false;} return true; } bool t_cuesheet_index_list::from_infos(file_info const & p_in,double p_base) { double pregap; bool found = false; if (!parse_value(p_in.info_get("pregap"),pregap)) pregap = 0; else found = true; m_positions[0] = p_base - pregap; m_positions[1] = p_base; for(unsigned n=2;n<count;n++) { char namebuffer[16]; sprintf(namebuffer,"index %02u",n); double temp; if (parse_value(p_in.info_get(namebuffer),temp)) { m_positions[n] = temp + p_base; found = true; } else { m_positions[n] = 0; } } return found; } cuesheet_format_index_time::cuesheet_format_index_time(double p_time) { t_uint64 ticks = audio_math::time_to_samples(p_time,75); t_uint64 seconds = ticks / 75; ticks %= 75; t_uint64 minutes = seconds / 60; seconds %= 60; m_buffer << pfc::format_uint(minutes,2) << ":" << pfc::format_uint(seconds,2) << ":" << pfc::format_uint(ticks,2); } double cuesheet_parse_index_time_e(const char * p_string,t_size p_length) { return (double) cuesheet_parse_index_time_ticks_e(p_string,p_length) / 75.0; } unsigned cuesheet_parse_index_time_ticks_e(const char * p_string,t_size p_length) { p_length = pfc::strlen_max(p_string,p_length); t_size ptr = 0; t_size splitmarks[2]; t_size splitptr = 0; for(ptr=0;ptr<p_length;ptr++) { if (p_string[ptr] == ':') { if (splitptr >= 2) throw std::exception("invalid INDEX time syntax",0); splitmarks[splitptr++] = ptr; } else if (!is_numeric(p_string[ptr])) throw std::exception("invalid INDEX time syntax",0); } t_size minutes_base = 0, minutes_length = 0, seconds_base = 0, seconds_length = 0, frames_base = 0, frames_length = 0; switch(splitptr) { case 0: frames_base = 0; frames_length = p_length; break; case 1: seconds_base = 0; seconds_length = splitmarks[0]; frames_base = splitmarks[0] + 1; frames_length = p_length - frames_base; break; case 2: minutes_base = 0; minutes_length = splitmarks[0]; seconds_base = splitmarks[0] + 1; seconds_length = splitmarks[1] - seconds_base; frames_base = splitmarks[1] + 1; frames_length = p_length - frames_base; break; } unsigned ret = 0; if (frames_length > 0) ret += pfc::atoui_ex(p_string + frames_base,frames_length); if (seconds_length > 0) ret += 75 * pfc::atoui_ex(p_string + seconds_base,seconds_length); if (minutes_length > 0) ret += 60 * 75 * pfc::atoui_ex(p_string + minutes_base,minutes_length); return ret; } bool t_cuesheet_index_list::is_empty() const { for(unsigned n=0;n<count;n++) if (m_positions[n] != m_positions[1]) return false; return true; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/cuesheet_index_list.h ================================================ struct t_cuesheet_index_list { enum {count = 100}; inline t_cuesheet_index_list() {reset();} inline t_cuesheet_index_list(const t_cuesheet_index_list & p_source) {*this=p_source;} void reset() {for(unsigned n=0;n<count;n++) m_positions[n]=0;} void to_infos(file_info & p_out) const; //returns false when there was nothing relevant in infos bool from_infos(file_info const & p_in,double p_base); double m_positions[count]; inline double start() const {return m_positions[1];} bool is_empty() const; bool is_valid() const; }; unsigned cuesheet_parse_index_time_ticks_e(const char * p_string,t_size p_length); double cuesheet_parse_index_time_e(const char * p_string,t_size p_length); class cuesheet_format_index_time { public: cuesheet_format_index_time(double p_time); inline operator const char*() const {return m_buffer;} private: pfc::string_formatter m_buffer; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/dialog_resize_helper.cpp ================================================ #include "stdafx.h" #include "dialog_resize_helper.h" void resize::calc_xy(HWND wnd,UINT id,RECT &r,RECT & o) { RECT c; GetClientRect(wnd,&c); SetWindowPos(GetDlgItem(wnd,id),0,0,0, r.right-r.left+c.right-o.right, r.bottom-r.top+c.bottom-o.bottom, SWP_NOMOVE|SWP_NOZORDER); } void resize::calc_move_xy(HWND wnd,UINT id,RECT &r,RECT & o) { RECT c; GetClientRect(wnd,&c); SetWindowPos(GetDlgItem(wnd,id),0, c.right-o.right+r.left, c.bottom-o.bottom+r.top, 0,0,SWP_NOSIZE|SWP_NOZORDER); } void resize::calc_move_x(HWND wnd,UINT id,RECT &r,RECT & o) { RECT c; GetClientRect(wnd,&c); SetWindowPos(GetDlgItem(wnd,id),0, c.right-o.right+r.left, r.top, 0,0,SWP_NOSIZE|SWP_NOZORDER); } void resize::calc_move_x_size_y(HWND wnd,UINT id,RECT &r,RECT & o) { RECT c; GetClientRect(wnd,&c); SetWindowPos(GetDlgItem(wnd,id),0, c.right-o.right+r.left, r.top, r.right-r.left, r.bottom-r.top+c.bottom-o.bottom, SWP_NOZORDER); } void resize::calc_move_y(HWND wnd,UINT id,RECT &r,RECT & o) { RECT c; GetClientRect(wnd,&c); SetWindowPos(GetDlgItem(wnd,id),0, r.left, c.bottom-o.bottom+r.top, 0,0,SWP_NOSIZE|SWP_NOZORDER); } void resize::calc_x(HWND wnd,UINT id,RECT &r,RECT & o) { RECT c; GetClientRect(wnd,&c); SetWindowPos(GetDlgItem(wnd,id),0,0,0, r.right-r.left+c.right-o.right, r.bottom-r.top, SWP_NOMOVE|SWP_NOZORDER); } void GetChildRect(HWND wnd,UINT id,RECT* child) { RECT temp; GetWindowRect(GetDlgItem(wnd,id),&temp); MapWindowPoints(0,wnd,(POINT*)&temp,2); *child = temp; } /* class dialog_resize_helper { struct entry { RECT orig; UINT id; UINT flags }; mem_block_list<entry> data; RECT orig_client; HWND parent; public: enum { X_MOVE = 1, X_SIZE = 2, Y_MOVE = 4, Y_SIZE = 8 }; void set_parent(HWND wnd); void add_item(UINT id,UINT flags); void reset(); }; */ void dialog_resize_helper::set_parent(HWND wnd) { reset(); parent = wnd; GetClientRect(parent,&orig_client); } void dialog_resize_helper::reset() { parent = 0; sizegrip = 0; } void dialog_resize_helper::on_wm_size() { if (parent) { unsigned count = m_table.get_size(); if (sizegrip != 0) count++; HDWP hWinPosInfo = BeginDeferWindowPos(count); for(unsigned n=0;n<m_table.get_size();n++) { param & e = m_table[n]; const RECT & orig_rect = rects[n]; RECT cur_client; GetClientRect(parent,&cur_client); HWND wnd = GetDlgItem(parent,e.id); unsigned dest_x = orig_rect.left, dest_y = orig_rect.top, dest_cx = orig_rect.right - orig_rect.left, dest_cy = orig_rect.bottom - orig_rect.top; int delta_x = cur_client.right - orig_client.right, delta_y = cur_client.bottom - orig_client.bottom; if (e.flags & X_MOVE) dest_x += delta_x; else if (e.flags & X_SIZE) dest_cx += delta_x; if (e.flags & Y_MOVE) dest_y += delta_y; else if (e.flags & Y_SIZE) dest_cy += delta_y; DeferWindowPos(hWinPosInfo, wnd,0,dest_x,dest_y,dest_cx,dest_cy,SWP_NOZORDER); } if (sizegrip != 0) { RECT rc, rc_grip; GetClientRect(parent, &rc); GetWindowRect(sizegrip, &rc_grip); DeferWindowPos(hWinPosInfo, sizegrip, NULL, rc.right - (rc_grip.right - rc_grip.left), rc.bottom - (rc_grip.bottom - rc_grip.top), 0, 0, SWP_NOZORDER | SWP_NOSIZE); } EndDeferWindowPos(hWinPosInfo); RedrawWindow(parent,0,0,RDW_INVALIDATE); } } bool dialog_resize_helper::process_message(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) { switch(msg) { case WM_SIZE: on_wm_size(); return true; case WM_GETMINMAXINFO: { RECT r; LPMINMAXINFO info = (LPMINMAXINFO) lp; DWORD dwStyle = GetWindowLong(wnd, GWL_STYLE); DWORD dwExStyle = GetWindowLong(wnd, GWL_EXSTYLE); if (max_x && max_y) { r.left = 0; r.right = max_x; r.top = 0; r.bottom = max_y; MapDialogRect(wnd,&r); AdjustWindowRectEx(&r, dwStyle, FALSE, dwExStyle); info->ptMaxTrackSize.x = r.right - r.left; info->ptMaxTrackSize.y = r.bottom - r.top; } if (min_x && min_y) { r.left = 0; r.right = min_x; r.top = 0; r.bottom = min_y; MapDialogRect(wnd,&r); AdjustWindowRectEx(&r, dwStyle, FALSE, dwExStyle); info->ptMinTrackSize.x = r.right - r.left; info->ptMinTrackSize.y = r.bottom - r.top; } } return true; case WM_INITDIALOG: set_parent(wnd); { unsigned n; for(n=0;n<m_table.get_size();n++) { GetChildRect(parent,m_table[n].id,&rects[n]); } } break; case WM_DESTROY: reset(); break; } return false; } void dialog_resize_helper::add_sizegrip() { if (parent != 0 && sizegrip == 0) { sizegrip = CreateWindowEx(0, WC_SCROLLBAR, _T(""), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | SBS_SIZEGRIP | SBS_SIZEBOXBOTTOMRIGHTALIGN, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, parent, (HMENU)0, NULL, NULL); if (sizegrip != 0) { RECT rc, rc_grip; GetClientRect(parent, &rc); GetWindowRect(sizegrip, &rc_grip); SetWindowPos(sizegrip, NULL, rc.right - (rc_grip.right - rc_grip.left), rc.bottom - (rc_grip.bottom - rc_grip.top), 0, 0, SWP_NOZORDER | SWP_NOSIZE); } } } dialog_resize_helper::dialog_resize_helper(const param * src,unsigned count,unsigned p_min_x,unsigned p_min_y,unsigned p_max_x,unsigned p_max_y) : min_x(p_min_x), min_y(p_min_y), max_x(p_max_x), max_y(p_max_y), parent(0), sizegrip(0) { m_table.set_size(count); unsigned n; for(n=0;n<count;n++) m_table[n] = src[n]; rects.set_size(count); } dialog_resize_helper::~dialog_resize_helper() { } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/dialog_resize_helper.h ================================================ #ifndef _DIALOG_RESIZE_HELPER_H_ #define _DIALOG_RESIZE_HELPER_H_ //deprecated, use dialog_resize_helper class namespace resize { void calc_xy(HWND wnd,UINT id,RECT &r,RECT & o); void calc_move_xy(HWND wnd,UINT id,RECT &r,RECT & o); void calc_move_x(HWND wnd,UINT id,RECT &r,RECT & o); void calc_move_x_size_y(HWND wnd,UINT id,RECT &r,RECT & o); void calc_move_y(HWND wnd,UINT id,RECT &r,RECT & o); void calc_x(HWND wnd,UINT id,RECT &r,RECT & o); }; void GetChildRect(HWND wnd,UINT id,RECT* child); class dialog_resize_helper { pfc::array_t<RECT> rects; RECT orig_client; HWND parent; HWND sizegrip; unsigned min_x,min_y,max_x,max_y; public: struct param { unsigned short id; unsigned short flags; }; private: pfc::array_t<param> m_table; void set_parent(HWND wnd); void add_item(UINT id,UINT flags); void reset(); void on_wm_size(); void add_items(const param* table,unsigned count); public: inline void set_min_size(unsigned x,unsigned y) {min_x = x; min_y = y;} inline void set_max_size(unsigned x,unsigned y) {max_x = x; max_y = y;} void add_sizegrip(); enum { X_MOVE = 1, X_SIZE = 2, Y_MOVE = 4, Y_SIZE = 8, XY_MOVE = X_MOVE|Y_MOVE, XY_SIZE = X_SIZE|Y_SIZE, X_MOVE_Y_SIZE = X_MOVE|Y_SIZE, X_SIZE_Y_MOVE = X_SIZE|Y_MOVE, }; bool process_message(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); explicit dialog_resize_helper(const param * src,unsigned count,unsigned p_min_x,unsigned p_min_y,unsigned p_max_x,unsigned p_max_y); ~dialog_resize_helper(); }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/dropdown_helper.cpp ================================================ #include "stdafx.h" #include "dropdown_helper.h" void cfg_dropdown_history::build_list(pfc::ptr_list_t<char> & out) { const char * src = data; while(*src) { int ptr = 0; while(src[ptr] && src[ptr]!=separator) ptr++; if (ptr>0) { out.add_item(pfc::strdup_n(src,ptr)); src += ptr; } while(*src==separator) src++; } } void cfg_dropdown_history::parse_list(const pfc::ptr_list_t<char> & src) { t_size n; pfc::string8_fastalloc temp; for(n=0;n<src.get_count();n++) { temp.add_string(src[n]); temp.add_char(separator); } data = temp; } static void g_setup_dropdown_fromlist(HWND wnd,const pfc::ptr_list_t<char> & list) { t_size n, m = list.get_count(); uSendMessage(wnd,CB_RESETCONTENT,0,0); for(n=0;n<m;n++) { uSendMessageText(wnd,CB_ADDSTRING,0,list[n]); } } void cfg_dropdown_history::setup_dropdown(HWND wnd) { pfc::ptr_list_t<char> list; build_list(list); g_setup_dropdown_fromlist(wnd,list); list.free_all(); } void cfg_dropdown_history::add_item(const char * item) { if (!item || !*item) return; pfc::string8 meh; if (strchr(item,separator)) { uReplaceChar(meh,item,-1,separator,'|',false); item = meh; } pfc::ptr_list_t<char> list; build_list(list); unsigned n; bool found = false; for(n=0;n<list.get_count();n++) { if (!strcmp(list[n],item)) { char* temp = list.remove_by_idx(n); list.insert_item(temp,0); found = true; } } if (!found) { while(list.get_count() > max) list.delete_by_idx(list.get_count()-1); list.insert_item(strdup(item),0); } parse_list(list); list.free_all(); } bool cfg_dropdown_history::is_empty() { const char * src = data; while(*src) { if (*src!=separator) return false; src++; } return true; } void cfg_dropdown_history::on_context(HWND wnd,LPARAM coords) { int coords_x = (short)LOWORD(coords), coords_y = (short)HIWORD(coords); if (coords_x == -1 && coords_y == -1) { RECT asdf; GetWindowRect(wnd,&asdf); coords_x = (asdf.left + asdf.right) / 2; coords_y = (asdf.top + asdf.bottom) / 2; } enum {ID_ERASE_ALL = 1, ID_ERASE_ONE }; HMENU menu = CreatePopupMenu(); uAppendMenu(menu,MF_STRING,ID_ERASE_ALL,"Wipe history"); { pfc::string8 tempvalue; uGetWindowText(wnd,tempvalue); if (!tempvalue.is_empty()) uAppendMenu(menu,MF_STRING,ID_ERASE_ONE,"Remove this history item"); } int cmd = TrackPopupMenu(menu,TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,coords_x,coords_y,0,wnd,0); DestroyMenu(menu); switch(cmd) { case ID_ERASE_ALL: { data = ""; pfc::string8 value;//preserve old value while wiping dropdown list uGetWindowText(wnd,value); uSendMessage(wnd,CB_RESETCONTENT,0,0); uSetWindowText(wnd,value); } break; case ID_ERASE_ONE: { pfc::string8 value; uGetWindowText(wnd,value); pfc::ptr_list_t<char> list; t_size n,m; bool found; build_list(list); m = list.get_count(); found = false; for(n=0;n<m;n++) { if (!strcmp(value,list[n])) { free(list[n]); list.remove_by_idx(n); found = true; break; } } if (found) parse_list(list); g_setup_dropdown_fromlist(wnd,list); list.free_all(); } break; } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/dropdown_helper.h ================================================ #ifndef _DROPDOWN_HELPER_H_ #define _DROPDOWN_HELPER_H_ class cfg_dropdown_history { enum {separator = '\n'}; cfg_string data; unsigned max; void build_list(pfc::ptr_list_t<char> & out); void parse_list(const pfc::ptr_list_t<char> & src); public: cfg_dropdown_history(const GUID & p_guid,unsigned p_max = 10,const char * init_vals = "") : data(p_guid,init_vals) {max = p_max;} void setup_dropdown(HWND wnd); void setup_dropdown(HWND wnd,UINT id) {setup_dropdown(GetDlgItem(wnd,id));} void add_item(const char * item); bool is_empty(); void on_context(HWND wnd,LPARAM coords); }; #endif //_DROPDOWN_HELPER_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/dynamic_bitrate_helper.cpp ================================================ #include "stdafx.h" static unsigned g_query_settings() { t_int32 temp; try { config_object::g_get_data_int32(standard_config_objects::int32_dynamic_bitrate_display_rate,temp); } catch(std::exception const &) {return 9;} if (temp < 0) return 0; return (unsigned) temp; } dynamic_bitrate_helper::dynamic_bitrate_helper() { reset(); } void dynamic_bitrate_helper::init() { if (!m_inited) { m_inited = true; unsigned temp = g_query_settings(); if (temp > 0) {m_enabled = true; m_update_interval = 1.0 / (double) temp; } else {m_enabled = false; m_update_interval = 0; } m_last_duration = 0; m_update_bits = 0; m_update_time = 0; } } void dynamic_bitrate_helper::on_frame(double p_duration,t_size p_bits) { init(); m_last_duration = p_duration; m_update_time += p_duration; m_update_bits += p_bits; } bool dynamic_bitrate_helper::on_update(file_info & p_out, double & p_timestamp_delta) { init(); bool ret = false; if (m_enabled) { if (m_update_time > m_update_interval) { int val = (int) ( ((double)m_update_bits / m_update_time + 500.0) / 1000.0 ); if (val != p_out.info_get_bitrate_vbr()) { p_timestamp_delta = - (m_update_time - m_last_duration); //relative to last frame beginning; p_out.info_set_bitrate_vbr(val); ret = true; } m_update_bits = 0; m_update_time = 0; } } else { m_update_bits = 0; m_update_time = 0; } return ret; } void dynamic_bitrate_helper::reset() { m_inited = false; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/dynamic_bitrate_helper.h ================================================ class dynamic_bitrate_helper { public: dynamic_bitrate_helper(); void on_frame(double p_duration,t_size p_bits); bool on_update(file_info & p_out, double & p_timestamp_delta); void reset(); private: void init(); double m_last_duration; t_size m_update_bits; double m_update_time; double m_update_interval; bool m_inited, m_enabled; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/file_cached.h ================================================ template<unsigned blocksize> class file_cached : public file { public: static void g_create(service_ptr_t<file> & p_out,service_ptr_t<file> p_base,abort_callback & p_abort) { service_ptr_t<file_cached<blocksize> > temp; temp = new service_impl_t<file_cached<blocksize> >(); temp->initialize(p_base,p_abort); p_out = temp.get_ptr(); } private: void initialize(service_ptr_t<file> p_base,abort_callback & p_abort) { m_base = p_base; m_position = 0; m_can_seek = m_base->can_seek(); if (m_can_seek) { m_position_base = m_base->get_position(p_abort); } else { m_position_base = 0; } m_size = m_base->get_size(p_abort); flush_buffer(); } public: t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { t_uint8 * outptr = (t_uint8*)p_buffer; t_size done = 0; while(done < p_bytes && m_position < m_size) { p_abort.check(); if (m_position >= m_buffer_position && m_position < m_buffer_position + m_buffer_status) { t_size delta = pfc::min_t<t_size>((t_size)(m_buffer_position + m_buffer_status - m_position),p_bytes - done); t_size bufptr = (t_size)(m_position - m_buffer_position); memcpy(outptr+done,m_buffer+bufptr,delta); done += delta; m_position += delta; if (m_buffer_status != sizeof(m_buffer) && done < p_bytes) break;//EOF before m_size is hit } else { m_buffer_position = m_position - m_position % blocksize; adjust_position(m_buffer_position,p_abort); m_buffer_status = m_base->read(m_buffer,sizeof(m_buffer),p_abort); m_position_base += m_buffer_status; if (m_buffer_status <= (t_size)(m_position - m_buffer_position)) break; } } return done; } void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { p_abort.check(); adjust_position(m_position,p_abort); m_base->write(p_buffer,p_bytes,p_abort); m_position_base = m_position = m_position + p_bytes; if (m_size < m_position) m_size = m_position; flush_buffer(); } t_filesize get_size(abort_callback & p_abort) { p_abort.check(); return m_size; } t_filesize get_position(abort_callback & p_abort) { p_abort.check(); return m_position; } void set_eof(abort_callback & p_abort) { p_abort.check(); adjust_position(m_position,p_abort); m_base->set_eof(p_abort); flush_buffer(); } void seek(t_filesize p_position,abort_callback & p_abort) { p_abort.check(); if (!m_can_seek) throw exception_io_object_not_seekable(); if (p_position > m_size) throw exception_io_seek_out_of_range(); m_position = p_position; } void reopen(abort_callback & p_abort) {seek(0,p_abort);} bool can_seek() {return m_can_seek;} bool get_content_type(pfc::string_base & out) {return m_base->get_content_type(out);} void on_idle(abort_callback & p_abort) {p_abort.check();m_base->on_idle(p_abort);} t_filetimestamp get_timestamp(abort_callback & p_abort) {p_abort.check(); return m_base->get_timestamp(p_abort);} bool is_remote() {return m_base->is_remote();} void resize(t_filesize p_size,abort_callback & p_abort) { flush_buffer(); m_base->resize(p_size,p_abort); m_size = p_size; if (m_position > m_size) m_position = m_size; if (m_position_base > m_size) m_position_base = m_size; } private: void adjust_position(t_filesize p_target,abort_callback & p_abort) { if (p_target != m_position_base) { m_base->seek(p_target,p_abort); m_position_base = p_target; } } void flush_buffer() { m_buffer_status = 0; m_buffer_position = 0; } service_ptr_t<file> m_base; t_filesize m_position,m_position_base,m_size; bool m_can_seek; t_filesize m_buffer_position; t_size m_buffer_status; t_uint8 m_buffer[blocksize]; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/file_info_const_impl.cpp ================================================ #include "stdafx.h" static const char * const standard_fieldnames[] = { "artist","ARTIST","Artist", "album","ALBUM","Album", "tracknumber","TRACKNUMBER","Tracknumber", "totaltracks","TOTALTRACKS","Totaltracks", "genre","GENRE","Genre", "title","TITLE","Title", "comment","COMMENT","Comment", "date","DATE","Date", "discnumber","DISCNUMBER","Discnumber" }; static const char * const standard_infonames[] = { "bitspersample", "channels", "bitrate", "codec", "codec_profile","tool","tagtype", "samplerate" }; static const char * optimize_fieldname(const char * p_string) { for(t_size n=0;n<tabsize(standard_fieldnames);n++) { const char * stdstring = standard_fieldnames[n]; if (/*p_string[0] == stdstring[0] && */strcmp(p_string,stdstring) == 0) { return stdstring; } } return NULL; } static const char * optimize_infoname(const char * p_string) { for(t_size n=0;n<tabsize(standard_infonames);n++) { const char * stdstring = standard_infonames[n]; if (/*p_string[0] == stdstring[0] && */strcmp(p_string,stdstring) == 0) { return stdstring; } } return NULL; } /* order of things meta entries meta value map info entries string buffer */ inline static char* stringbuffer_append(char * & buffer,const char * value) { char * ret = buffer; while(*value) *(buffer++) = *(value++); *(buffer++) = 0; return ret; } #ifdef __file_info_const_impl_have_hintmap__ namespace { class sort_callback_hintmap_impl : public pfc::sort_callback { public: sort_callback_hintmap_impl(const file_info_const_impl::meta_entry * p_meta,file_info_const_impl::t_index * p_hintmap) : m_meta(p_meta), m_hintmap(p_hintmap) { } int compare(t_size p_index1, t_size p_index2) const { // profiler(sort_callback_hintmap_impl_compare); return pfc::stricmp_ascii(m_meta[m_hintmap[p_index1]].m_name,m_meta[m_hintmap[p_index2]].m_name); } void swap(t_size p_index1, t_size p_index2) { pfc::swap_t<file_info_const_impl::t_index>(m_hintmap[p_index1],m_hintmap[p_index2]); } private: const file_info_const_impl::meta_entry * m_meta; file_info_const_impl::t_index * m_hintmap; }; class bsearch_callback_hintmap_impl : public pfc::bsearch_callback { public: bsearch_callback_hintmap_impl( const file_info_const_impl::meta_entry * p_meta, const file_info_const_impl::t_index * p_hintmap, const char * p_name, t_size p_name_length) : m_meta(p_meta), m_hintmap(p_hintmap), m_name(p_name), m_name_length(p_name_length) { } int test(t_size p_index) const { return pfc::stricmp_ascii_ex(m_meta[m_hintmap[p_index]].m_name,infinite,m_name,m_name_length); } private: const file_info_const_impl::meta_entry * m_meta; const file_info_const_impl::t_index * m_hintmap; const char * m_name; t_size m_name_length; }; } #endif//__file_info_const_impl_have_hintmap__ void file_info_const_impl::copy(const file_info & p_source) { // profiler(file_info_const_impl__copy); t_size meta_size = 0; t_size info_size = 0; t_size valuemap_size = 0; t_size stringbuffer_size = 0; #ifdef __file_info_const_impl_have_hintmap__ t_size hintmap_size = 0; #endif { // profiler(file_info_const_impl__copy__pass1); t_size index; m_meta_count = pfc::downcast_guarded<t_index>(p_source.meta_get_count()); meta_size = m_meta_count * sizeof(meta_entry); #ifdef __file_info_const_impl_have_hintmap__ hintmap_size = (m_meta_count > hintmap_cutoff) ? m_meta_count * sizeof(t_index) : 0; #endif//__file_info_const_impl_have_hintmap__ for(index = 0; index < m_meta_count; index++ ) { { const char * name = p_source.meta_enum_name(index); if (optimize_fieldname(name) == 0) stringbuffer_size += strlen(name) + 1; } t_size val; const t_size val_max = p_source.meta_enum_value_count(index); if (val_max == 1) { stringbuffer_size += strlen(p_source.meta_enum_value(index,0)) + 1; } else { valuemap_size += val_max * sizeof(char*); for(val = 0; val < val_max; val++ ) { stringbuffer_size += strlen(p_source.meta_enum_value(index,val)) + 1; } } } m_info_count = pfc::downcast_guarded<t_index>(p_source.info_get_count()); info_size = m_info_count * sizeof(info_entry); for(index = 0; index < m_info_count; index++ ) { const char * name = p_source.info_enum_name(index); if (optimize_infoname(name) == NULL) stringbuffer_size += strlen(name) + 1; stringbuffer_size += strlen(p_source.info_enum_value(index)) + 1; } } { // profiler(file_info_const_impl__copy__alloc); m_buffer.set_size( #ifdef __file_info_const_impl_have_hintmap__ hintmap_size + #endif meta_size + info_size + valuemap_size + stringbuffer_size); } char * walk = m_buffer.get_ptr(); #ifdef __file_info_const_impl_have_hintmap__ t_index* hintmap = (hintmap_size > 0) ? (t_index*) walk : NULL; walk += hintmap_size; #endif meta_entry * meta = (meta_entry*) walk; walk += meta_size; char ** valuemap = (char**) walk; walk += valuemap_size; info_entry * info = (info_entry*) walk; walk += info_size; char * stringbuffer = walk; m_meta = meta; m_info = info; #ifdef __file_info_const_impl_have_hintmap__ m_hintmap = hintmap; #endif { // profiler(file_info_const_impl__copy__pass2); t_size index; for( index = 0; index < m_meta_count; index ++ ) { t_size val; const t_size val_max = p_source.meta_enum_value_count(index); { const char * name = p_source.meta_enum_name(index); const char * name_opt = optimize_fieldname(name); if (name_opt == NULL) meta[index].m_name = stringbuffer_append(stringbuffer, name ); else meta[index].m_name = name_opt; } meta[index].m_valuecount = val_max; if (val_max == 1) { meta[index].m_valuemap = reinterpret_cast<const char * const *>(stringbuffer_append(stringbuffer, p_source.meta_enum_value(index,0) )); } else { meta[index].m_valuemap = valuemap; for( val = 0; val < val_max ; val ++ ) *(valuemap ++ ) = stringbuffer_append(stringbuffer, p_source.meta_enum_value(index,val) ); } } for( index = 0; index < m_info_count; index ++ ) { const char * name = p_source.info_enum_name(index); const char * name_opt = optimize_infoname(name); if (name_opt == NULL) info[index].m_name = stringbuffer_append(stringbuffer, name ); else info[index].m_name = name_opt; info[index].m_value = stringbuffer_append(stringbuffer, p_source.info_enum_value(index) ); } } m_length = p_source.get_length(); m_replaygain = p_source.get_replaygain(); #ifdef __file_info_const_impl_have_hintmap__ if (hintmap != NULL) { // profiler(file_info_const_impl__copy__hintmap); for(t_size n=0;n<m_meta_count;n++) hintmap[n]=n; pfc::sort(sort_callback_hintmap_impl(meta,hintmap),m_meta_count); } #endif//__file_info_const_impl_have_hintmap__ } void file_info_const_impl::reset() { m_meta_count = m_info_count = 0; m_length = 0; m_replaygain.reset(); } t_size file_info_const_impl::meta_find_ex(const char * p_name,t_size p_name_length) const { #ifdef __file_info_const_impl_have_hintmap__ if (m_hintmap != NULL) { t_size result = infinite; if (!pfc::bsearch(m_meta_count,bsearch_callback_hintmap_impl(m_meta,m_hintmap,p_name,p_name_length),result)) return infinite; else return m_hintmap[result]; } else { return file_info::meta_find_ex(p_name,p_name_length); } #else return file_info::meta_find_ex(p_name,p_name_length); #endif } t_size file_info_const_impl::meta_enum_value_count(t_size p_index) const { return m_meta[p_index].m_valuecount; } const char* file_info_const_impl::meta_enum_value(t_size p_index,t_size p_value_number) const { const meta_entry & entry = m_meta[p_index]; if (entry.m_valuecount == 1) return reinterpret_cast<const char*>(entry.m_valuemap); else return entry.m_valuemap[p_value_number]; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/file_info_const_impl.h ================================================ #define __file_info_const_impl_have_hintmap__ //! Special implementation of file_info that implements only const and copy methods. The difference between this and regular file_info_impl is amount of resources used and speed of the copy operation. class file_info_const_impl : public file_info { public: file_info_const_impl(const file_info & p_source) {copy(p_source);} file_info_const_impl(const file_info_const_impl & p_source) {copy(p_source);} file_info_const_impl() {m_meta_count = m_info_count = 0; m_length = 0; m_replaygain.reset();} double get_length() const {return m_length;} t_size meta_get_count() const {return m_meta_count;} const char* meta_enum_name(t_size p_index) const {return m_meta[p_index].m_name;} t_size meta_enum_value_count(t_size p_index) const; const char* meta_enum_value(t_size p_index,t_size p_value_number) const; t_size meta_find_ex(const char * p_name,t_size p_name_length) const; t_size info_get_count() const {return m_info_count;} const char* info_enum_name(t_size p_index) const {return m_info[p_index].m_name;} const char* info_enum_value(t_size p_index) const {return m_info[p_index].m_value;} const file_info_const_impl & operator=(const file_info & p_source) {copy(p_source); return *this;} const file_info_const_impl & operator=(const file_info_const_impl & p_source) {copy(p_source); return *this;} void copy(const file_info & p_source); void reset(); replaygain_info get_replaygain() const {return m_replaygain;} private: void set_length(double p_length) {throw pfc::exception_bug_check();} t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {throw pfc::exception_bug_check();} void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) {throw pfc::exception_bug_check();} void meta_remove_mask(const bit_array & p_mask) {throw pfc::exception_bug_check();} void meta_reorder(const t_size * p_order) {throw pfc::exception_bug_check();} void meta_remove_values(t_size p_index,const bit_array & p_mask) {throw pfc::exception_bug_check();} void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) {throw pfc::exception_bug_check();} t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {throw pfc::exception_bug_check();} void info_remove_mask(const bit_array & p_mask) {throw pfc::exception_bug_check();} void set_replaygain(const replaygain_info & p_info) {throw pfc::exception_bug_check();} t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {throw pfc::exception_bug_check();} t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {throw pfc::exception_bug_check();} public: struct meta_entry { const char * m_name; t_size m_valuecount; const char * const * m_valuemap; }; struct info_entry { const char * m_name; const char * m_value; }; #ifdef __file_info_const_impl_have_hintmap__ typedef t_uint32 t_index; enum {hintmap_cutoff = 20}; #endif//__file_info_const_impl_have_hintmap__ private: pfc::array_t<char> m_buffer; t_index m_meta_count; t_index m_info_count; const meta_entry * m_meta; const info_entry * m_info; #ifdef __file_info_const_impl_have_hintmap__ const t_index * m_hintmap; #endif double m_length; replaygain_info m_replaygain; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/file_list_helper.cpp ================================================ #include "stdafx.h" static void file_list_remove_duplicates(pfc::ptr_list_t<char> & out) { t_size n, m = out.get_count(); out.sort_t(metadb::path_compare); bit_array_bittable mask(m); t_size duplicates = 0; for(n=1;n<m;n++) { if (!metadb::path_compare(out[n-1],out[n])) {duplicates++;mask.set(n,true);} } if (duplicates>0) { out.free_mask(mask); } } namespace file_list_helper { void file_list_from_metadb_handle_list::__add(const char * p_what) { char * temp = strdup(p_what); if (temp == NULL) throw std::bad_alloc(); try {m_data.add_item(temp); } catch(...) {free(temp); throw;} } void file_list_from_metadb_handle_list::init_from_list(const list_base_const_t<metadb_handle_ptr> & p_list) { m_data.free_all(); t_size n, m = p_list.get_count(); for(n=0;n<m;n++) { __add( p_list.get_item(n)->get_path() ); } file_list_remove_duplicates(m_data); } void file_list_from_metadb_handle_list::init_from_list_display(const list_base_const_t<metadb_handle_ptr> & p_list) { m_data.free_all(); pfc::string8_fastalloc temp; t_size n, m = p_list.get_count(); for(n=0;n<m;n++) { filesystem::g_get_display_path(p_list.get_item(n)->get_path(),temp); __add(temp); } file_list_remove_duplicates(m_data); } t_size file_list_from_metadb_handle_list::get_count() const {return m_data.get_count();} void file_list_from_metadb_handle_list::get_item_ex(const char * & p_out,t_size n) const {p_out = m_data.get_item(n);} file_list_from_metadb_handle_list::~file_list_from_metadb_handle_list() { m_data.free_all(); } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/file_list_helper.h ================================================ #ifndef _foobar2000_helpers_file_list_helper_ #define _foobar2000_helpers_file_list_helper_ namespace file_list_helper { //list guaranteed to be sorted by metadb::path_compare class file_list_from_metadb_handle_list : public pfc::list_base_const_t<const char*> { public: void init_from_list(const list_base_const_t<metadb_handle_ptr> & p_list); void init_from_list_display(const list_base_const_t<metadb_handle_ptr> & p_list); t_size get_count() const; void get_item_ex(const char * & p_out,t_size n) const; ~file_list_from_metadb_handle_list(); private: void __add(const char * p_what); pfc::ptr_list_t<char> m_data; }; }; #endif //_foobar2000_helpers_file_list_helper_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/file_move_helper.cpp ================================================ #include "stdafx.h" static bool grab_items_by_path(pfc::list_base_t<metadb_handle_ptr> & p_out,const char * p_path,abort_callback & p_abort) { try { pfc::string8 path; filesystem::g_get_canonical_path(p_path,path); p_out.remove_all(); service_ptr_t<input_info_reader> reader; input_entry::g_open_for_info_read(reader,0,path,p_abort); static_api_ptr_t<metadb> l_metadb; const t_uint32 count = reader->get_subsong_count(); for(t_uint32 n=0;n<count;n++) { p_abort.check_e(); metadb_handle_ptr ptr; l_metadb->handle_create(ptr,make_playable_location(path,reader->get_subsong(n))); p_out.add_item(ptr); } return p_out.get_count() > 0; } catch(std::exception const &) {return false;} } file_move_helper::file_move_helper() {} file_move_helper::~file_move_helper() {} file_move_helper::file_move_helper(const file_move_helper & p_src) {*this = p_src;} bool file_move_helper::take_snapshot(const char * p_path,abort_callback & p_abort) { in_metadb_sync blah; m_source_handles.remove_all(); m_data.set_size(0); if (!grab_items_by_path(m_source_handles,p_path,p_abort)) return false; t_size n, m = m_source_handles.get_count(); m_data.set_size(m); for(n=0;n<m;n++) { m_data[n].m_location = m_source_handles[n]->get_location(); m_data[n].m_have_info = m_source_handles[n]->get_info(m_data[n].m_info); m_data[n].m_stats = m_source_handles[n]->get_filestats(); } return true; } bool file_move_helper::on_moved(const char * p_path,abort_callback & p_abort) { bool ret; file_move_callback_manager cb; ret = on_moved(p_path,p_abort,cb); cb.run_callback(); return ret; } bool file_move_helper::on_copied(const char * p_path,abort_callback & p_abort) { bool ret; file_move_callback_manager cb; ret = on_copied(p_path,p_abort,cb); cb.run_callback(); return ret; } bool file_move_helper::on_moved(const char * p_path,abort_callback & p_abort,file_move_callback_manager & p_cb) { file_path_canonical path(p_path); if (m_data.get_size() == 0) return false; if (!metadb::path_compare(path,get_source_path())) return true; metadb_handle_list items; make_new_item_list(items,path,p_cb); /*if (p_update_db)*/ p_cb.on_library_add_items(items); p_cb.on_library_remove_items(m_source_handles); p_cb.on_moved(pfc::list_single_ref_t<const char*>(get_source_path()),pfc::list_single_ref_t<const char*>(path)); return true; } bool file_move_helper::on_copied(const char * p_path,abort_callback & p_abort,file_move_callback_manager & p_cb) { file_path_canonical path(p_path); if (m_data.get_size() == 0) return false; if (!metadb::path_compare(path,get_source_path())) return true; metadb_handle_list items; make_new_item_list(items,path,p_cb); /*if (p_update_db)*/ p_cb.on_library_add_items(items); p_cb.on_copied(pfc::list_single_ref_t<const char*>(get_source_path()),pfc::list_single_ref_t<const char*>(path)); return true; } bool file_move_helper::g_on_deleted(const pfc::list_base_const_t<const char *> & p_files) { file_operation_callback::g_on_files_deleted(p_files); return true; } void file_move_helper::make_new_item_list(pfc::list_base_t<metadb_handle_ptr> & p_out,const char * p_new_path,file_move_callback_manager & p_cb) { pfc::string8 new_path; filesystem::g_get_canonical_path(p_new_path,new_path); t_size n; const t_size m = m_data.get_size(); static_api_ptr_t<metadb> api; pfc::array_t<metadb_handle_ptr> hint_handles; pfc::array_t<const file_info*> hint_infos; pfc::array_t<t_filestats> hint_stats; hint_handles.set_size(m); hint_infos.set_size(m); hint_stats.set_size(m); t_size hintptr = 0; for(n=0;n<m;n++) { metadb_handle_ptr temp; api->handle_create(temp,make_playable_location(new_path,m_data[n].m_location.get_subsong())); if (m_data[n].m_have_info) { hint_handles[hintptr] = temp; hint_infos[hintptr] = &m_data[n].m_info; hint_stats[hintptr] = m_data[n].m_stats; hintptr++; } p_out.add_item(temp); } if (hintptr > 0) { p_cb.on_hint( pfc::list_const_array_t<metadb_handle_ptr,const pfc::array_t<metadb_handle_ptr> &>(hint_handles,hintptr), pfc::list_const_array_t<const file_info *,const pfc::array_t<const file_info *> &>(hint_infos,hintptr), pfc::list_const_array_t<t_filestats,const pfc::array_t<t_filestats> &>(hint_stats,hintptr) ); /* static_api_ptr_t<metadb_io>()->hint_multi( list_const_array_t<metadb_handle_ptr,const array_t<metadb_handle_ptr> &>(hint_handles,hintptr), list_const_array_t<const file_info *,const array_t<const file_info *> &>(hint_infos,hintptr), list_const_array_t<t_filestats,const array_t<t_filestats> &>(hint_stats,hintptr), bit_array_false());*/ } } const char * file_move_helper::get_source_path() const { return m_data.get_size() > 0 ? m_data[0].m_location.get_path() : 0; } t_size file_move_helper::g_filter_dead_files_sorted_make_mask(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead,bit_array_var & p_mask) { t_size n, m = p_data.get_count(); t_size found = 0; for(n=0;n<m;n++) { t_size dummy; bool dead = p_dead.bsearch_t(metadb::path_compare,p_data.get_item(n)->get_path(),dummy); if (dead) found++; p_mask.set(n,dead); } return found; } t_size file_move_helper::g_filter_dead_files_sorted(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead) { bit_array_bittable mask(p_data.get_count()); t_size found = g_filter_dead_files_sorted_make_mask(p_data,p_dead,mask); if (found > 0) p_data.remove_mask(mask); return found; } t_size file_move_helper::g_filter_dead_files(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead) { pfc::ptr_list_t<const char> temp; temp.add_items(p_dead); temp.sort_t(metadb::path_compare); return g_filter_dead_files_sorted(p_data,temp); } void file_move_callback_manager::on_library_add_items(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) { m_added.add_items(p_data); } void file_move_callback_manager::on_library_remove_items(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) { m_removed.add_items(p_data); } void file_move_callback_manager::on_moved(const pfc::string_list_const & p_from,const pfc::string_list_const & p_to) { assert(p_from.get_count() == p_to.get_count()); m_move_from += p_from; m_move_to += p_to; } void file_move_callback_manager::on_copied(const pfc::string_list_const & p_from,const pfc::string_list_const & p_to) { assert(p_from.get_count() == p_to.get_count()); m_copy_from += p_from; m_copy_to += p_to; } void file_move_callback_manager::on_hint(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const pfc::list_base_const_t<const file_info*> & p_infos,const pfc::list_base_const_t<t_filestats> & p_stats) { m_hint_handles.add_items(p_list); m_hint_stats.add_items(p_stats); { t_size old_count = m_hint_infos.get_count(), delta = p_infos.get_count(); m_hint_infos.set_count(old_count + delta); for(t_size n=0;n<delta;n++) m_hint_infos[old_count+n] = *p_infos[n]; } /* metadb_handle_list m_hint_handles; list_t<file_info_impl_const> m_hint_infos; list_t<t_filestats> m_hint_stats;*/ } void file_move_callback_manager::run_callback() { if (m_hint_handles.get_count() > 0) { static_api_ptr_t<metadb_io>()->hint_multi( m_hint_handles, pfc::ptr_list_const_array_t<const file_info,const pfc::list_t<file_info_const_impl> &>(m_hint_infos,m_hint_infos.get_count()), m_hint_stats, bit_array_false()); m_hint_infos.remove_all(); m_hint_stats.remove_all(); //trick to make sure values don't get wiped //m_hint_handles.remove_all(); } assert(m_copy_from.get_count() == m_copy_to.get_count()); assert(m_move_from.get_count() == m_move_to.get_count()); static_api_ptr_t<library_manager> api_library_manager; if (m_added.get_count() > 0) { api_library_manager->add_items(m_added); m_added.remove_all(); } if (m_removed.get_count()) { api_library_manager->remove_items(m_removed); m_removed.remove_all(); } if (m_copy_from.get_count() > 0) { file_operation_callback::g_on_files_copied(m_copy_from,m_copy_to); m_copy_from.remove_all(); m_copy_to.remove_all(); } if (m_move_from.get_count() > 0) { file_operation_callback::g_on_files_moved(m_move_from,m_move_to); m_move_from.remove_all(); m_move_to.remove_all(); } //trick to make sure values don't get wiped m_hint_handles.remove_all(); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/file_move_helper.h ================================================ class file_move_callback_manager { public: void on_library_add_items(const pfc::list_base_const_t<metadb_handle_ptr> & p_data); void on_library_remove_items(const pfc::list_base_const_t<metadb_handle_ptr> & p_data); void on_moved(const pfc::string_list_const & p_from,const pfc::string_list_const & p_to); void on_copied(const pfc::string_list_const & p_from,const pfc::string_list_const & p_to); void on_hint(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const pfc::list_base_const_t<const file_info*> & p_infos,const pfc::list_base_const_t<t_filestats> & p_stats); void run_callback(); private: metadb_handle_list m_removed; metadb_handle_list m_added; pfc::string_list_impl m_copy_from,m_copy_to; pfc::string_list_impl m_move_from,m_move_to; metadb_handle_list m_hint_handles; pfc::list_t<file_info_const_impl> m_hint_infos; pfc::list_t<t_filestats> m_hint_stats; }; class file_move_helper { public: file_move_helper(); ~file_move_helper(); file_move_helper(const file_move_helper & p_src); bool take_snapshot(const char * p_path,abort_callback & p_abort); bool on_moved(const char * p_path,abort_callback & p_abort); bool on_copied(const char * p_path,abort_callback & p_abort); bool on_moved(const char * p_path,abort_callback & p_abort,file_move_callback_manager & p_cb); bool on_copied(const char * p_path,abort_callback & p_abort,file_move_callback_manager & p_cb); static bool g_on_deleted(const pfc::list_base_const_t<const char *> & p_files); static t_size g_filter_dead_files_sorted_make_mask(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead,bit_array_var & p_mask); static t_size g_filter_dead_files_sorted(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead); static t_size g_filter_dead_files(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead); private: struct t_entry { playable_location_impl m_location; file_info_i m_info; t_filestats m_stats; bool m_have_info; }; metadb_handle_list m_source_handles; pfc::array_t<t_entry> m_data; const char * get_source_path() const; void make_new_item_list(pfc::list_base_t<metadb_handle_ptr> & p_out,const char * p_path,file_move_callback_manager & p_cb); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/file_win32_wrapper.h ================================================ namespace file_win32_helpers { static t_filesize get_size(HANDLE p_handle) { union { t_uint64 val64; t_uint32 val32[2]; } u; u.val64 = 0; SetLastError(NO_ERROR); u.val32[0] = GetFileSize(p_handle,reinterpret_cast<DWORD*>(&u.val32[1])); if (GetLastError()!=NO_ERROR) exception_io_from_win32(GetLastError()); return u.val64; } static void seek(HANDLE p_handle,t_sfilesize p_position,file::t_seek_mode p_mode) { union { t_int64 temp64; struct { DWORD temp_lo; LONG temp_hi; }; }; temp64 = p_position; SetLastError(ERROR_SUCCESS); temp_lo = SetFilePointer(p_handle,temp_lo,&temp_hi,(DWORD)p_mode); if (GetLastError() != ERROR_SUCCESS) exception_io_from_win32(GetLastError()); } } template<bool p_seekable,bool p_writeable> class file_win32_wrapper_t : public file { public: file_win32_wrapper_t(HANDLE p_handle) : m_handle(p_handle), m_position(0) { } static service_ptr_t<file> g_CreateFile(const char * p_path,DWORD p_access,DWORD p_sharemode,LPSECURITY_ATTRIBUTES p_security_attributes,DWORD p_createmode,DWORD p_flags,HANDLE p_template) { SetLastError(NO_ERROR); HANDLE handle = uCreateFile(p_path,p_access,p_sharemode,p_security_attributes,p_createmode,p_flags,p_template); if (handle == INVALID_HANDLE_VALUE) exception_io_from_win32(GetLastError()); try { return g_create_from_handle(handle); } catch(...) {CloseHandle(handle); throw;} } static service_ptr_t<file> g_create_from_handle(HANDLE p_handle) { return new service_impl_t<file_win32_wrapper_t<p_seekable,p_writeable> >(p_handle); } void reopen(abort_callback & p_abort) {seek(0,p_abort);} void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { if (!p_writeable) throw exception_io_denied(); pfc::static_assert_t< (sizeof(t_size) >= sizeof(DWORD)) >(); t_size bytes_written_total = 0; if (sizeof(t_size) == sizeof(DWORD)) { p_abort.check_e(); DWORD bytes_written = 0; SetLastError(ERROR_SUCCESS); if (!WriteFile(m_handle,p_buffer,(DWORD)p_bytes,&bytes_written,0)) exception_io_from_win32(GetLastError()); if (bytes_written != p_bytes) throw exception_io("Write failure"); bytes_written_total = bytes_written; m_position += bytes_written; } else { while(bytes_written_total < p_bytes) { p_abort.check_e(); DWORD bytes_written = 0; DWORD delta = (DWORD) pfc::min_t<t_size>(p_bytes - bytes_written_total, 0x80000000); SetLastError(ERROR_SUCCESS); if (!WriteFile(m_handle,(const t_uint8*)p_buffer + bytes_written_total,delta,&bytes_written,0)) exception_io_from_win32(GetLastError()); if (bytes_written != delta) throw exception_io("Write failure"); bytes_written_total += bytes_written; m_position += bytes_written; } } } t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { pfc::static_assert_t< (sizeof(t_size) >= sizeof(DWORD)) >(); t_size bytes_read_total = 0; if (sizeof(t_size) == sizeof(DWORD)) { p_abort.check_e(); DWORD bytes_read = 0; SetLastError(ERROR_SUCCESS); if (!ReadFile(m_handle,p_buffer,pfc::downcast_guarded<DWORD>(p_bytes),&bytes_read,0)) exception_io_from_win32(GetLastError()); bytes_read_total = bytes_read; m_position += bytes_read; } else { while(bytes_read_total < p_bytes) { p_abort.check_e(); DWORD bytes_read = 0; DWORD delta = (DWORD) pfc::min_t<t_size>(p_bytes - bytes_read_total, 0x80000000); SetLastError(ERROR_SUCCESS); if (!ReadFile(m_handle,(t_uint8*)p_buffer + bytes_read_total,delta,&bytes_read,0)) exception_io_from_win32(GetLastError()); bytes_read_total += bytes_read; m_position += bytes_read; if (bytes_read != delta) break; } } return bytes_read_total; } t_filesize get_size(abort_callback & p_abort) { p_abort.check_e(); return file_win32_helpers::get_size(m_handle); } t_filesize get_position(abort_callback & p_abort) { p_abort.check_e(); return m_position; } void resize(t_filesize p_size,abort_callback & p_abort) { if (!p_writeable) throw exception_io_denied(); p_abort.check_e(); if (m_position != p_size) { file_win32_helpers::seek(m_handle,p_size,file::seek_from_beginning); } SetLastError(ERROR_SUCCESS); if (!SetEndOfFile(m_handle)) { DWORD code = GetLastError(); if (m_position != p_size) try {file_win32_helpers::seek(m_handle,m_position,file::seek_from_beginning);} catch(...) {} exception_io_from_win32(code); } if (m_position > p_size) m_position = p_size; if (m_position != p_size) file_win32_helpers::seek(m_handle,m_position,file::seek_from_beginning); } #if 0 void set_eof(abort_callback & p_abort) { if (!p_writeable) throw exception_io_denied(); p_abort.check_e(); SetLastError(ERROR_SUCCESS); if (!SetEndOfFile(m_handle)) exception_io_from_win32(GetLastError()); } #endif void seek(t_filesize p_position,abort_callback & p_abort) { if (!p_seekable) throw exception_io_object_not_seekable(); p_abort.check_e(); if (p_position > file_win32_helpers::get_size(m_handle)) throw exception_io_seek_out_of_range(); file_win32_helpers::seek(m_handle,p_position,file::seek_from_beginning); m_position = p_position; //return seek_ex((t_sfilesize)p_position,file::seek_from_beginning,p_abort); } #if 0 void seek_ex(t_sfilesize p_position,file::t_seek_mode p_mode,abort_callback & p_abort) { if (!p_seekable) throw exception_io_object_not_seekable(); p_abort.check_e(); file_win32_helpers::seek(m_handle,p_position,p_mode); m_position = (t_filesize) temp64; } #endif bool can_seek() {return p_seekable;} bool get_content_type(pfc::string_base & out) {return false;} bool is_in_memory() {return false;} void on_idle(abort_callback & p_abort) {p_abort.check_e();} t_filetimestamp get_timestamp(abort_callback & p_abort) { p_abort.check_e(); FlushFileBuffers(m_handle); SetLastError(ERROR_SUCCESS); t_filetimestamp temp; if (!GetFileTime(m_handle,0,0,(FILETIME*)&temp)) exception_io_from_win32(GetLastError()); return temp; } bool is_remote() {return false;} ~file_win32_wrapper_t() {CloseHandle(m_handle);} private: HANDLE m_handle; t_filesize m_position; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/file_wrapper_simple.cpp ================================================ #include "stdafx.h" t_size file_wrapper_simple::read(void * p_buffer,t_size p_bytes) { if (m_has_failed) return 0; try { return m_file->read(p_buffer,p_bytes,m_abort); } catch(std::exception const &) { m_has_failed = true; return 0; } } t_size file_wrapper_simple::write(const void * p_buffer,t_size p_bytes) { if (m_has_failed) return 0; try { m_file->write(p_buffer,p_bytes,m_abort); return p_bytes; } catch(std::exception const &) { m_has_failed = true; return 0; } } bool file_wrapper_simple::seek(t_filesize p_offset) { if (m_has_failed) return false; try { m_file->seek(p_offset,m_abort); return true; } catch(std::exception const &) { m_has_failed = true; return false; } } t_filesize file_wrapper_simple::get_position() { if (m_has_failed) return filesize_invalid; try { return m_file->get_position(m_abort); } catch(std::exception const &) { m_has_failed = true; return filesize_invalid; } } bool file_wrapper_simple::truncate() { if (m_has_failed) return false; try { m_file->set_eof(m_abort); return true; } catch(std::exception) { m_has_failed = true; return true; } } t_filesize file_wrapper_simple::get_size() { if (m_has_failed) return filesize_invalid; try { return m_file->get_size(m_abort); } catch(std::exception const &) { m_has_failed = true; return filesize_invalid; } } bool file_wrapper_simple::can_seek() { if (m_has_failed) return false; try { return m_file->can_seek(); } catch(std::exception const &) { m_has_failed = true; return false; } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/file_wrapper_simple.h ================================================ class file_wrapper_simple { public: explicit file_wrapper_simple(const service_ptr_t<file> & p_file,abort_callback & p_abort) : m_file(p_file), m_abort(p_abort), m_has_failed(false) {} inline bool has_failed() const {return m_has_failed;} inline void reset_status() {m_has_failed = false;} t_size read(void * p_buffer,t_size p_bytes); t_size write(const void * p_buffer,t_size p_bytes); bool seek(t_filesize p_offset); t_filesize get_position(); t_filesize get_size(); bool can_seek(); bool truncate(); private: service_ptr_t<file> m_file; abort_callback & m_abort; bool m_has_failed; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/format_title_group.cpp ================================================ #include "stdafx.h" namespace { class titleformat_hook_impl : public titleformat_hook { public: titleformat_hook_impl(pfc::list_base_const_t<metadb_handle_ptr> const & p_items,titleformat_hook * p_hook) : m_hook(p_hook) { m_infos.set_size(p_items.get_count()); for(t_size n = 0; n < p_items.get_count(); n++) { if (p_items[n]->get_info_locked(m_infos[n])) m_infos[n] = NULL; } } bool process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) { if (m_hook != NULL) { if (m_hook->process_field(p_out,p_name,p_name_length,p_found_flag)) return true; } if (process_own_field(p_out,p_name,p_name_length,p_found_flag)) return true; for(t_size n = 0; n < m_infos.get_size(); n++) { if (m_infos[n] != NULL) { if (titleformat_hook_impl_file_info(make_playable_location("",0),m_infos[n]).process_field(p_out,p_name,p_name_length,p_found_flag)) return true; } } return false; } bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) { if (m_hook != NULL) { if (m_hook->process_function(p_out,p_name,p_name_length,p_params,p_found_flag)) return true; } for(t_size n = 0; n < m_infos.get_size(); n++) { if (m_infos[n] != NULL) { if (titleformat_hook_impl_file_info(make_playable_location("",0),m_infos[n]).process_function(p_out,p_name,p_name_length,p_params,p_found_flag)) return true; } } return false; } private: bool process_own_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) { if (stricmp_utf8_ex(p_name,p_name_length,"multiitem",infinite) == 0) { if (m_infos.get_size() > 1) { p_out->write_int(titleformat_inputtypes::unknown,1); p_found_flag = true; } else { p_out->write_int(titleformat_inputtypes::unknown,0); p_found_flag = false; } return true; } else { return false; } } in_metadb_sync m_lock; titleformat_hook * m_hook; pfc::array_t<const file_info*> m_infos; }; } void format_title_group(pfc::list_base_const_t<metadb_handle_ptr> const & p_items,titleformat_hook * p_hook,pfc::string_base & p_out,service_ptr_t<titleformat_object> p_script,titleformat_text_filter * p_filter) { p_script->run(&titleformat_hook_impl(p_items,p_hook),p_out,p_filter); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/format_title_group.h ================================================ void format_title_group(pfc::list_base_const_t<metadb_handle_ptr> const & p_list,titleformat_hook * p_hook,pfc::string_base & p_out,service_ptr_t<titleformat_object> p_script,titleformat_text_filter * p_filter); ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/helpers.h ================================================ #ifndef _FOOBAR2000_SDK_HELPERS_H_ #define _FOOBAR2000_SDK_HELPERS_H_ #include "input_helpers.h" #include "create_directory_helper.h" #include "dialog_resize_helper.h" #include "dropdown_helper.h" #include "window_placement_helper.h" #include "win32_dialog.h" #include "wildcard.h" #include "cuesheet_index_list.h" #include "cue_creator.h" #include "cue_parser.h" #include "search_filter.h" #include "text_file_loader.h" #include "file_list_helper.h" #include "preload_info_helper.h" #include "listview_helper.h" #include "stream_buffer_helper.h" #include "file_info_const_impl.h" #include "file_wrapper_simple.h" #include "dynamic_bitrate_helper.h" #include "cfg_guidlist.h" #include "cfg_structlist.h" #include "file_win32_wrapper.h" #include "file_move_helper.h" #include "file_cached.h" #include "seekabilizer.h" #include "string_filter.h" #include "bitreader_helper.h" #include "mp3_utils.h" #include "win32_misc.h" #include "metadb_io_hintlist.h" #include "format_title_group.h" #include "inplace_edit.h" #include "meta_table_builder.h" #endif //_FOOBAR2000_SDK_HELPERS_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/inplace_edit.cpp ================================================ #include "stdafx.h" using namespace InPlaceEdit; static const TCHAR g_prop_instance[] = _T("{91154665-2B6A-42da-BB2A-A132EC20927F}"); namespace { static pfc::avltree_t<HWND> g_editboxes; static HHOOK g_hook = NULL; static LRESULT CALLBACK GMouseProc(int nCode,WPARAM wParam,LPARAM lParam) { if (nCode >= 0) { const MOUSEHOOKSTRUCT * mhs = (const MOUSEHOOKSTRUCT *) lParam; switch(wParam) { case WM_NCLBUTTONDOWN: case WM_NCRBUTTONDOWN: case WM_NCMBUTTONDOWN: case WM_NCXBUTTONDOWN: case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: if (!g_editboxes.have_item(mhs->hwnd)) { SetFocus(mhs->hwnd); } break; } } return CallNextHookEx(g_hook,nCode,wParam,lParam); } void on_editbox_creation(HWND p_editbox) { g_editboxes.add(p_editbox); if (g_hook == NULL) { g_hook = SetWindowsHookEx(WH_MOUSE,GMouseProc,NULL,GetCurrentThreadId()); } } void on_editbox_destruction(HWND p_editbox) { g_editboxes.remove(p_editbox); if (g_editboxes.get_count() == 0 && g_hook != NULL) { UnhookWindowsHookEx(pfc::replace_null_t(g_hook)); } } enum { MSG_COMPLETION = WM_USER, MSG_DISABLE_EDITING, }; class InPlaceEditHook { public: InPlaceEditHook(HWND p_wnd) : m_oldproc(NULL) { SetProp(p_wnd,g_prop_instance,reinterpret_cast<HANDLE>(this)); m_oldproc = uHookWindowProc(p_wnd,GEditHook); on_editbox_creation(p_wnd); } private: static void ForwardCompletion(HWND p_mywnd,unsigned p_code) { HWND owner = GetParent(p_mywnd); SendMessage(owner,MSG_DISABLE_EDITING,0,0); PostMessage(owner,MSG_COMPLETION,p_code,0); EnableWindow(p_mywnd,FALSE); } ~InPlaceEditHook() {} LRESULT EditHook(HWND p_wnd,UINT p_msg,WPARAM p_wp,LPARAM p_lp) { switch(p_msg) { case WM_GETDLGCODE: return DLGC_WANTALLKEYS; case WM_KILLFOCUS: ForwardCompletion(p_wnd,KEditLostFocus); return m_oldproc(p_wnd,p_msg,p_wp,p_lp); case WM_CHAR: switch(p_wp) { case VK_RETURN: if (!IsKeyPressed(VK_LCONTROL) && !IsKeyPressed(VK_RCONTROL)) { ForwardCompletion(p_wnd,KEditEnter); return 0; } break; case VK_TAB: ForwardCompletion(p_wnd,IsKeyPressed(VK_SHIFT) ? KEditShiftTab : KEditTab); return 0; case VK_ESCAPE: ForwardCompletion(p_wnd,KEditAborted); return 0; } return m_oldproc(p_wnd,p_msg,p_wp,p_lp); case WM_DESTROY: { WNDPROC l_wndproc = m_oldproc; uHookWindowProc(p_wnd,l_wndproc); RemoveProp(p_wnd,g_prop_instance); on_editbox_destruction(p_wnd); try {delete this;} catch(...) {} return l_wndproc(p_wnd,p_msg,p_wp,p_lp); } default: return m_oldproc(p_wnd,p_msg,p_wp,p_lp); } } static LRESULT CALLBACK GEditHook(HWND p_wnd,UINT p_msg,WPARAM p_wp,LPARAM p_lp) { InPlaceEditHook * instance = reinterpret_cast<InPlaceEditHook*>( GetProp(p_wnd,g_prop_instance) ); if (instance == NULL) return DefWindowProc(p_wnd,p_msg,p_wp,p_lp); return instance->EditHook(p_wnd,p_msg,p_wp,p_lp); } WNDPROC m_oldproc; }; class InPlaceEditContainer { public: InPlaceEditContainer(HWND p_parentwnd,const RECT & p_rect,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) : m_content(p_content), m_notify(p_notify), m_completed(false), m_initialized(false), m_changed(false), m_disable_editing(false) { { static volatile unsigned increment; TCHAR classname[64]; wsprintf(classname,_T("{54340C80-248C-4b8e-8CD4-D624A8E9377B}/%u"),++increment); m_class_scope.toggle_on(0,GWndProc,0,sizeof(void*),NULL,NULL,NULL,classname,NULL); } HWND container, edit; RECT rect_cropped; { RECT client; SetLastError(0); if (!GetClientRect(p_parentwnd,&client)) throw exception_win32(GetLastError()); IntersectRect(&rect_cropped,&client,&p_rect); } AdjustWindowRect(&rect_cropped,WS_BORDER|WS_CHILD,FALSE); SetLastError(0); container = CreateWindowEx( 0, (const TCHAR*) m_class_scope.get_class(), _T(""), WS_BORDER|WS_CHILD, rect_cropped.left,rect_cropped.top, rect_cropped.right-rect_cropped.left,rect_cropped.bottom-rect_cropped.top, p_parentwnd,NULL, core_api::get_my_instance(),reinterpret_cast<void*>(this)); if (container == NULL) throw exception_win32(GetLastError()); try { RECT parent_client; SetLastError(0); if (!GetClientRect(container,&parent_client)) throw exception_win32(GetLastError()); SetLastError(0); edit = CreateWindowEx( /*WS_EX_STATICEDGE*/ 0, WC_EDIT,TEXT(""), ((p_flags & KFlagMultiLine) ? (WS_VSCROLL|ES_MULTILINE) : ES_AUTOHSCROLL) | ((p_flags & KFlagReadOnly) ? ES_READONLY : 0) | WS_CHILD|ES_LEFT|WS_VISIBLE,//parent is invisible now 0,0, parent_client.right,parent_client.bottom, container, (HMENU)ID_MYEDIT, core_api::get_my_instance(), 0); if (edit == NULL) throw exception_win32(GetLastError()); SendMessage(edit,WM_SETFONT,SendMessage(p_parentwnd,WM_GETFONT,0,0),0); uSetWindowText(edit,*m_content); SendMessage(edit,EM_SETSEL,0,-1); } catch(...) { PostMessage(container,MSG_COMPLETION,InPlaceEdit::KEditAborted,0); return; } try { new InPlaceEditHook(edit); } catch(...) { PostMessage(container,MSG_COMPLETION,InPlaceEdit::KEditAborted,0); return; } ShowWindow(container,SW_SHOW); SetFocus(edit); m_initialized = true; } private: enum {ID_MYEDIT = 666}; LRESULT WndProc(HWND p_wnd,UINT p_msg,WPARAM p_wp,LPARAM p_lp) { switch(p_msg) { case WM_MOUSEWHEEL: return 0; case MSG_DISABLE_EDITING: ShowWindow(p_wnd,SW_HIDE); UpdateWindow(GetParent(p_wnd)); m_disable_editing = true; return 0; case MSG_COMPLETION: PFC_ASSERT(m_initialized); if ((p_wp & KEditMaskReason) != KEditLostFocus) { SetFocus(GetParent(p_wnd)); } OnCompletion(p_wp); DestroyWindow(p_wnd); return 0; case WM_COMMAND: switch(p_wp) { case (EN_CHANGE << 16) | ID_MYEDIT: if (m_initialized && !m_disable_editing) { uGetDlgItemText(p_wnd,ID_MYEDIT,*m_content); m_changed = true; } return 0; default: return 0; } case WM_DESTROY: try {delete this;}catch(...){} SetWindowLongPtr(p_wnd,0,NULL); return DefWindowProc(p_wnd,p_msg,p_wp,p_lp); default: return DefWindowProc(p_wnd,p_msg,p_wp,p_lp); } } static LRESULT CALLBACK GWndProc(HWND p_wnd,UINT p_msg,WPARAM p_wp,LPARAM p_lp) { if (p_msg == WM_CREATE) { const CREATESTRUCT * ptr = reinterpret_cast<const CREATESTRUCT*>(p_lp); SetWindowLongPtr(p_wnd,0,reinterpret_cast<LONG_PTR>(ptr->lpCreateParams)); } InPlaceEditContainer * instance = reinterpret_cast<InPlaceEditContainer*>(GetWindowLongPtr(p_wnd,0)); if (instance != NULL) { return instance->WndProc(p_wnd,p_msg,p_wp,p_lp); } else { return DefWindowProc(p_wnd,p_msg,p_wp,p_lp); } } void OnCompletion(unsigned p_status) { if (!m_completed) { m_completed = true; p_status &= KEditMaskReason; unsigned code = p_status; if (m_changed && p_status != KEditAborted) code |= KEditFlagContentChanged; if (m_notify.is_valid()) m_notify->on_completion(code); } } registerclass_scope_delayed m_class_scope; pfc::rcptr_t<pfc::string_base> m_content; completion_notify_ptr m_notify; bool m_completed; bool m_initialized, m_changed; bool m_disable_editing; }; } static void fail(completion_notify_ptr p_notify) { completion_notify::g_signal_completion_async(p_notify,KEditAborted); } void InPlaceEdit::Start(HWND p_parentwnd,const RECT & p_rect,bool p_multiline,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) { StartEx(p_parentwnd,p_rect,p_multiline ? KFlagMultiLine : 0, p_content,p_notify); } void InPlaceEdit::Start_FromListView(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) { Start_FromListViewEx(p_listview,p_item,p_subitem,p_linecount,0,p_content,p_notify); } bool InPlaceEdit::TableEditAdvance(unsigned & p_item,unsigned & p_column, unsigned p_item_count,unsigned p_column_count, unsigned p_whathappened) { if (p_item >= p_item_count || p_column >= p_column_count) return false; int delta = 0; switch(p_whathappened & KEditMaskReason) { case KEditEnter: delta = (int) p_column_count; break; case KEditTab: delta = 1; break; case KEditShiftTab: delta = -1; break; default: return false; } while(delta > 0) { p_column++; if (p_column >= p_column_count) { p_column = 0; p_item++; if (p_item >= p_item_count) return false; } delta--; } while(delta < 0) { if (p_column == 0) { if (p_item == 0) return false; p_item--; p_column = p_column_count; } p_column--; delta++; } return true; } void InPlaceEdit::StartEx(HWND p_parentwnd,const RECT & p_rect,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) { try { new InPlaceEditContainer(p_parentwnd,p_rect,p_flags,p_content,p_notify); } catch(...) { fail(p_notify); } } void InPlaceEdit::Start_FromListViewEx(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) { try { ListView_EnsureVisible(p_listview,p_item,FALSE); RECT itemrect; if (!ListView_GetSubItemRect(p_listview,p_item,p_subitem,LVIR_BOUNDS,&itemrect)) throw pfc::exception("ListView_GetSubItemRect failure"); const bool multiline = p_linecount > 1; if (multiline) { itemrect.bottom = itemrect.top + (itemrect.bottom - itemrect.top) * p_linecount; } StartEx(p_listview,itemrect,p_flags | (multiline ? KFlagMultiLine : 0),p_content,p_notify); } catch(...) { fail(p_notify); } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/inplace_edit.h ================================================ namespace InPlaceEdit { enum { KEditAborted = 0, KEditTab, KEditShiftTab, KEditEnter, KEditLostFocus, KEditMaskReason = 0xFF, KEditFlagContentChanged = 0x100, KFlagReadOnly = 1 << 0, KFlagMultiLine = 1 << 1, }; void Start(HWND p_parentwnd,const RECT & p_rect,bool p_multiline,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify); void StartEx(HWND p_parentwnd,const RECT & p_rect,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify); void Start_FromListView(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify); void Start_FromListViewEx(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify); bool TableEditAdvance(unsigned & p_item,unsigned & p_column, unsigned p_item_count,unsigned p_column_count, unsigned p_whathappened); class CTableEditHelper { public: void TableEdit_Start(HWND p_listview,unsigned p_item,unsigned p_column,unsigned p_itemcount,unsigned p_columncount,unsigned p_basecolumn,unsigned p_flags = 0) { if (m_notify.is_valid()) return; m_listview = p_listview; m_item = p_item; m_column = p_column; m_itemcount = p_itemcount; m_columncount = p_columncount; m_basecolumn = p_basecolumn; m_flags = p_flags; __Start(); } void TableEdit_Abort(bool p_forwardcontent) { if (m_notify.is_valid()) { m_notify->orphan(); m_notify.release(); if (p_forwardcontent) { if (m_content.is_valid()) { pfc::string8 temp(*m_content); m_content.release(); TableEdit_SetItemText(m_item,m_column,temp); } } else { m_content.release(); } SetFocus(NULL); } } bool TableEdit_IsActive() const { return m_notify.is_valid(); } virtual bool TableEdit_GetItemText(unsigned p_item,unsigned p_column,pfc::string_base & p_out,unsigned & p_linecount) { listview_helper::get_item_text(m_listview,p_item,p_column + m_basecolumn,p_out); p_linecount = pfc::is_multiline(p_out) ? 5 : 1; return true; } virtual void TableEdit_SetItemText(unsigned p_item,unsigned p_column,const char * p_text) { listview_helper::set_item_text(m_listview,p_item,p_column + m_basecolumn,p_text); } void on_task_completion(unsigned p_taskid,unsigned p_state) { if (p_taskid == KTaskID) { m_notify.release(); if (m_content.is_valid()) { if (p_state & InPlaceEdit::KEditFlagContentChanged) { TableEdit_SetItemText(m_item,m_column,*m_content); } m_content.release(); } if (InPlaceEdit::TableEditAdvance(m_item,m_column,m_itemcount,m_columncount,p_state)) { __Start(); } } } ~CTableEditHelper() { if (m_notify.is_valid()) { m_notify->orphan(); m_notify.release(); } } protected: HWND TableEdit_GetListView() const {return m_listview;} private: void __Start() { listview_helper::select_single_item(m_listview,m_item); m_content.new_t(); unsigned linecount = 1; if (!TableEdit_GetItemText(m_item,m_column,*m_content,linecount)) return; m_notify = completion_notify_create(this,KTaskID); InPlaceEdit::Start_FromListViewEx(m_listview,m_item,m_column+m_basecolumn,linecount,m_flags,m_content,m_notify); } enum { KTaskID = 0xc0ffee }; HWND m_listview; unsigned m_item,m_column; unsigned m_itemcount,m_columncount,m_basecolumn; unsigned m_flags; pfc::rcptr_t<pfc::string8> m_content; service_ptr_t<completion_notify_orphanable> m_notify; }; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/input_helpers.cpp ================================================ #include "stdafx.h" void input_helper::open(service_ptr_t<file> p_filehint,metadb_handle_ptr p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect,bool p_skip_hints) { open(p_filehint,p_location->get_location(),p_flags,p_abort,p_from_redirect,p_skip_hints); } static void process_fullbuffer(service_ptr_t<file> & p_file,const char * p_path,t_filesize p_fullbuffer,abort_callback & p_abort) { if (p_fullbuffer > 0) { if (p_file.is_empty()) { service_ptr_t<filesystem> fs; if (filesystem::g_get_interface(fs,p_path)) { fs->open(p_file,p_path,filesystem::open_mode_read,p_abort); } } if (p_file.is_valid()) { t_filesize size = p_file->get_size(p_abort); if (size != filesize_invalid && size <= p_fullbuffer) { service_ptr_t<file> l_file_buffered; if (reader_membuffer_mirror::g_create(l_file_buffered,p_file,p_abort)) { p_file = l_file_buffered; } } } } } void input_helper::open(service_ptr_t<file> p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect,bool p_skip_hints) { p_abort.check(); if (m_input.is_empty() || metadb::path_compare(p_location.get_path(),m_path) != 0) { m_input.release(); service_ptr_t<file> l_file = p_filehint; process_fullbuffer(l_file,p_location.get_path(),m_fullbuffer,p_abort); TRACK_CODE("input_entry::g_open_for_decoding", input_entry::g_open_for_decoding(m_input,l_file,p_location.get_path(),p_abort,p_from_redirect) ); if (!p_skip_hints) { try { static_api_ptr_t<metadb_io>()->hint_reader(m_input.get_ptr(),p_location.get_path(),p_abort); } catch(exception_io_data) { //don't fail to decode when this barfs m_input.release(); if (l_file.is_valid()) l_file->reopen(p_abort); TRACK_CODE("input_entry::g_open_for_decoding", input_entry::g_open_for_decoding(m_input,l_file,p_location.get_path(),p_abort,p_from_redirect) ); } } m_path = p_location.get_path(); } TRACK_CODE("input_decoder::initialize",m_input->initialize(p_location.get_subsong_index(),p_flags,p_abort)); } void input_helper::close() { m_input.release(); } bool input_helper::is_open() { return m_input.is_valid(); } bool input_helper::run(audio_chunk & p_chunk,abort_callback & p_abort) { if (m_input.is_valid()) { TRACK_CODE("input_decoder::run",return m_input->run(p_chunk,p_abort)); } else { throw pfc::exception_bug_check(); } } void input_helper::seek(double seconds,abort_callback & p_abort) { if (m_input.is_valid()) { TRACK_CODE("input_decoder::seek",m_input->seek(seconds,p_abort)); } else { throw pfc::exception_bug_check(); } } bool input_helper::can_seek() { if (m_input.is_valid()) { return m_input->can_seek(); } else { throw pfc::exception_bug_check(); } } void input_helper::set_full_buffer(t_filesize val) { m_fullbuffer = val; } void input_helper::on_idle(abort_callback & p_abort) { if (m_input.is_valid()) { TRACK_CODE("input_decoder::on_idle",m_input->on_idle(p_abort)); } } bool input_helper::get_dynamic_info(file_info & p_out,double & p_timestamp_delta) { if (m_input.is_valid()) { TRACK_CODE("input_decoder::get_dynamic_info",return m_input->get_dynamic_info(p_out,p_timestamp_delta)); } else { throw pfc::exception_bug_check(); } } bool input_helper::get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta) { if (m_input.is_valid()) { TRACK_CODE("input_decoder::get_dynamic_info_track",return m_input->get_dynamic_info_track(p_out,p_timestamp_delta)); } else { throw pfc::exception_bug_check(); } } void input_helper::get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) { if (m_input.is_valid()) { TRACK_CODE("input_decoder::get_info",m_input->get_info(p_subsong,p_info,p_abort)); } else { throw pfc::exception_bug_check(); } } const char * input_helper::get_path() const { return m_path; } input_helper::input_helper() : m_fullbuffer(0) { } void input_helper::g_get_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect) { service_ptr_t<input_info_reader> instance; input_entry::g_open_for_info_read(instance,0,p_location.get_path(),p_abort,p_from_redirect); instance->get_info(p_location.get_subsong_index(),p_info,p_abort); } void input_helper::g_set_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect) { service_ptr_t<input_info_writer> instance; input_entry::g_open_for_info_write(instance,0,p_location.get_path(),p_abort,p_from_redirect); instance->set_info(p_location.get_subsong_index(),p_info,p_abort); instance->commit(p_abort); } bool dead_item_filter::run(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,bit_array_var & p_mask) { file_list_helper::file_list_from_metadb_handle_list path_list; path_list.init_from_list(p_list); metadb_handle_list valid_handles; static_api_ptr_t<metadb> l_metadb; for(t_size pathidx=0;pathidx<path_list.get_count();pathidx++) { if (is_aborting()) return false; on_progress(pathidx,path_list.get_count()); const char * path = path_list[pathidx]; if (filesystem::g_is_remote_safe(path)) { metadb_handle_ptr temp; l_metadb->handle_create(temp,make_playable_location(path,0)); valid_handles.add_item(temp); } else { try { service_ptr_t<input_info_reader> reader; input_entry::g_open_for_info_read(reader,0,path,*this); t_uint32 count = reader->get_subsong_count(); for(t_uint32 n=0;n<count && !is_aborting();n++) { metadb_handle_ptr ptr; t_uint32 index = reader->get_subsong(n); l_metadb->handle_create(ptr,make_playable_location(path,index)); valid_handles.add_item(ptr); } } catch(...) {} } } if (is_aborting()) return false; valid_handles.sort_by_pointer(); for(t_size listidx=0;listidx<p_list.get_count();listidx++) { bool dead = valid_handles.bsearch_by_pointer(p_list[listidx]) == infinite; if (dead) console::formatter() << "Dead item: " << p_list[listidx]; p_mask.set(listidx,dead); } return !is_aborting(); } namespace { class dead_item_filter_impl_simple : public dead_item_filter { public: inline dead_item_filter_impl_simple(abort_callback & p_abort) : m_abort(p_abort) {} bool is_aborting() const {return m_abort.is_aborting();} abort_callback_event get_abort_event() const {return m_abort.get_abort_event();} void on_progress(t_size p_position,t_size p_total) {} private: abort_callback & m_abort; }; } bool input_helper::g_mark_dead(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,bit_array_var & p_mask,abort_callback & p_abort) { dead_item_filter_impl_simple filter(p_abort); return filter.run(p_list,p_mask); } void input_info_read_helper::open(const char * p_path,abort_callback & p_abort) { if (m_input.is_empty() || metadb::path_compare(m_path,p_path) != 0) { TRACK_CODE("input_entry::g_open_for_info_read",input_entry::g_open_for_info_read(m_input,0,p_path,p_abort)); m_path = p_path; } } void input_info_read_helper::get_info(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,abort_callback & p_abort) { open(p_location.get_path(),p_abort); TRACK_CODE("input_info_reader::get_file_stats",p_stats = m_input->get_file_stats(p_abort)); TRACK_CODE("input_info_reader::get_info",m_input->get_info(p_location.get_subsong_index(),p_info,p_abort)); } void input_info_read_helper::get_info_check(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,bool & p_reloaded,abort_callback & p_abort) { open(p_location.get_path(),p_abort); t_filestats newstats; TRACK_CODE("input_info_reader::get_file_stats",newstats = m_input->get_file_stats(p_abort)); if (metadb_handle::g_should_reload(p_stats,newstats,true)) { p_stats = newstats; TRACK_CODE("input_info_reader::get_info",m_input->get_info(p_location.get_subsong_index(),p_info,p_abort)); p_reloaded = true; } else { p_reloaded = false; } } void input_helper_cue::open(service_ptr_t<file> p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,double p_start,double p_length) { p_abort.check(); m_start = p_start; m_position = 0; m_dynamic_info_trigger = false; m_dynamic_info_track_trigger = false; m_input.open(p_filehint,p_location,p_flags,p_abort,true,true); if (!m_input.can_seek()) throw exception_io_object_not_seekable(); if (m_start > 0) { m_input.seek(m_start,p_abort); } if (p_length > 0) { m_length = p_length; } else { file_info_impl temp; m_input.get_info(0,temp,p_abort); double ref_length = temp.get_length(); if (ref_length <= 0) throw exception_io_data(); m_length = ref_length - m_start + p_length /* negative or zero */; if (m_length <= 0) throw exception_io_data(); } } void input_helper_cue::close() {m_input.close();} bool input_helper_cue::is_open() {return m_input.is_open();} bool input_helper_cue::run(audio_chunk & p_chunk,abort_callback & p_abort) { p_abort.check(); if (m_length > 0) { if (m_position >= m_length) return false; m_dynamic_info_trigger = true; m_dynamic_info_track_trigger = true; if (!m_input.run(p_chunk,p_abort)) return false; t_uint64 max = (t_uint64) audio_math::time_to_samples(m_length - m_position, p_chunk.get_sample_rate()); if (max == 0) {//handle rounding accidents, this normally shouldn't trigger m_position = m_length; return false; } t_size samples = p_chunk.get_sample_count(); if ((t_uint64)samples > max) { p_chunk.set_sample_count((unsigned)max); m_position = m_length; } else { m_position += p_chunk.get_duration(); } return true; } else { if (!m_input.run(p_chunk,p_abort)) return false; m_position += p_chunk.get_duration(); return true; } } void input_helper_cue::seek(double p_seconds,abort_callback & p_abort) { m_dynamic_info_trigger = false; m_dynamic_info_track_trigger = false; if (m_length <= 0 || p_seconds < m_length) { m_input.seek(p_seconds + m_start,p_abort); m_position = p_seconds; } else { m_position = m_length; } } bool input_helper_cue::can_seek() {return true;} void input_helper_cue::set_full_buffer(t_filesize val) {m_input.set_full_buffer(val);} void input_helper_cue::on_idle(abort_callback & p_abort) {m_input.on_idle(p_abort);} bool input_helper_cue::get_dynamic_info(file_info & p_out,double & p_timestamp_delta) { if (m_dynamic_info_trigger) { m_dynamic_info_trigger = false; return m_input.get_dynamic_info(p_out,p_timestamp_delta); } else { return false; } } bool input_helper_cue::get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta) { if (m_dynamic_info_track_trigger) { m_dynamic_info_track_trigger = false; return m_input.get_dynamic_info_track(p_out,p_timestamp_delta); } else { return false; } } const char * input_helper_cue::get_path() const {return m_input.get_path();} void input_helper_cue::get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) {m_input.get_info(p_subsong,p_info,p_abort);} ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/input_helpers.h ================================================ #ifndef _INPUT_HELPERS_H_ #define _INPUT_HELPERS_H_ class input_helper { public: input_helper(); void open(service_ptr_t<file> p_filehint,metadb_handle_ptr p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect = false,bool p_skip_hints = false); void open(service_ptr_t<file> p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect = false,bool p_skip_hints = false); void close(); bool is_open(); bool run(audio_chunk & p_chunk,abort_callback & p_abort); void seek(double seconds,abort_callback & p_abort); bool can_seek(); void set_full_buffer(t_filesize val); void on_idle(abort_callback & p_abort); bool get_dynamic_info(file_info & p_out,double & p_timestamp_delta); bool get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta); //! Retrieves path of currently open file. const char * get_path() const; //! Retrieves info about specific subsong of currently open file. void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort); static void g_get_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect = false); static void g_set_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect = false); static bool g_mark_dead(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,bit_array_var & p_mask,abort_callback & p_abort); private: service_ptr_t<input_decoder> m_input; pfc::string8 m_path; t_filesize m_fullbuffer; }; class NOVTABLE dead_item_filter : public abort_callback { public: virtual void on_progress(t_size p_position,t_size p_total) = 0; bool run(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,bit_array_var & p_mask); }; class input_info_read_helper { public: input_info_read_helper() {} void get_info(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,abort_callback & p_abort); void get_info_check(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,bool & p_reloaded,abort_callback & p_abort); private: void open(const char * p_path,abort_callback & p_abort); pfc::string8 m_path; service_ptr_t<input_info_reader> m_input; }; class input_helper_cue { public: void open(service_ptr_t<file> p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,double p_start,double p_length); void close(); bool is_open(); bool run(audio_chunk & p_chunk,abort_callback & p_abort); void seek(double seconds,abort_callback & p_abort); bool can_seek(); void set_full_buffer(t_filesize val); void on_idle(abort_callback & p_abort); bool get_dynamic_info(file_info & p_out,double & p_timestamp_delta); bool get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta); const char * get_path() const; void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort); private: input_helper m_input; double m_start,m_length,m_position; bool m_dynamic_info_trigger,m_dynamic_info_track_trigger; }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/listview_helper.cpp ================================================ #include "stdafx.h" #define uTEXT(blah) TEXT(blah) #define uLVM_SETITEM LVM_SETITEM #define uLVM_INSERTITEM LVM_INSERTITEM #define uLVM_INSERTCOLUMN LVM_INSERTCOLUMN #define uLVM_GETITEM LVM_GETITEM namespace listview_helper { unsigned insert_item(HWND p_listview,unsigned p_index,const char * p_name,LPARAM p_param) { if (p_index == infinite) p_index = ListView_GetItemCount(p_listview); LVITEM item; memset(&item,0,sizeof(item)); pfc::stringcvt::string_os_from_utf8 os_string_temp(p_name); os_string_temp.convert(p_name); item.mask = LVIF_TEXT | LVIF_PARAM; item.iItem = p_index; item.lParam = p_param; item.pszText = const_cast<TCHAR*>(os_string_temp.get_ptr()); LRESULT ret = uSendMessage(p_listview,uLVM_INSERTITEM,0,(LPARAM)&item); if (ret < 0) return infinite; else return (unsigned) ret; } unsigned insert_column(HWND p_listview,unsigned p_index,const char * p_name,unsigned p_width_dlu) { pfc::stringcvt::string_os_from_utf8 os_string_temp; os_string_temp.convert(p_name); RECT rect = {0,0,p_width_dlu,0}; MapDialogRect(GetParent(p_listview),&rect); LVCOLUMN data; memset(&data,0,sizeof(data)); data.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT; data.fmt = LVCFMT_LEFT; data.cx = rect.right; data.pszText = const_cast<TCHAR*>(os_string_temp.get_ptr()); LRESULT ret = uSendMessage(p_listview,uLVM_INSERTCOLUMN,p_index,(LPARAM)&data); if (ret < 0) return infinite; else return (unsigned) ret; } void get_item_text(HWND p_listview,unsigned p_index,unsigned p_column,pfc::string_base & p_out) { enum {buffer_length = 4096}; TCHAR buffer[buffer_length]; ListView_GetItemText(p_listview,p_index,p_column,buffer,buffer_length); p_out = pfc::stringcvt::string_utf8_from_os(buffer,buffer_length); } bool set_item_text(HWND p_listview,unsigned p_index,unsigned p_column,const char * p_name) { LVITEM item; memset(&item,0,sizeof(item)); pfc::stringcvt::string_os_from_utf8 os_string_temp; os_string_temp.convert(p_name); item.mask = LVIF_TEXT; item.iItem = p_index; item.iSubItem = p_column; item.pszText = const_cast<TCHAR*>(os_string_temp.get_ptr()); return uSendMessage(p_listview,uLVM_SETITEM,0,(LPARAM)&item) ? true : false; } bool is_item_selected(HWND p_listview,unsigned p_index) { LVITEM item; memset(&item,0,sizeof(item)); item.mask = LVIF_STATE; item.iItem = p_index; item.stateMask = LVIS_SELECTED; if (!uSendMessage(p_listview,uLVM_GETITEM,0,(LPARAM)&item)) return false; return (item.state & LVIS_SELECTED) ? true : false; } bool set_item_selection(HWND p_listview,unsigned p_index,bool p_state) { LVITEM item; memset(&item,0,sizeof(item)); item.mask = LVIF_STATE; item.iItem = p_index; item.stateMask = LVIS_SELECTED; item.state = p_state ? LVIS_SELECTED : 0; return uSendMessage(p_listview,uLVM_SETITEM,0,(LPARAM)&item) ? true : false; } bool select_single_item(HWND p_listview,unsigned p_index) { LRESULT temp = SendMessage(p_listview,LVM_GETITEMCOUNT,0,0); if (temp < 0) return false; ListView_SetSelectionMark(p_listview,p_index); unsigned n; const unsigned m = pfc::downcast_guarded<unsigned>(temp); for(n=0;n<m;n++) { enum {mask = LVIS_FOCUSED | LVIS_SELECTED}; ListView_SetItemState(p_listview,n,n == p_index ? mask : 0, mask); } return ensure_visible(p_listview,p_index); } bool ensure_visible(HWND p_listview,unsigned p_index) { return uSendMessage(p_listview,LVM_ENSUREVISIBLE,p_index,FALSE) ? true : false; } } bool ListView_GetContextMenuPoint(HWND p_list,LPARAM p_coords,POINT & p_point,int & p_selection) { if ((DWORD)p_coords == (DWORD)infinite) { int firstsel = ListView_GetFirstSelection(p_list); if (firstsel >= 0) { RECT rect; if (!ListView_GetItemRect(p_list,firstsel,&rect,LVIR_BOUNDS)) return false; p_point.x = (rect.left + rect.right) / 2; p_point.y = (rect.top + rect.bottom) / 2; if (!ClientToScreen(p_list,&p_point)) return false; } else { RECT rect; if (!GetClientRect(p_list,&rect)) return false; p_point.x = (rect.left + rect.right) / 2; p_point.y = (rect.top + rect.bottom) / 2; if (!ClientToScreen(p_list,&p_point)) return false; } p_selection = firstsel; return true; } else { POINT pt = {(short)LOWORD(p_coords),(short)HIWORD(p_coords)}; p_point = pt; POINT client = pt; if (!ScreenToClient(p_list,&client)) return false; LVHITTESTINFO info; memset(&info,0,sizeof(info)); info.pt = client; p_selection = ListView_HitTest(p_list,&info); return true; } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/listview_helper.h ================================================ namespace listview_helper { unsigned insert_item(HWND p_listview,unsigned p_index,const char * p_name,LPARAM p_param);//returns index of new item on success, infinite on failure unsigned insert_column(HWND p_listview,unsigned p_index,const char * p_name,unsigned p_width_dlu);//returns index of new item on success, infinite on failure bool set_item_text(HWND p_listview,unsigned p_index,unsigned p_column,const char * p_name); bool is_item_selected(HWND p_listview,unsigned p_index); bool set_item_selection(HWND p_listview,unsigned p_index,bool p_state); bool select_single_item(HWND p_listview,unsigned p_index); bool ensure_visible(HWND p_listview,unsigned p_index); void get_item_text(HWND p_listview,unsigned p_index,unsigned p_column,pfc::string_base & p_out); }; static int ListView_GetFirstSelection(HWND p_listview) { return ListView_GetNextItem(p_listview,-1,LVNI_SELECTED); } static int ListView_GetSingleSelection(HWND p_listview) { if (ListView_GetSelectedCount(p_listview) != 1) return -1; return ListView_GetFirstSelection(p_listview); } static int ListView_GetFocusItem(HWND p_listview) { return ListView_GetNextItem(p_listview,-1,LVNI_FOCUSED); } static bool ListView_IsItemSelected(HWND p_listview,int p_index) { return ListView_GetItemState(p_listview,p_index,LVIS_SELECTED) != 0; } bool ListView_GetContextMenuPoint(HWND p_list,LPARAM p_coords,POINT & p_point,int & p_selection); ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/meta_table_builder.h ================================================ class __meta_table_enum_wrapper { public: __meta_table_enum_wrapper(file_info & p_info) : m_info(p_info) {} void operator() (const char * p_name,const pfc::chain_list_t<pfc::string8> & p_values) { t_size index = infinite; for(pfc::chain_list_t<pfc::string8>::const_iterator iter = p_values.first(); iter.is_valid(); ++iter) { if (index == infinite) index = m_info.__meta_add_unsafe(p_name,*iter); else m_info.meta_add_value(index,*iter); } } private: file_info & m_info; }; //! Purpose: building a file_info metadata table from loose input without search-for-existing-entry bottleneck class meta_table_builder { public: void add(const char * p_name,const char * p_value,t_size p_value_len = infinite) { if (file_info::g_is_valid_field_name(p_name)) { __add(p_name).insert_last()->set_string(p_value,p_value_len); } } void remove(const char * p_name) { m_data.remove(p_name); } void set(const char * p_name,const char * p_value,t_size p_value_len = infinite) { if (file_info::g_is_valid_field_name(p_name)) { t_entry & entry = __add(p_name); entry.remove_all(); entry.insert_last()->set_string(p_value,p_value_len); } } pfc::chain_list_t<pfc::string8> & add(const char * p_name) { if (!file_info::g_is_valid_field_name(p_name)) throw pfc::exception_bug_check();//we return a reference, nothing smarter to do return __add(p_name); } void finalize(file_info & p_info) { p_info.meta_remove_all(); m_data.enumerate(__meta_table_enum_wrapper(p_info)); } void from_info(const file_info & p_info) { m_data.remove_all(); from_info_overwrite(p_info); } void from_info_overwrite(const file_info & p_info) { for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk ) { const t_size valuecount = p_info.meta_enum_value_count(metawalk); if (valuecount > 0) { t_entry & entry = add(p_info.meta_enum_name(metawalk)); entry.remove_all(); for(t_size valuewalk = 0; valuewalk < valuecount; ++valuewalk) { entry.insert_last(p_info.meta_enum_value(metawalk,valuewalk)); } } } } private: typedef pfc::chain_list_t<pfc::string8> t_entry; t_entry & __add(const char * p_name) { return m_data.find_or_add(p_name); } pfc::map_t<pfc::string8,t_entry,file_info::field_name_comparator> m_data; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/metadb_io_hintlist.cpp ================================================ #include "stdafx.h" void metadb_io_hintlist::run() { if (m_entries.get_count() > 0) { static_api_ptr_t<metadb_io>()->hint_multi_async( metadb_io_hintlist_wrapper_part1(m_entries), metadb_io_hintlist_wrapper_part2(m_entries), metadb_io_hintlist_wrapper_part3(m_entries), metadb_io_hintlist_wrapper_part4(m_entries) ); } m_entries.remove_all(); } void metadb_io_hintlist::add(metadb_handle_ptr const & p_handle,const file_info & p_info,t_filestats const & p_stats,bool p_fresh) { t_entry entry; entry.m_handle = p_handle; entry.m_info.new_t(p_info); entry.m_stats = p_stats; entry.m_fresh = p_fresh; m_entries.add_item(entry); } void metadb_io_hintlist::hint_reader(service_ptr_t<input_info_reader> p_reader, const char * p_path,abort_callback & p_abort) { static_api_ptr_t<metadb> api; const t_uint32 subsongcount = p_reader->get_subsong_count(); t_filestats stats = p_reader->get_file_stats(p_abort); for(t_uint32 subsong = 0; subsong < subsongcount; subsong++) { t_uint32 subsong_id = p_reader->get_subsong(subsong); metadb_handle_ptr handle; api->handle_create(handle,make_playable_location(p_path,subsong_id)); if (handle->should_reload(stats,true)) { file_info_impl temp; p_reader->get_info(subsong_id,temp,p_abort); add(handle,temp,stats,true); } } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/metadb_io_hintlist.h ================================================ class metadb_io_hintlist { public: void hint_reader(service_ptr_t<input_info_reader> p_reader, const char * p_path,abort_callback & p_abort); void add(metadb_handle_ptr const & p_handle,const file_info & p_info,t_filestats const & p_stats,bool p_fresh); void run(); private: struct t_entry { metadb_handle_ptr m_handle; pfc::rcptr_t<file_info_const_impl> m_info; t_filestats m_stats; bool m_fresh; }; class metadb_io_hintlist_wrapper_part1 : public pfc::list_base_const_t<metadb_handle_ptr> { public: metadb_io_hintlist_wrapper_part1(const pfc::list_base_const_t<metadb_io_hintlist::t_entry> & p_list) : m_list(p_list) {} t_size get_count() const {return m_list.get_count();} void get_item_ex(metadb_handle_ptr & p_out, t_size n) const {p_out = m_list[n].m_handle;} private: const pfc::list_base_const_t<metadb_io_hintlist::t_entry> & m_list; }; class metadb_io_hintlist_wrapper_part2 : public pfc::list_base_const_t<const file_info*> { public: metadb_io_hintlist_wrapper_part2(const pfc::list_base_const_t<metadb_io_hintlist::t_entry> & p_list) : m_list(p_list) {} t_size get_count() const {return m_list.get_count();} void get_item_ex(const file_info* & p_out, t_size n) const {p_out = &*m_list[n].m_info;} private: const pfc::list_base_const_t<metadb_io_hintlist::t_entry> & m_list; }; class metadb_io_hintlist_wrapper_part3 : public pfc::list_base_const_t<t_filestats> { public: metadb_io_hintlist_wrapper_part3(const pfc::list_base_const_t<metadb_io_hintlist::t_entry> & p_list) : m_list(p_list) {} t_size get_count() const {return m_list.get_count();} void get_item_ex(t_filestats & p_out, t_size n) const {p_out = m_list[n].m_stats;} private: const pfc::list_base_const_t<metadb_io_hintlist::t_entry> & m_list; }; class metadb_io_hintlist_wrapper_part4 : public bit_array { public: metadb_io_hintlist_wrapper_part4(const pfc::list_base_const_t<metadb_io_hintlist::t_entry> & p_list) : m_list(p_list) {} bool get(t_size n) const {return m_list[n].m_fresh;} private: const pfc::list_base_const_t<metadb_io_hintlist::t_entry> & m_list; }; pfc::list_t<t_entry,pfc::alloc_fast> m_entries; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/mp3_utils.cpp ================================================ #include "stdafx.h" using namespace bitreader_helper; static unsigned extract_header_bits(const t_uint8 p_header[4],unsigned p_base,unsigned p_bits) { assert(p_base+p_bits<=32); return extract_bits(p_header,p_base,p_bits); } namespace { class header_parser { public: header_parser(const t_uint8 p_header[4]) : m_bitptr(0) { memcpy(m_header,p_header,4); } unsigned read(unsigned p_bits) { unsigned ret = extract_header_bits(m_header,m_bitptr,p_bits); m_bitptr += p_bits; return ret; } private: t_uint8 m_header[4]; unsigned m_bitptr; }; } typedef t_uint16 uint16; static const uint16 bitrate_table_l1v1[16] = { 0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448, 0}; static const uint16 bitrate_table_l2v1[16] = { 0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384, 0}; static const uint16 bitrate_table_l3v1[16] = { 0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320, 0}; static const uint16 bitrate_table_l1v2[16] = { 0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256, 0}; static const uint16 bitrate_table_l23v2[16] = { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160, 0}; static const uint16 sample_rate_table[] = {11025,12000,8000}; unsigned mp3_utils::QueryMPEGFrameSize(const t_uint8 p_header[4]) { TMPEGFrameInfo info; if (!ParseMPEGFrameHeader(info,p_header)) return 0; return info.m_bytes; } bool mp3_utils::ParseMPEGFrameHeader(TMPEGFrameInfo & p_info,const t_uint8 p_header[4]) { enum {MPEG_LAYER_1 = 3, MPEG_LAYER_2 = 2, MPEG_LAYER_3 = 1}; enum {_MPEG_1 = 3, _MPEG_2 = 2, _MPEG_25 = 0}; header_parser parser(p_header); if (parser.read(11) != 0x7FF) return false; unsigned mpeg_version = parser.read(2); unsigned layer = parser.read(2); unsigned protection = parser.read(1); unsigned bitrate_index = parser.read(4); unsigned sample_rate_index = parser.read(2); if (sample_rate_index == 11) return false;//reserved unsigned paddingbit = parser.read(1); int paddingdelta = 0; parser.read(1);//private unsigned channel_mode = parser.read(2); parser.read(2);//channel_mode_extension parser.read(1);//copyright parser.read(1);//original parser.read(2);//emphasis unsigned bitrate = 0,sample_rate = 0; switch(layer) { default: return false; case MPEG_LAYER_3: paddingdelta = paddingbit ? 1 : 0; p_info.m_layer = 3; switch(mpeg_version) { case _MPEG_1: p_info.m_duration = 1152; bitrate = bitrate_table_l3v1[bitrate_index]; break; case _MPEG_2: case _MPEG_25: p_info.m_duration = 576; bitrate = bitrate_table_l23v2[bitrate_index]; break; default: return false; } break; case MPEG_LAYER_2: paddingdelta = paddingbit ? 1 : 0; p_info.m_duration = 1152; p_info.m_layer = 2; switch(mpeg_version) { case _MPEG_1: bitrate = bitrate_table_l2v1[bitrate_index]; break; case _MPEG_2: case _MPEG_25: bitrate = bitrate_table_l23v2[bitrate_index]; break; default: return false; } break; case MPEG_LAYER_1: paddingdelta = paddingbit ? 4 : 0; p_info.m_duration = 384; p_info.m_layer = 1; switch(mpeg_version) { case _MPEG_1: bitrate = bitrate_table_l1v1[bitrate_index]; break; case _MPEG_2: case _MPEG_25: bitrate = bitrate_table_l1v2[bitrate_index]; break; default: return false; } break; } if (bitrate == 0) return false; sample_rate = sample_rate_table[sample_rate_index]; if (sample_rate == 0) return false; switch(mpeg_version) { case _MPEG_1: sample_rate *= 4; p_info.m_mpegversion = MPEG_1; break; case _MPEG_2: sample_rate *= 2; p_info.m_mpegversion = MPEG_2; break; case _MPEG_25: p_info.m_mpegversion = MPEG_25; break; } switch(channel_mode) { case 0: case 1: case 2: p_info.m_channels = 2; break; case 3: p_info.m_channels = 1; break; } p_info.m_channel_mode = channel_mode; p_info.m_sample_rate = sample_rate; p_info.m_bytes = ( bitrate /*kbps*/ * (1000/8) /* kbps-to-bytes*/ * p_info.m_duration /*samples-per-frame*/ ) / sample_rate + paddingdelta; if (p_info.m_layer == 1) p_info.m_bytes &= ~3; p_info.m_crc = protection == 0; return true; } unsigned mp3header::get_samples_per_frame() { mp3_utils::TMPEGFrameInfo fr; if (!decode(fr)) return 0; return fr.m_duration; } bool mp3_utils::IsSameStream(TMPEGFrameInfo const & p_frame1,TMPEGFrameInfo const & p_frame2) { return p_frame1.m_channel_mode == p_frame2.m_channel_mode && p_frame1.m_sample_rate == p_frame2.m_sample_rate && p_frame1.m_layer == p_frame2.m_layer && p_frame1.m_mpegversion == p_frame2.m_mpegversion; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/mp3_utils.h ================================================ namespace mp3_utils { enum { MPG_MD_STEREO=0, MPG_MD_JOINT_STEREO=1, MPG_MD_DUAL_CHANNEL=2, MPG_MD_MONO=3, }; typedef t_uint8 byte; enum {MPEG_1, MPEG_2, MPEG_25}; struct TMPEGFrameInfo { unsigned m_bytes; unsigned m_sample_rate; unsigned m_layer; unsigned m_mpegversion; unsigned m_channels; unsigned m_duration; unsigned m_channel_mode; bool m_crc; }; bool ParseMPEGFrameHeader(TMPEGFrameInfo & p_info,const t_uint8 p_header[4]); unsigned QueryMPEGFrameSize(const t_uint8 p_header[4]); bool IsSameStream(TMPEGFrameInfo const & p_frame1,TMPEGFrameInfo const & p_frame2); }; class mp3header { t_uint8 bytes[4]; public: inline void copy(const mp3header & src) {memcpy(bytes,src.bytes,4);} inline void copy_raw(const void * src) {memcpy(bytes,src,4);} inline mp3header(const mp3header & src) {copy(src);} inline mp3header() {} inline const mp3header & operator=(const mp3header & src) {copy(src); return *this;} inline void get_bytes(void * out) {memcpy(out,bytes,4);} inline unsigned get_frame_size() const {return mp3_utils::QueryMPEGFrameSize(bytes);} inline bool decode(mp3_utils::TMPEGFrameInfo & p_out) {return mp3_utils::ParseMPEGFrameHeader(p_out,bytes);} unsigned get_samples_per_frame(); }; static inline mp3header mp3header_from_buffer(const void * p_buffer) { mp3header temp; temp.copy_raw(p_buffer); return temp; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/preload_info_helper.cpp ================================================ #include "stdafx.h" bool preload_info_helper::preload_info(metadb_handle_ptr p_item,HWND p_parent_window,bool p_showerror) { if (p_item->is_info_loaded()) return true; return static_api_ptr_t<metadb_io>()->load_info(p_item,metadb_io::load_info_default,p_parent_window,p_showerror) == metadb_io::load_info_success; } bool preload_info_helper::are_all_loaded(const pfc::list_base_const_t<metadb_handle_ptr> & p_items) { t_size n, m = p_items.get_count(); for(n=0;n<m;n++) { if (!p_items[n]->is_info_loaded()) return false; } return true; } bool preload_info_helper::preload_info_multi(const pfc::list_base_const_t<metadb_handle_ptr> & p_items,HWND p_parent_window,bool p_showerror) { if (are_all_loaded(p_items)) return true; return static_api_ptr_t<metadb_io>()->load_info_multi(p_items,metadb_io::load_info_default,p_parent_window,p_showerror) == metadb_io::load_info_success; } bool preload_info_helper::preload_info_multi_modalcheck(const pfc::list_base_const_t<metadb_handle_ptr> & p_items,HWND p_parent_window,bool p_showerror) { if (are_all_loaded(p_items)) return true; if (!modal_dialog_scope::can_create()) return false; return static_api_ptr_t<metadb_io>()->load_info_multi(p_items,metadb_io::load_info_default,p_parent_window,p_showerror) == metadb_io::load_info_success; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/preload_info_helper.h ================================================ namespace preload_info_helper { bool __declspec(deprecated("Use metadb_io_v2 methods instead.")) preload_info(metadb_handle_ptr p_item,HWND p_parent_window,bool p_showerror); bool __declspec(deprecated("Use metadb_io_v2 methods instead.")) preload_info_multi(const pfc::list_base_const_t<metadb_handle_ptr> & p_items,HWND p_parent_window,bool p_showerror); bool __declspec(deprecated("Use metadb_io_v2 methods instead.")) preload_info_multi_modalcheck(const pfc::list_base_const_t<metadb_handle_ptr> & p_items,HWND p_parent_window,bool p_showerror); bool are_all_loaded(const pfc::list_base_const_t<metadb_handle_ptr> & p_items); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/search_filter.cpp ================================================ #include "stdafx.h" static bool is_spacing(char c) {return c == ' ' || c==10 || c==13;} static bool is_alpha(char c) {return (c>='a' && c<='z') || (c>='A' && c<='Z');} static bool is_alphanumeric(char c) {return is_alpha(c) || (c>='0' && c<='9');} static bool is_spacing_or_null(char c) {return c == 0 || is_spacing(c);} static bool is_format_spec(const char * ptr) {return strchr(ptr,'%') || strchr(ptr,'#') || strchr(ptr,'$');} namespace search_tools { class substring_filter { pfc::ptr_list_t<char> data; mutable pfc::string8_fastalloc temp; public: substring_filter(const char * src) { while(*src) { while(*src && is_spacing(*src)) src++; t_size ptr = 0; while(src[ptr] && !is_spacing(src[ptr])) ptr++; if (ptr) { uStringLower(temp,src,ptr); data.add_item(strdup(temp)); } src+=ptr; } } bool test(const char * src) const { t_size n,m = data.get_count(); if (m==0) return false; uStringLower(temp,src); for(n=0;n<m;n++) { if (!strstr(temp,data[n])) return false; } return true; } ~substring_filter() {data.free_all();} }; class filter_node_and : public filter_node { filter_node_cptr n1, n2; public: filter_node_and(filter_node_cptr p1,filter_node_cptr p2) : n1(p1), n2(p2) {} virtual bool test(const file_info * item,const metadb_handle_ptr & handle) const {return n1->test(item,handle) && n2->test(item,handle);} }; class filter_node_or : public filter_node { filter_node_cptr n1, n2; public: filter_node_or(filter_node_cptr p1,filter_node_cptr p2) : n1(p1), n2(p2) {} virtual bool test(const file_info * item,const metadb_handle_ptr & handle) const {return n1->test(item,handle) || n2->test(item,handle);} }; class filter_node_not : public filter_node { filter_node_cptr n; public: filter_node_not(filter_node_cptr p) : n(p) {} virtual bool test(const file_info * item,const metadb_handle_ptr & handle) const {return !n->test(item,handle);} }; class filter_node_missing : public filter_node { pfc::string8 m_field; public: filter_node_missing(const char * p_string,t_size p_len) { while(p_len > 0 && p_string[0] == ' ') {p_string++;p_len--;} while(p_len > 0 && p_string[p_len - 1] == ' ') p_len--; m_field.set_string(p_string,p_len); } bool test(const file_info * item,const metadb_handle_ptr & handle) const { return item->meta_find(m_field) == infinite; } }; class filter_node_is_format : public filter_node { pfc::string_simple param; service_ptr_t<titleformat_object> m_script; mutable pfc::string8_fastalloc temp; bool is_wildcard; bool test_internal(const char * src) const { if (is_wildcard) return wildcard_helper::test(src,param,false); else return !stricmp_utf8(src,param); } public: filter_node_is_format(const char * p_field,const char * p_param) : param(p_param), is_wildcard(wildcard_helper::has_wildcards(p_param)) { if (!static_api_ptr_t<titleformat_compiler>()->compile(m_script,p_field)) throw exception_parse_error(); } bool test(const file_info * item,const metadb_handle_ptr & handle) const { handle->format_title(0,temp,m_script,0); return test_internal(temp); } }; class filter_node_is : public filter_node { pfc::string_simple field,param; mutable pfc::string8_fastalloc temp; bool is_wildcard; bool test_internal(const char * src) const { if (is_wildcard) return wildcard_helper::test(src,param,false); else return !stricmp_utf8(src,param); } public: filter_node_is(const char * p_field,const char * p_param) : field(p_field), param(p_param), is_wildcard(wildcard_helper::has_wildcards(p_param)) { } bool test(const file_info * item,const metadb_handle_ptr & handle) const { t_size index = item->meta_find(field); if (index == infinite) return false; t_size n,m = item->meta_enum_value_count(index); for(n=0;n<m;n++) { if (test_internal(item->meta_enum_value(index,n))) return true; } return false; } }; class filter_node_has : public filter_node { pfc::string_simple field; mutable pfc::string8_fastalloc temp; substring_filter m_filter; public: filter_node_has(const char * p_field,const char * p_param) : field(p_field), m_filter(p_param) { } ~filter_node_has() {} bool test(const file_info * item,const metadb_handle_ptr & handle) const { t_size index = item->meta_find(field); if (index == infinite) return false; t_size n, m = item->meta_enum_value_count(index); temp.reset(); for(n=0;n<m;n++) { temp += " "; temp += item->meta_enum_value(index,n); } return m_filter.test(temp); } }; class filter_node_has_ex : public filter_node { pfc::string_simple field; substring_filter m_filter; mutable pfc::string8_fastalloc temp; public: filter_node_has_ex(const char * p_field,const char * p_param) : field(p_field), m_filter(p_param) { } bool test(const file_info * item,const metadb_handle_ptr & handle) const { handle->format_title_legacy(0,temp,field,0); return m_filter.test(temp); } }; class filter_node_simple : public filter_node { substring_filter m_filter; mutable pfc::string8_fastalloc temp; public: filter_node_simple(const char * src) : m_filter(src) {} bool test(const file_info * info,const metadb_handle_ptr & handle) const { t_size n, m = info->meta_get_count(); temp = handle->get_path(); for(n=0;n<m;n++) { t_size n1, m1 = info->meta_enum_value_count(n); for(n1=0;n1<m1;n1++) { temp += " "; temp += info->meta_enum_value(n,n1); } } m = info->info_get_count(); for(n=0;n<m;n++) { temp += " "; temp += info->info_enum_value(n); } return m_filter.test(temp); } }; class parser { const char * base; t_size len,ptr; public: inline parser() : base(0), len(0), ptr(0) {} inline parser(const char * p_ptr,t_size p_len) : base(p_ptr), len(p_len), ptr(0) {} inline parser(const parser & param) : base(param.get_ptr()), len(param.get_remaining()), ptr(0) {} inline parser(const parser & param,t_size len) : base(param.get_ptr()), len(len), ptr(0) {assert(len<=param.get_remaining());} inline const char * get_ptr() const {return base+ptr;} inline t_size get_remaining() const {return len-ptr;} inline t_size advance(t_size n) {ptr+=n; assert(ptr<=len);return n;} inline char get_char() const {return get_ptr()[0];} inline t_size get_offset() const {return ptr;} inline void reset(t_size offset=0) {ptr=offset;} t_size test_spacing() const { t_size n = 0, m = get_remaining(); const char * src = get_ptr(); while(n<m && is_spacing(src[n])) n++; return n; } t_size skip_spacing() { return advance(test_spacing()); } bool is_empty() const {return test_spacing() == get_remaining();} t_size test_token() const { parser p(*this); if (p.get_remaining()==0) return 0; if (p.get_char() == '\"') { p.advance(1); for(;;) { if (p.get_remaining()==0) return 0; if (p.get_char()=='\"') { p.advance(1); return p.get_offset(); } else p.advance(1); } } else if (p.get_char()=='(') { p.advance(1); p.skip_spacing(); do { if (p.skip_token()==0) return 0; p.skip_spacing(); if (p.get_remaining()==0) return 0; } while(p.get_char()!=')'); p.advance(1); return p.get_offset(); } else if (p.get_char()==')') return 0; else { do { p.advance(1); } while(p.get_remaining()>0 && p.get_char()!=')' && !is_spacing(p.get_char())); return p.get_offset(); } return 0; } t_size skip_token() { return advance(test_token()); } }; typedef filter_node_cptr (*operator_handler)(const parser & token_left,const parser & token_right); static filter_node_cptr operator_handler_and(const parser & token_left,const parser & token_right) { return pfc::rcnew_t<filter_node_and>(filter_node::g_create(token_left),filter_node::g_create(token_right)); } static filter_node_cptr operator_handler_or(const parser & token_left,const parser & token_right) { return pfc::rcnew_t<filter_node_or>(filter_node::g_create(token_left),filter_node::g_create(token_right)); } static filter_node_cptr operator_handler_not(const parser & token_left,const parser & token_right) { if (!token_left.is_empty()) throw exception_parse_error(); return pfc::rcnew_t<filter_node_not>(filter_node::g_create(token_right)); } static filter_node_cptr operator_handler_missing(const parser & token_left,const parser & token_right) { if (!token_right.is_empty()) throw exception_parse_error(); return pfc::rcnew_t<filter_node_missing>(token_left.get_ptr(),token_left.get_remaining()); } static void parse_string(const parser & src,pfc::string_base & out) { parser p(src); p.skip_spacing(); out.reset(); if (p.get_remaining()>0) { if (p.get_char()=='\"') { p.advance(1); while(p.get_remaining()>0 && p.get_char()!='\"') {out.add_byte(p.get_char());p.advance(1);} if (p.get_remaining() == 0) throw exception_parse_error(); return; } else { out.add_string(p.get_ptr(),p.get_remaining()); t_size trunc = out.length(); while(trunc>0 && is_spacing(out[trunc-1])) trunc--; out.truncate(trunc); if (trunc == 0) throw exception_parse_error(); return; } } else throw exception_parse_error(); } static filter_node_cptr operator_handler_has(const parser & token_left,const parser & token_right) { pfc::string8 name,value; parse_string(token_left,name); parse_string(token_right,value); if (!strcmp(name,"*")) return pfc::rcnew_t<filter_node_simple>(value); else if (is_format_spec(name)) return pfc::rcnew_t<filter_node_has_ex>(name,value); else return pfc::rcnew_t<filter_node_has>(name,value); } static filter_node_cptr operator_handler_is(const parser & token_left,const parser & token_right) { pfc::string8 name,value; parse_string(token_left,name); parse_string(token_right,value); if (is_format_spec(name)) return pfc::rcnew_t<filter_node_is_format>(name,value); else return pfc::rcnew_t<filter_node_is>(name,value); } class filter_node_mathop : public filter_node { pfc::string_simple left; service_ptr_t<titleformat_object> left_script; t_int64 rval; bool left_ex; int type; mutable pfc::string8_fastalloc ltemp; static t_int64 parse_int_or_time(const char * ptr) { bool neg = false; t_int64 a = 0; // whitespace while (ptr[0] == ' ' || ptr[0] == '\t') ptr++; // sign neg = ptr[0] == '-'; if (ptr[0] == '+' || ptr[0] == '-') ptr++; // digits if (ptr[0] >= '0' && ptr[0] <= '9') { do { a = a * 10 + (ptr[0] - '0'); ptr++; } while (*ptr >= '0' && *ptr <= '9'); } else return 0; if (ptr[0] != ':') return neg ? -a : a; ptr++; // minutes or seconds if ((ptr[0] >= '0' && ptr[0] <= '5') && (ptr[1] >= '0' && ptr[1] <= '9')) { a = a * 60 + (ptr[0] - '0') * 10 + (ptr[1] - '0'); if (ptr[2] == ':') { // seconds if ((ptr[3] >= '0' && ptr[3] <= '5') && (ptr[4] >= '0' && ptr[4] <= '9') && !(ptr[5] >= '0' && ptr[5] <= '9')) { a = a * 60 + (ptr[3] - '0') * 10 + (ptr[4] - '0'); } } } return neg ? -a : a; } public: explicit filter_node_mathop(const char * p_left,const char * p_right,int p_type) : left(p_left), rval(parse_int_or_time(p_right)), type(p_type), left_ex(is_format_spec(p_left)) { if (left_ex) static_api_ptr_t<titleformat_compiler>()->compile_safe(left_script,left); } bool test(const file_info * item,const metadb_handle_ptr & handle) const { t_int64 lval; if (left_ex) { handle->format_title(0,ltemp,left_script,0); lval = parse_int_or_time(ltemp); } else { const char * ptr = item->meta_get(left,0); if (!ptr) return false; lval = parse_int_or_time(ptr); } switch(type) { case -1: return lval < rval; case 0: return lval == rval; case 1: return lval > rval; default: assert(0); return false; } } }; static filter_node_cptr operator_handler_mathop(const parser & token_left,const parser & token_right,int type) { pfc::string8 left,right; parse_string(token_left,left); parse_string(token_right,right); return pfc::rcnew_t<filter_node_mathop>(left,right,type); } static filter_node_cptr operator_handler_greater(const parser & token_left,const parser & token_right) { return operator_handler_mathop(token_left,token_right,1); } static filter_node_cptr operator_handler_less(const parser & token_left,const parser & token_right) { return operator_handler_mathop(token_left,token_right,-1); } static filter_node_cptr operator_handler_equal(const parser & token_left,const parser & token_right) { return operator_handler_mathop(token_left,token_right,0); } struct operator_desc { const char * name; bool right_to_left; operator_handler handler; bool test_name(const char * ptr,t_size len) const { const char * ptr2 = name; while(len) { if (*ptr != *ptr2) return false; ptr++; ptr2++; len--; } return *ptr2==0; } }; static operator_desc g_operators[] = { {"OR",false,operator_handler_or}, {"AND",false,operator_handler_and}, {"NOT",false,operator_handler_not}, {"HAS",false,operator_handler_has}, {"IS",false,operator_handler_is}, {"IST",false,operator_handler_is}, {"EQUAL",false,operator_handler_equal}, {"GREATER",false,operator_handler_greater}, {"LESS",false,operator_handler_less}, {"MISSING",false,operator_handler_missing}, }; static bool has_operators(const char * src) { t_size n; for(n=0;n<tabsize(g_operators);n++) { const char * name = g_operators[n].name; t_size namelen = strlen(name); const char * ptr = src; for(;;) { ptr = strstr(ptr,name); if (ptr) { if (ptr && (ptr==src || is_spacing(ptr[-1])) && is_spacing_or_null(ptr[namelen])) return true; ptr++; } else break; } } return false; } struct token_info { t_size ptr,len; token_info() {} token_info(t_size p_ptr,t_size p_len) : ptr(p_ptr), len(p_len) {} }; static t_size test_operator(const char * ptr,t_size len) { t_size n; for(n=0;n<tabsize(g_operators);n++) { if (g_operators[n].test_name(ptr,len)) return n; } return ~0; } static bool is_operator(const char * ptr,t_size len) {return test_operator(ptr,len)!=~0;} filter_node_cptr filter_node::g_create(const parser & p_parser,bool b_allow_simple) { parser p(p_parser); if (b_allow_simple) { if (!has_operators(pfc::string8(p.get_ptr(),p.get_remaining()))) { pfc::string8 temp; parse_string(p,temp); return pfc::rcnew_t<filter_node_simple>(temp); } } pfc::list_hybrid_t<token_info,8> operators; p.skip_spacing(); while(p.get_remaining()>0) { t_size delta = p.test_token(); if (delta==0) throw exception_parse_error(); if (is_operator(p.get_ptr(),delta)) operators.add_item(token_info(p.get_offset(),delta)); p.advance(delta); p.skip_spacing(); } p.reset(); if (operators.get_count()>0) { t_size n; for(n=0;n<tabsize(g_operators);n++) { if (g_operators[n].right_to_left) { t_size m; for(m=operators.get_count()-1;m!=infinite;m--) { if (g_operators[n].test_name(p.get_ptr()+operators[m].ptr,operators[m].len)) { t_size right_base = operators[m].ptr + operators[m].len; return g_operators[n].handler(parser(p.get_ptr(),operators[m].ptr),parser(p.get_ptr()+right_base,p.get_remaining()-right_base)); } } } else { t_size m; for(m=0;m<operators.get_count();m++) { if (g_operators[n].test_name(p.get_ptr()+operators[m].ptr,operators[m].len)) { t_size right_base = operators[m].ptr + operators[m].len; return g_operators[n].handler(parser(p.get_ptr(),operators[m].ptr),parser(p.get_ptr()+right_base,p.get_remaining()-right_base)); } } } } throw exception_parse_error(); } else { p.skip_spacing(); if (p.get_remaining()>0 && p.get_char()=='(') { t_size base = p.get_offset(); t_size len = p.skip_token(); if (len==0 || len<2) throw exception_parse_error(); if (!p.is_empty()) throw exception_parse_error(); p.reset(base+1); return g_create(parser(p,len-2)); } else if (b_allow_simple) { pfc::string8 temp; parse_string(p,temp); return pfc::rcnew_t<filter_node_simple>(temp); } throw exception_parse_error(); } } filter_node_cptr filter_node::g_create(const char * ptr,t_size len,bool b_allow_simple) { return g_create(parser(ptr,len),b_allow_simple); } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/search_filter.h ================================================ namespace search_tools { PFC_DECLARE_EXCEPTION(exception_parse_error,pfc::exception,"Error parsing filter expression"); class filter_node; typedef pfc::rcptr_const_t<filter_node> filter_node_cptr; typedef pfc::rcptr_t<filter_node> filter_node_ptr; class NOVTABLE filter_node { public: virtual bool test(const file_info * item,const metadb_handle_ptr & handle) const = 0; virtual ~filter_node() {} static filter_node_cptr g_create(const char * ptr,t_size len,bool b_allow_simple = false); static filter_node_cptr g_create(const class parser & p,bool b_allow_simple = false); }; class search_filter { filter_node_cptr m_root; public: search_filter() {} ~search_filter() {} bool init(const char * pattern) { try { m_root = filter_node::g_create(pattern,strlen(pattern),true) ; return true; } catch(exception_parse_error) {return false;} } void deinit() { m_root.release(); } bool test(const file_info * item,const metadb_handle_ptr & handle) const { return m_root.is_valid() ? m_root->test(item,handle) : false; } bool test(const metadb_handle_ptr & item) const { bool rv = false; const file_info * ptr; if (item->get_info_locked(ptr)) rv = test(ptr,item); return rv; } }; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/seekabilizer.cpp ================================================ #include "stdafx.h" enum {backread_on_seek = 1024}; void seekabilizer_backbuffer::initialize(t_size p_size) { m_depth = m_cursor = 0; m_buffer.set_size(p_size); } void seekabilizer_backbuffer::write(const void * p_buffer,t_size p_bytes) { if (p_bytes >= m_buffer.get_size()) { memcpy(m_buffer.get_ptr(),(const t_uint8*)p_buffer + p_bytes - m_buffer.get_size(),m_buffer.get_size()); m_cursor = 0; m_depth = m_buffer.get_size(); } else { const t_uint8* sourceptr = (const t_uint8*) p_buffer; t_size remaining = p_bytes; while(remaining > 0) { t_size delta = m_buffer.get_size() - m_cursor; if (delta > remaining) delta = remaining; memcpy(m_buffer.get_ptr() + m_cursor,sourceptr,delta); sourceptr += delta; remaining -= delta; m_cursor = (m_cursor + delta) % m_buffer.get_size(); m_depth = pfc::min_t<t_size>(m_buffer.get_size(),m_depth + delta); } } } void seekabilizer_backbuffer::read(t_size p_backlogdepth,void * p_buffer,t_size p_bytes) const { assert(p_backlogdepth <= m_depth); assert(p_backlogdepth >= p_bytes); t_uint8* targetptr = (t_uint8*) p_buffer; t_size remaining = p_bytes; t_size cursor = (m_cursor + m_buffer.get_size() - p_backlogdepth) % m_buffer.get_size(); while(remaining > 0) { t_size delta = m_buffer.get_size() - cursor; if (delta > remaining) delta = remaining; memcpy(targetptr,m_buffer.get_ptr() + cursor,delta); targetptr += delta; remaining -= delta; cursor = (cursor + delta) % m_buffer.get_size(); } } t_size seekabilizer_backbuffer::get_depth() const { return m_depth; } t_size seekabilizer_backbuffer::get_max_depth() const { return m_buffer.get_size(); } void seekabilizer_backbuffer::reset() { m_depth = m_cursor = 0; } void seekabilizer::initialize(service_ptr_t<file> p_base,t_size p_buffer_size,abort_callback & p_abort) { m_buffer.initialize(p_buffer_size); m_file = p_base; m_position = m_position_base = 0; m_size = m_file->get_size(p_abort); } void seekabilizer::g_seekabilize(service_ptr_t<file> & p_reader,t_size p_buffer_size,abort_callback & p_abort) { if (p_reader.is_valid() && p_reader->is_remote() && p_buffer_size > 0) { service_ptr_t<seekabilizer> instance = new service_impl_t<seekabilizer>(); instance->initialize(p_reader,p_buffer_size,p_abort); p_reader = instance.get_ptr(); } } t_size seekabilizer::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { p_abort.check_e(); if (m_position > m_position_base + pfc::max_t<t_size>(m_buffer.get_max_depth(),backread_on_seek) && m_file->can_seek()) { t_filesize target = m_position; if (target < backread_on_seek) target = 0; else target -= backread_on_seek; m_file->seek(target,p_abort); m_position_base = target; } //seek ahead while(m_position > m_position_base) { enum {tempsize = 1024}; t_uint8 temp[tempsize]; t_size delta = (t_size) pfc::min_t<t_filesize>(tempsize,m_position - m_position_base); t_size bytes_read = 0; bytes_read = m_file->read(temp,delta,p_abort); m_buffer.write(temp,bytes_read); m_position_base += bytes_read; if (bytes_read < delta) { return 0; } } t_size done = 0; t_uint8 * targetptr = (t_uint8*) p_buffer; //try to read backbuffer if (m_position < m_position_base) { if (m_position_base - m_position > (t_filesize)m_buffer.get_depth()) throw exception_io_seek_out_of_range(); t_size backread_depth = (t_size) (m_position_base - m_position); t_size delta = pfc::min_t<t_size>(backread_depth,p_bytes-done); m_buffer.read(backread_depth,targetptr,delta); done += delta; m_position += delta; } //regular read if (done < p_bytes) { t_size bytes_read; bytes_read = m_file->read(targetptr+done,p_bytes-done,p_abort); m_buffer.write(targetptr+done,bytes_read); done += bytes_read; m_position += bytes_read; m_position_base += bytes_read; } return done; } t_filesize seekabilizer::get_size(abort_callback & p_abort) { p_abort.check_e(); return m_size; } t_filesize seekabilizer::get_position(abort_callback & p_abort) { p_abort.check_e(); return m_position; } void seekabilizer::seek(t_filesize p_position,abort_callback & p_abort) { assert(m_position_base >= m_buffer.get_depth()); p_abort.check_e(); if (m_size != filesize_invalid && p_position > m_size) throw exception_io_seek_out_of_range(); t_filesize lowest = m_position_base - m_buffer.get_depth(); if (p_position < lowest) { if (m_file->can_seek()) { m_buffer.reset(); t_filesize target = p_position; t_size delta = m_buffer.get_max_depth(); if (delta > backread_on_seek) delta = backread_on_seek; if (target > delta) target -= delta; else target = 0; m_file->seek(target,p_abort); m_position_base = target; } else { m_buffer.reset(); m_file->reopen(p_abort); m_position_base = 0; } } m_position = p_position; } bool seekabilizer::can_seek() { return true; } bool seekabilizer::get_content_type(pfc::string_base & p_out) {return m_file->get_content_type(p_out);} bool seekabilizer::is_in_memory() {return false;} void seekabilizer::on_idle(abort_callback & p_abort) {return m_file->on_idle(p_abort);} t_filetimestamp seekabilizer::get_timestamp(abort_callback & p_abort) { p_abort.check_e(); return m_file->get_timestamp(p_abort); } void seekabilizer::reopen(abort_callback & p_abort) { if (m_position_base - m_buffer.get_depth() == 0) { seek(0,p_abort); } else { m_position = m_position_base = 0; m_buffer.reset(); m_file->reopen(p_abort); } } bool seekabilizer::is_remote() { return m_file->is_remote(); } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/seekabilizer.h ================================================ class seekabilizer_backbuffer { public: void initialize(t_size p_size); void write(const void * p_buffer,t_size p_bytes); void read(t_size p_backlogdepth,void * p_buffer,t_size p_bytes) const; t_size get_depth() const; void reset(); t_size get_max_depth() const; private: pfc::array_t<t_uint8> m_buffer; t_size m_depth,m_cursor; }; class seekabilizer : public file_readonly { public: void initialize(service_ptr_t<file> p_base,t_size p_buffer_size,abort_callback & p_abort); static void g_seekabilize(service_ptr_t<file> & p_reader,t_size p_buffer_size,abort_callback & p_abort); t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort); t_filesize get_size(abort_callback & p_abort); t_filesize get_position(abort_callback & p_abort); void seek(t_filesize p_position,abort_callback & p_abort); bool can_seek(); bool get_content_type(pfc::string_base & p_out); bool is_in_memory(); void on_idle(abort_callback & p_abort); t_filetimestamp get_timestamp(abort_callback & p_abort); void reopen(abort_callback & p_abort); bool is_remote(); private: service_ptr_t<file> m_file; seekabilizer_backbuffer m_buffer; t_filesize m_size,m_position,m_position_base; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/stream_buffer_helper.cpp ================================================ #include "stdafx.h" stream_reader_buffered::stream_reader_buffered(stream_reader * p_base,t_size p_buffer) : m_base(p_base) { m_buffer.set_size(p_buffer); m_buffer_ptr = 0; m_buffer_max = 0; } t_size stream_reader_buffered::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) { p_abort.check_e(); char * output = (char*) p_buffer; t_size output_ptr = 0; while(output_ptr < p_bytes) { { t_size delta = pfc::min_t(p_bytes - output_ptr, m_buffer_max - m_buffer_ptr); if (delta > 0) { memcpy(output + output_ptr, m_buffer.get_ptr() + m_buffer_ptr, delta); output_ptr += delta; m_buffer_ptr += delta; } } if (m_buffer_ptr == m_buffer_max) { t_size bytes_read; bytes_read = m_base->read(m_buffer.get_ptr(), m_buffer.get_size(), p_abort); m_buffer_ptr = 0; m_buffer_max = bytes_read; if (m_buffer_max == 0) break; } } return output_ptr; } stream_writer_buffered::stream_writer_buffered(stream_writer * p_base,t_size p_buffer) : m_base(p_base) { m_buffer.set_size(p_buffer); m_buffer_ptr = 0; } void stream_writer_buffered::write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) { p_abort.check_e(); const char * source = (const char*)p_buffer; t_size source_remaining = p_bytes; const t_size buffer_size = m_buffer.get_size(); if (source_remaining >= buffer_size) { flush(p_abort); m_base->write_object(source,source_remaining,p_abort); return; } if (m_buffer_ptr + source_remaining >= buffer_size) { t_size delta = buffer_size - m_buffer_ptr; memcpy(m_buffer.get_ptr() + m_buffer_ptr, source,delta); source += delta; source_remaining -= delta; m_buffer_ptr += delta; flush(p_abort); } memcpy(m_buffer.get_ptr() + m_buffer_ptr, source,source_remaining); m_buffer_ptr += source_remaining; } void stream_writer_buffered::flush(abort_callback & p_abort) { if (m_buffer_ptr > 0) { m_base->write_object(m_buffer.get_ptr(),m_buffer_ptr,p_abort); m_buffer_ptr = 0; } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/stream_buffer_helper.h ================================================ class stream_reader_buffered : public stream_reader { public: stream_reader_buffered(stream_reader * p_base,t_size p_buffer); t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort); private: stream_reader * m_base; pfc::array_t<char> m_buffer; t_size m_buffer_ptr, m_buffer_max; }; class stream_writer_buffered : public stream_writer { public: stream_writer_buffered(stream_writer * p_base,t_size p_buffer); void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort); void flush(abort_callback & p_abort); private: stream_writer * m_base; pfc::array_t<char> m_buffer; t_size m_buffer_ptr; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/string_filter.h ================================================ class string_filter_noncasesensitive { public: string_filter_noncasesensitive(const char * p_string,t_size p_string_len = infinite) { uStringLower(m_pattern,p_string,p_string_len); } bool test(const char * p_string,t_size p_string_len = infinite) const { ::uStringLower(m_lowercasebuffer,p_string,p_string_len); t_size walk = 0; while(m_pattern[walk] != 0) { while(m_pattern[walk] == ' ') walk++; t_size delta = 0; while(m_pattern[walk+delta] != 0 && m_pattern[walk+delta] != ' ') delta++; if (delta > 0) { if (pfc::string_find_first_ex(m_lowercasebuffer,infinite,m_pattern+walk,delta) == infinite) return false; } walk += delta; } return true; } private: mutable pfc::string8_fastalloc m_lowercasebuffer; pfc::string8 m_pattern; }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/text_file_loader.cpp ================================================ #include "StdAfx.h" static const unsigned char utf8_header[3] = {0xEF,0xBB,0xBF}; namespace text_file_loader { void write(const service_ptr_t<file> & p_file,abort_callback & p_abort,const char * p_string,bool is_utf8) { p_file->seek(0,p_abort); p_file->set_eof(p_abort); if (is_utf8) { p_file->write_object(utf8_header,sizeof(utf8_header),p_abort); p_file->write_object(p_string,strlen(p_string),p_abort); } else { pfc::stringcvt::string_ansi_from_utf8 bah(p_string); p_file->write_object(bah,bah.length(),p_abort); } } void read(const service_ptr_t<file> & p_file,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8) { p_out.reset(); if (p_file->can_seek()) { p_file->seek(0,p_abort); } pfc::array_t<char> mem; t_filesize size64; size64 = p_file->get_size(p_abort); if (size64 == filesize_invalid)//typically HTTP { pfc::string8 ansitemp; is_utf8 = false; enum {delta = 1024*64, max = 1024*512}; char temp[3]; t_size done; done = p_file->read(temp,3,p_abort); if (done != 3) { if (done > 0) p_out = pfc::stringcvt::string_utf8_from_ansi(temp,done); return; } if (!memcmp(utf8_header,temp,3)) is_utf8 = true; else ansitemp.add_string(temp,3); mem.set_size(delta); for(;;) { done = p_file->read(mem.get_ptr(),delta,p_abort); if (done > 0) { if (is_utf8) p_out.add_string(mem.get_ptr(),done); else ansitemp.add_string(mem.get_ptr(),done); } if (done < delta) break; } if (!is_utf8) { p_out = pfc::stringcvt::string_utf8_from_ansi(ansitemp); } return; } else { if (size64>1024*1024*128) throw exception_io_data();//hard limit t_size size = pfc::downcast_guarded<t_size>(size64); mem.set_size(size+1); char * asdf = mem.get_ptr(); p_file->read_object(asdf,size,p_abort); asdf[size]=0; if (size>3 && !memcmp(utf8_header,asdf,3)) {is_utf8 = true; p_out.add_string(asdf+3); } else { is_utf8 = false; p_out = pfc::stringcvt::string_utf8_from_ansi(asdf); } return; } } void write(const char * p_path,abort_callback & p_abort,const char * p_string,bool is_utf8) { service_ptr_t<file> f; filesystem::g_open_write_new(f,p_path,p_abort); write(f,p_abort,p_string,is_utf8); } void read(const char * p_path,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8) { service_ptr_t<file> f; filesystem::g_open_read(f,p_path,p_abort); read(f,p_abort,p_out,is_utf8); } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/text_file_loader.h ================================================ namespace text_file_loader { void write(const service_ptr_t<file> & p_file,abort_callback & p_abort,const char * p_string,bool is_utf8); void read(const service_ptr_t<file> & p_file,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8); void write(const char * p_path,abort_callback & p_abort,const char * p_string,bool is_utf8); void read(const char * p_path,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8); }; ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/wildcard.cpp ================================================ #include "stdafx.h" static bool test_recur(const char * fn,const char * rm,bool b_sep) { for(;;) { if ((b_sep && *rm==';') || *rm==0) return *fn==0; else if (*rm=='*') { rm++; do { if (test_recur(fn,rm,b_sep)) return true; } while(pfc::utf8_advance(fn)); return false; } else if (*fn==0) return false; else if (*rm!='?' && char_lower(pfc::utf8_get_char(fn))!=char_lower(pfc::utf8_get_char(rm))) return false; fn = pfc::utf8_char_next(fn); rm = pfc::utf8_char_next(rm); } } bool wildcard_helper::test_path(const char * path,const char * pattern,bool b_sep) {return test(path + pfc::scan_filename(path),pattern,b_sep);} bool wildcard_helper::test(const char * fn,const char * pattern,bool b_sep) { if (!b_sep) return test_recur(fn,pattern,false); const char * rm=pattern; while(*rm) { if (test_recur(fn,rm,true)) return true; while(*rm && *rm!=';') rm++; if (*rm==';') { while(*rm==';') rm++; while(*rm==' ') rm++; } }; return false; } bool wildcard_helper::has_wildcards(const char * str) {return strchr(str,'*') || strchr(str,'?');} ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/wildcard.h ================================================ #ifndef __FOOBAR2000_HELPER_WILDCARD_H__ #define __FOOBAR2000_HELPER_WILDCARD_H__ namespace wildcard_helper { bool test_path(const char * path,const char * pattern,bool b_separate_by_semicolon = false);//will extract filename from path first bool test(const char * str,const char * pattern,bool b_separate_by_semicolon = false);//tests if str matches pattern bool has_wildcards(const char * str); }; #endif //__FOOBAR2000_HELPER_WILDCARD_H__ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/win32_dialog.cpp ================================================ #include "stdafx.h" namespace dialog_helper { INT_PTR CALLBACK dialog::DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) { dialog * p_this; BOOL rv; if (msg==WM_INITDIALOG) { p_this = reinterpret_cast<dialog*>(lp); p_this->wnd = wnd; SetWindowLongPtr(wnd,DWLP_USER,lp); if (p_this->m_is_modal) p_this->m_modal_scope.initialize(wnd); } else p_this = reinterpret_cast<dialog*>(GetWindowLongPtr(wnd,DWLP_USER)); rv = p_this ? p_this->on_message(msg,wp,lp) : FALSE; if (msg==WM_DESTROY && p_this) { SetWindowLongPtr(wnd,DWLP_USER,0); // p_this->wnd = 0; } return rv; } int dialog::run_modal(unsigned id,HWND parent) { assert(wnd == 0); if (wnd != 0) return -1; m_is_modal = true; return uDialogBox(id,parent,DlgProc,reinterpret_cast<LPARAM>(this)); } HWND dialog::run_modeless(unsigned id,HWND parent) { assert(wnd == 0); if (wnd != 0) return 0; m_is_modal = false; return uCreateDialog(id,parent,DlgProc,reinterpret_cast<LPARAM>(this)); } void dialog::end_dialog(int code) { assert(m_is_modal); if (m_is_modal) uEndDialog(wnd,code); } int dialog_modal::run(unsigned p_id,HWND p_parent,HINSTANCE p_instance) { int status; // note: uDialogBox() has its own modal scope, we don't want that to trigger // if this is ever changed, move deinit to WM_DESTROY handler in DlgProc status = (int)DialogBoxParam(p_instance,MAKEINTRESOURCE(p_id),p_parent,DlgProc,reinterpret_cast<LPARAM>(this)); m_modal_scope.deinitialize(); return status; } void dialog_modal::end_dialog(int p_code) { EndDialog(m_wnd,p_code); } INT_PTR CALLBACK dialog_modal::DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) { dialog_modal * _this; if (msg==WM_INITDIALOG) { _this = reinterpret_cast<dialog_modal*>(lp); _this->m_wnd = wnd; SetWindowLongPtr(wnd,DWLP_USER,lp); _this->m_modal_scope.initialize(wnd); } else _this = reinterpret_cast<dialog_modal*>(GetWindowLongPtr(wnd,DWLP_USER)); assert(_this == 0 || _this->m_wnd == wnd); return _this ? _this->on_message(msg,wp,lp) : FALSE; } bool dialog_modeless::create(unsigned p_id,HWND p_parent,HINSTANCE p_instance) { assert(!m_is_in_create); if (m_is_in_create) return false; pfc::vartoggle_t<bool> scope(m_is_in_create,true); if (CreateDialogParam(p_instance,MAKEINTRESOURCE(p_id),p_parent,DlgProc,reinterpret_cast<LPARAM>(this)) == 0) return false; return m_wnd != 0; } dialog_modeless::~dialog_modeless() { assert(!m_is_in_create); switch(m_destructor_status) { case destructor_none: m_destructor_status = destructor_normal; if (m_wnd != 0) { DestroyWindow(m_wnd); m_wnd = 0; } break; case destructor_fromwindow: if (m_wnd != 0) SetWindowLongPtr(m_wnd,DWLP_USER,0); break; default: //should never trigger pfc::crash(); break; } } void dialog_modeless::on_window_destruction() { if (m_is_in_create) { m_wnd = 0; } else switch(m_destructor_status) { case destructor_none: m_destructor_status = destructor_fromwindow; delete this; break; case destructor_fromwindow: pfc::crash(); break; default: break; } } BOOL dialog_modeless::on_message_wrap(UINT msg,WPARAM wp,LPARAM lp) { if (m_destructor_status == destructor_none) return on_message(msg,wp,lp); else return FALSE; } INT_PTR CALLBACK dialog_modeless::DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) { dialog_modeless * thisptr; BOOL rv; if (msg == WM_INITDIALOG) { thisptr = reinterpret_cast<dialog_modeless*>(lp); thisptr->m_wnd = wnd; SetWindowLongPtr(wnd,DWLP_USER,lp); modeless_dialog_manager::g_add(wnd); } else thisptr = reinterpret_cast<dialog_modeless*>(GetWindowLongPtr(wnd,DWLP_USER)); rv = thisptr ? thisptr->on_message_wrap(msg,wp,lp) : FALSE; if (msg == WM_DESTROY) modeless_dialog_manager::g_remove(wnd); if (msg == WM_DESTROY && thisptr != 0) thisptr->on_window_destruction(); return rv; } dialog_modeless_v2::dialog_modeless_v2(unsigned p_id,HWND p_parent,HINSTANCE p_instance,bool p_stealfocus) : m_wnd(0), m_status(status_construction), m_stealfocus(p_stealfocus) { SetLastError(NO_ERROR); HWND result = CreateDialogParam(p_instance,MAKEINTRESOURCE(p_id),p_parent,DlgProc,reinterpret_cast<LPARAM>(this)); if (result == 0 || m_wnd == 0) throw exception_win32(GetLastError()); m_status = status_lifetime; } dialog_modeless_v2::~dialog_modeless_v2() { bool is_window_being_destroyed = (m_status == status_destruction_requested); m_status = status_destruction; if (m_wnd != 0) { if (is_window_being_destroyed) detach_window(); else DestroyWindow(m_wnd); } } INT_PTR CALLBACK dialog_modeless_v2::DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) { dialog_modeless_v2 * thisptr; BOOL rv = FALSE; if (msg == WM_INITDIALOG) { thisptr = reinterpret_cast<dialog_modeless_v2*>(lp); assert(thisptr->m_status == status_construction); thisptr->m_wnd = wnd; SetWindowLongPtr(wnd,DWLP_USER,lp); modeless_dialog_manager::g_add(wnd); } else thisptr = reinterpret_cast<dialog_modeless_v2*>(GetWindowLongPtr(wnd,DWLP_USER)); if (thisptr != NULL) rv = thisptr->on_message_internal(msg,wp,lp); if (msg == WM_DESTROY) { modeless_dialog_manager::g_remove(wnd); } return rv; } void dialog_modeless_v2::detach_window() { if (m_wnd != 0) { SetWindowLongPtr(m_wnd,DWLP_USER,0); m_wnd = 0; } } BOOL dialog_modeless_v2::on_message_internal(UINT msg,WPARAM wp,LPARAM lp) { if (m_status == status_lifetime || m_status == status_destruction_requested) { if (msg == WM_DESTROY) { assert(m_status == status_lifetime); m_status = status_destruction_requested; delete this; return TRUE; } else return on_message(msg,wp,lp); } else if (m_status == status_construction) { if (msg == WM_INITDIALOG) return m_stealfocus ? TRUE : FALSE; else return FALSE; } else return FALSE; } } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/win32_dialog.h ================================================ #ifndef _FOOBAR2000_HELPERS_WIN32_DIALOG_H_ #define _FOOBAR2000_HELPERS_WIN32_DIALOG_H_ namespace dialog_helper { class dialog { protected: dialog() : wnd(0), m_is_modal(false) {} ~dialog() { } virtual BOOL on_message(UINT msg,WPARAM wp,LPARAM lp)=0; void end_dialog(int code); public: inline HWND get_wnd() {return wnd;} int run_modal(unsigned id,HWND parent); HWND run_modeless(unsigned id,HWND parent); private: HWND wnd; static INT_PTR CALLBACK DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); bool m_is_modal; modal_dialog_scope m_modal_scope; }; //! This class is meant to be instantiated on-stack, as a local variable. Using new/delete operators instead or even making this a member of another object works, but does not make much sense because of the way this works (single run() call). class dialog_modal { public: int run(unsigned p_id,HWND p_parent,HINSTANCE p_instance = core_api::get_my_instance()); protected: virtual BOOL on_message(UINT msg,WPARAM wp,LPARAM lp)=0; inline dialog_modal() : m_wnd(0) {} void end_dialog(int p_code); inline HWND get_wnd() const {return m_wnd;} private: static INT_PTR CALLBACK DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); HWND m_wnd; modal_dialog_scope m_modal_scope; }; //! This class is meant to be used with new/delete operators only. Destroying the window - outside create() / WM_INITDIALOG - will result in object calling delete this. If object is deleted directly using delete operator, WM_DESTROY handler may not be called so it should not be used (use destructor of derived class instead). //! Classes derived from dialog_modeless must not be instantiated in any other way than operator new(). /*! Typical usage : \n class mydialog : public dialog_helper::dialog_modeless {...}; (...) bool createmydialog() { mydialog * instance = new mydialog; if (instance == 0) return flase; if (!instance->create(...)) {delete instance; return false;} return true; } */ class dialog_modeless { public: //! Creates the dialog window. This will call on_message with WM_INITDIALOG. To abort creation, you can call DestroyWindow() on our window; it will not delete the object but make create() return false instead. You should not delete the object from inside WM_INITDIALOG handler or anything else possibly called from create(). //! @returns true on success, false on failure. bool create(unsigned p_id,HWND p_parent,HINSTANCE p_instance = core_api::get_my_instance()); protected: //! Standard windows message handler (DialogProc-style). Use get_wnd() to retrieve our dialog window handle. virtual BOOL on_message(UINT msg,WPARAM wp,LPARAM lp)=0; inline dialog_modeless() : m_wnd(0), m_destructor_status(destructor_none), m_is_in_create(false) {} inline HWND get_wnd() const {return m_wnd;} virtual ~dialog_modeless(); private: static INT_PTR CALLBACK DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); void on_window_destruction(); BOOL on_message_wrap(UINT msg,WPARAM wp,LPARAM lp); HWND m_wnd; enum {destructor_none,destructor_normal,destructor_fromwindow} m_destructor_status; bool m_is_in_create; }; #pragma deprecated(dialog_modeless) class dialog_modeless_v2 { protected: explicit dialog_modeless_v2(unsigned p_id,HWND p_parent,HINSTANCE p_instance = core_api::get_my_instance(),bool p_stealfocus = true); virtual ~dialog_modeless_v2(); HWND get_wnd() const {return m_wnd;} virtual BOOL on_message(UINT msg,WPARAM wp,LPARAM lp) {return FALSE;} static dialog_modeless_v2 * __unsafe__instance_from_window(HWND p_wnd) {return reinterpret_cast<dialog_modeless_v2*>(GetWindowLongPtr(p_wnd,DWLP_USER));} private: static INT_PTR CALLBACK DlgProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp); void detach_window(); BOOL on_message_internal(UINT msg,WPARAM wp,LPARAM lp); enum {status_construction, status_lifetime, status_destruction_requested, status_destruction} m_status; HWND m_wnd; const bool m_stealfocus; const dialog_modeless_v2 & operator=(const dialog_modeless_v2 &); dialog_modeless_v2(const dialog_modeless_v2 &); }; }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/win32_misc.cpp ================================================ #include "stdafx.h" void registerclass_scope_delayed::toggle_on(UINT p_style,WNDPROC p_wndproc,int p_clsextra,int p_wndextra,HICON p_icon,HCURSOR p_cursor,HBRUSH p_background,const TCHAR * p_class_name,const TCHAR * p_menu_name) { toggle_off(); WNDCLASS wc; memset(&wc,0,sizeof(wc)); wc.style = p_style; wc.lpfnWndProc = p_wndproc; wc.cbClsExtra = p_clsextra; wc.cbWndExtra = p_wndextra; wc.hInstance = core_api::get_my_instance(); wc.hIcon = p_icon; wc.hCursor = p_cursor; wc.hbrBackground = p_background; wc.lpszMenuName = p_menu_name; wc.lpszClassName = p_class_name; SetLastError(NO_ERROR); m_class = RegisterClass(&wc); if (m_class == 0) throw ::exception_win32(GetLastError()); } void registerclass_scope_delayed::toggle_off() { if (m_class != 0) { UnregisterClass((LPCTSTR)m_class,core_api::get_my_instance()); m_class = 0; } } namespace { class clipboard_scope { public: clipboard_scope() : m_open(false) {} ~clipboard_scope() {close();} bool open(HWND p_owner) { close(); if (OpenClipboard(p_owner) == TRUE) { m_open = true; return true; } else { return false; } } void close() { if (m_open) { m_open = false; CloseClipboard(); } } private: bool m_open; }; }; bool uGetClipboardString(pfc::string_base & p_out) { clipboard_scope scope; if (!scope.open(NULL)) return false; HANDLE data = GetClipboardData( #ifdef UNICODE CF_UNICODETEXT #else CF_TEXT #endif ); if (data == NULL) return false; CGlobalLock lock(data); p_out = pfc::stringcvt::string_utf8_from_os( (const TCHAR*) lock.GetPtr(), lock.GetSize() / sizeof(TCHAR) ); return true; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/win32_misc.h ================================================ class registerclass_scope_delayed { public: registerclass_scope_delayed() : m_class(0) {} bool is_registered() const {return m_class != 0;} void toggle_on(UINT p_style,WNDPROC p_wndproc,int p_clsextra,int p_wndextra,HICON p_icon,HCURSOR p_cursor,HBRUSH p_background,const TCHAR * p_classname,const TCHAR * p_menuname); void toggle_off(); ATOM get_class() const {return m_class;} ~registerclass_scope_delayed() {toggle_off();} private: registerclass_scope_delayed(const registerclass_scope_delayed &) {throw pfc::exception_not_implemented();} const registerclass_scope_delayed & operator=(const registerclass_scope_delayed &) {throw pfc::exception_not_implemented();} ATOM m_class; }; template<typename t_object> class syncd_storage { private: typedef syncd_storage<t_object> t_self; public: syncd_storage() {} template<typename t_source> syncd_storage(const t_source & p_source) : m_object(p_source) {} template<typename t_source> void set(t_source const & p_in) { insync(m_sync); m_object = p_in; } template<typename t_destination> void get(t_destination & p_out) const { insync(m_sync); p_out = m_object; } t_object get() const { insync(m_sync); return m_object; } template<typename t_source> const t_self & operator=(t_source const & p_source) {set(p_source); return *this;} private: mutable critical_section m_sync; t_object m_object; }; template<typename t_object> class syncd_storage_flagged { private: typedef syncd_storage_flagged<t_object> t_self; public: syncd_storage_flagged() : m_changed_flag(false) {} template<typename t_source> syncd_storage_flagged(const t_source & p_source) : m_changed_flag(false), m_object(p_source) {} void set_changed(bool p_flag = true) { insync(m_sync); m_changed_flag = p_flag; } template<typename t_source> void set(t_source const & p_in) { insync(m_sync); m_object = p_in; m_changed_flag = true; } bool has_changed() const { insync(m_sync); return m_changed_flag; } t_object peek() const {insync(m_sync); return m_object;} template<typename t_destination> bool get_if_changed(t_destination & p_out) { insync(m_sync); if (m_changed_flag) { p_out = m_object; m_changed_flag = false; return true; } else { return false; } } t_object get() { insync(m_sync); m_changed_flag = false; return m_object; } template<typename t_destination> void get(t_destination & p_out) { insync(m_sync); p_out = m_object; m_changed_flag = false; } template<typename t_source> const t_self & operator=(t_source const & p_source) {set(p_source); return *this;} private: bool m_changed_flag; mutable critical_section m_sync; t_object m_object; }; class CGlobalLock { public: CGlobalLock(HGLOBAL p_handle) : m_handle(p_handle), m_ptr(GlobalLock(p_handle)) {} ~CGlobalLock() { if (m_ptr != NULL) GlobalUnlock(m_handle); } void * GetPtr() const {return m_ptr;} t_size GetSize() const {return GlobalSize(m_handle);} private: void * m_ptr; HGLOBAL m_handle; }; bool uGetClipboardString(pfc::string_base & p_out); #ifdef __ATLWIN_H__ class CMenuSelectionReceiver : public CWindowImpl<CMenuSelectionReceiver> { public: CMenuSelectionReceiver(HWND p_parent) { SetLastError(NO_ERROR); if (Create(p_parent) == NULL) throw exception_win32(GetLastError()); } ~CMenuSelectionReceiver() { DestroyWindow(); } typedef CWindowImpl<CMenuSelectionReceiver> _baseClass; DECLARE_WND_CLASS_EX(TEXT("{DF0087DB-E765-4283-BBAB-6AB2E8AB64A1}"),0,0); BEGIN_MSG_MAP(CMenuSelectionReceiver) MESSAGE_HANDLER(WM_MENUSELECT,OnMenuSelect) END_MSG_MAP() protected: virtual bool QueryHint(unsigned p_id,pfc::string_base & p_out) { return false; } private: LRESULT OnMenuSelect(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) { if (p_lp != 0) { if (HIWORD(p_wp) & MF_POPUP) { m_status.release(); } else { pfc::string8 msg; if (!QueryHint(LOWORD(p_wp),msg)) { m_status.release(); } else { if (m_status.is_empty()) { if (!static_api_ptr_t<ui_control>()->override_status_text_create(m_status)) m_status.release(); } if (m_status.is_valid()) { m_status->override_text(msg); } } } } else { m_status.release(); } return 0; } service_ptr_t<ui_status_text_override> m_status; PFC_CLASS_NOT_COPYABLE(CMenuSelectionReceiver,CMenuSelectionReceiver); }; #endif //#ifdef __ATLWIN_H__ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/window_placement_helper.cpp ================================================ #include "stdafx.h" static bool g_is_enabled() { return standard_config_objects::query_remember_window_positions(); } static BOOL CALLBACK __MonitorEnumProc( HMONITOR hMonitor, // handle to display monitor HDC hdcMonitor, // handle to monitor DC LPRECT lprcMonitor, // monitor intersection rectangle LPARAM dwData // data ) { RECT * clip = (RECT*)dwData; RECT newclip; UnionRect(&newclip,clip,lprcMonitor); *clip = newclip; return TRUE; } static bool test_rect(const RECT * rc) { RECT clip = {}; if (EnumDisplayMonitors(NULL,NULL,__MonitorEnumProc,(LPARAM)&clip)) { const LONG sanitycheck = 4; const LONG cwidth = clip.right - clip.left; const LONG cheight = clip.bottom - clip.top; const LONG width = rc->right - rc->left; const LONG height = rc->bottom - rc->top; if (width > cwidth * sanitycheck || height > cheight * sanitycheck) return false; } return MonitorFromRect(rc,MONITOR_DEFAULTTONULL) != NULL; } bool cfg_window_placement::read_from_window(HWND window) { WINDOWPLACEMENT wp; memset(&wp,0,sizeof(wp)); if (g_is_enabled()) { wp.length = sizeof(wp); if (!GetWindowPlacement(window,&wp)) memset(&wp,0,sizeof(wp)); /*else { if (!IsWindowVisible(window)) wp.showCmd = SW_HIDE; }*/ } m_data = wp; return m_data.length == sizeof(m_data); } bool cfg_window_placement::on_window_creation(HWND window) { bool ret = false; PFC_ASSERT(!m_windows.have_item(window)); m_windows.add_item(window); if (g_is_enabled()) { if (m_data.length==sizeof(m_data) && test_rect(&m_data.rcNormalPosition)) { if (SetWindowPlacement(window,&m_data)) { ret = true; } } } return ret; } void cfg_window_placement::on_window_destruction(HWND window) { if (m_windows.have_item(window)) { read_from_window(window); m_windows.remove_item(window); } } void cfg_window_placement::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { if (g_is_enabled()) { { t_size n, m = m_windows.get_count(); for(n=0;n<m;n++) { HWND window = m_windows[n]; PFC_ASSERT(IsWindow(window)); if (IsWindow(window) && read_from_window(window)) break; } } if (m_data.length == sizeof(m_data)) { p_stream->write_object(&m_data,sizeof(m_data),p_abort); } } } void cfg_window_placement::set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { WINDOWPLACEMENT temp; try { p_stream->read_object(&temp,sizeof(temp),p_abort); } catch(exception_io_data const &) {return;} if (temp.length == sizeof(temp)) m_data = temp; } cfg_window_placement::cfg_window_placement(const GUID & p_guid) : cfg_var(p_guid) { memset(&m_data,0,sizeof(m_data)); } cfg_window_size::cfg_window_size(const GUID & p_guid) : cfg_var(p_guid), m_width(infinite32), m_height(infinite32) {} static BOOL SetWindowSize(HWND p_wnd,unsigned p_x,unsigned p_y) { if (p_x != infinite32 && p_y != infinite32) return SetWindowPos(p_wnd,0,0,0,p_x,p_y,SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER); else return FALSE; } bool cfg_window_size::on_window_creation(HWND p_wnd) { bool ret = false; PFC_ASSERT(!m_windows.have_item(p_wnd)); m_windows.add_item(p_wnd); if (g_is_enabled()) { if (SetWindowSize(p_wnd,m_width,m_height)) ret = true; } return ret; } void cfg_window_size::on_window_destruction(HWND p_wnd) { if (m_windows.have_item(p_wnd)) { read_from_window(p_wnd); m_windows.remove_item(p_wnd); } } bool cfg_window_size::read_from_window(HWND p_wnd) { if (g_is_enabled()) { RECT r; if (GetWindowRect(p_wnd,&r)) { m_width = r.right - r.left; m_height = r.bottom - r.top; return true; } else { m_width = m_height = infinite32; return false; } } else { m_width = m_height = infinite32; return false; } } void cfg_window_size::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) { if (g_is_enabled()) { { t_size n, m = m_windows.get_count(); for(n=0;n<m;n++) { HWND window = m_windows[n]; PFC_ASSERT(IsWindow(window)); if (IsWindow(window) && read_from_window(window)) break; } } if (m_width != infinite32 && m_height != infinite32) { p_stream->write_lendian_t(m_width,p_abort); p_stream->write_lendian_t(m_height,p_abort); } } } void cfg_window_size::set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) { t_uint32 width,height; try { p_stream->read_lendian_t(width,p_abort); p_stream->read_lendian_t(height,p_abort); } catch(exception_io_data const &) {return;} m_width = width; m_height = height; } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/helpers/window_placement_helper.h ================================================ #ifndef _WINDOW_PLACEMENT_HELPER_H_ #define _WINDOW_PLACEMENT_HELPER_H_ class cfg_window_placement : public cfg_var { public: bool on_window_creation(HWND window);//returns true if window position has been changed, false if not void on_window_destruction(HWND window); bool read_from_window(HWND window); void get_data_raw(stream_writer * p_stream,abort_callback & p_abort); void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort); cfg_window_placement(const GUID & p_guid); private: pfc::list_hybrid_t<HWND,2> m_windows; WINDOWPLACEMENT m_data; }; class cfg_window_size : public cfg_var { public: bool on_window_creation(HWND window);//returns true if window position has been changed, false if not void on_window_destruction(HWND window); bool read_from_window(HWND window); void get_data_raw(stream_writer * p_stream,abort_callback & p_abort); void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort); cfg_window_size(const GUID & p_guid); private: pfc::list_hybrid_t<HWND,2> m_windows; t_uint32 m_width,m_height; }; #endif //_WINDOW_PLACEMENT_HELPER_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/shared/audio_math.h ================================================ #include <math.h> #ifdef _M_X64 #include <xmmintrin.h> #include <emmintrin.h> #endif #define audio_sample_size 32 #if audio_sample_size == 32 typedef float audio_sample; #define audio_sample_asm dword #elif audio_sample_size == 64 typedef double audio_sample; #define audio_sample_asm qword #else #error wrong audio_sample_size #endif #define audio_sample_bytes (audio_sample_size/8) namespace audio_math { //! p_source/p_output can point to same buffer void SHARED_EXPORT scale(const audio_sample * p_source,t_size p_count,audio_sample * p_output,audio_sample p_scale); void SHARED_EXPORT convert_to_int16(const audio_sample * p_source,t_size p_count,t_int16 * p_output,audio_sample p_scale); void SHARED_EXPORT convert_to_int32(const audio_sample * p_source,t_size p_count,t_int32 * p_output,audio_sample p_scale); audio_sample SHARED_EXPORT convert_to_int16_calculate_peak(const audio_sample * p_source,t_size p_count,t_int16 * p_output,audio_sample p_scale); void SHARED_EXPORT convert_from_int16(const t_int16 * p_source,t_size p_count,audio_sample * p_output,audio_sample p_scale); void SHARED_EXPORT convert_from_int32(const t_int32 * p_source,t_size p_count,audio_sample * p_output,audio_sample p_scale); audio_sample SHARED_EXPORT convert_to_int32_calculate_peak(const audio_sample * p_source,t_size p_count,t_int32 * p_output,audio_sample p_scale); audio_sample SHARED_EXPORT calculate_peak(const audio_sample * p_source,t_size p_count); void SHARED_EXPORT remove_denormals(audio_sample * p_buffer,t_size p_count); void SHARED_EXPORT add_offset(audio_sample * p_buffer,audio_sample p_delta,t_size p_count); inline t_uint64 time_to_samples(double p_time,t_uint32 p_sample_rate) { return (t_uint64)floor((double)p_sample_rate * p_time + 0.5); } inline double samples_to_time(t_uint64 p_samples,t_uint32 p_sample_rate) { PFC_ASSERT(p_sample_rate > 0); return (double) p_samples / (double) p_sample_rate; } #ifdef _M_IX86 inline static t_int64 rint64(audio_sample val) { t_int64 rv; _asm { fld val; fistp rv; } return rv; } inline static t_int32 rint32(audio_sample val) { t_int32 rv; _asm { fld val; fistp rv; } return rv; } #elif defined(_M_X64) inline static t_int64 rint64(audio_sample val) {return (t_int64)floor(val+0.5);} static inline t_int32 rint32(float p_val) { return (t_int32)_mm_cvtss_si32(_mm_load_ss(&p_val)); } #else inline static t_int64 rint64(audio_sample val) {return (t_int64)floor(val+0.5);} inline static t_int32 rint32(audio_sample val) {return (t_int32)floor(val+0.5);} #endif inline audio_sample gain_to_scale(double p_gain) {return (audio_sample) pow(10.0,p_gain / 20.0);} } ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/shared/shared.h ================================================ #ifndef _SHARED_DLL__SHARED_H_ #define _SHARED_DLL__SHARED_H_ #include "../../pfc/pfc.h" #ifndef WIN32 #error N/A #endif #ifndef STRICT #define STRICT #endif #include <windows.h> #include <ddeml.h> #include <commctrl.h> #ifndef NOTHROW #ifdef _MSC_VER #define NOTHROW __declspec(nothrow) #else #define NOTHROW #endif #endif #define SHARED_API /*NOTHROW*/ __stdcall #ifndef SHARED_EXPORTS #define SHARED_EXPORT __declspec(dllimport) SHARED_API #else #define SHARED_EXPORT __declspec(dllexport) SHARED_API #endif extern "C" { //SHARED_EXPORT BOOL IsUnicode(); #ifdef UNICODE #define IsUnicode() 1 #else #define IsUnicode() 0 #endif LRESULT SHARED_EXPORT uSendMessageText(HWND wnd,UINT msg,WPARAM wp,const char * text); LRESULT SHARED_EXPORT uSendDlgItemMessageText(HWND wnd,UINT id,UINT msg,WPARAM wp,const char * text); BOOL SHARED_EXPORT uGetWindowText(HWND wnd,pfc::string_base & out); BOOL SHARED_EXPORT uSetWindowText(HWND wnd,const char * p_text); BOOL SHARED_EXPORT uSetWindowTextEx(HWND wnd,const char * p_text,unsigned p_text_length); BOOL SHARED_EXPORT uGetDlgItemText(HWND wnd,UINT id,pfc::string_base & out); BOOL SHARED_EXPORT uSetDlgItemText(HWND wnd,UINT id,const char * p_text); BOOL SHARED_EXPORT uSetDlgItemTextEx(HWND wnd,UINT id,const char * p_text,unsigned p_text_length); BOOL SHARED_EXPORT uBrowseForFolder(HWND parent,const char * title,pfc::string_base & out); BOOL SHARED_EXPORT uBrowseForFolderWithFile(HWND parent,const char * title,pfc::string_base & out,const char * p_file_to_find); int SHARED_EXPORT uMessageBox(HWND wnd,const char * text,const char * caption,UINT type); void SHARED_EXPORT uOutputDebugString(const char * msg); BOOL SHARED_EXPORT uAppendMenu(HMENU menu,UINT flags,UINT_PTR id,const char * content); BOOL SHARED_EXPORT uInsertMenu(HMENU menu,UINT position,UINT flags,UINT_PTR id,const char * content); int SHARED_EXPORT uStringCompare(const char * elem1, const char * elem2); int SHARED_EXPORT uCharCompare(t_uint32 p_char1,t_uint32 p_char2); int SHARED_EXPORT uStringCompare_ConvertNumbers(const char * elem1,const char * elem2); HINSTANCE SHARED_EXPORT uLoadLibrary(const char * name); HANDLE SHARED_EXPORT uCreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState, const char * lpName); DWORD SHARED_EXPORT uGetModuleFileName(HMODULE hMod,pfc::string_base & out); BOOL SHARED_EXPORT uSetClipboardString(const char * ptr); BOOL SHARED_EXPORT uIsDialogMessage(HWND dlg,LPMSG msg); BOOL SHARED_EXPORT uGetMessage(LPMSG msg,HWND wnd,UINT min,UINT max); BOOL SHARED_EXPORT uGetClassName(HWND wnd,pfc::string_base & out); t_size SHARED_EXPORT uCharLength(const char * src); BOOL SHARED_EXPORT uDragQueryFile(HDROP hDrop,UINT idx,pfc::string_base & out); UINT SHARED_EXPORT uDragQueryFileCount(HDROP hDrop); BOOL SHARED_EXPORT uGetTextExtentPoint32(HDC dc,const char * text,UINT cb,LPSIZE size);//note, cb is number of bytes, not actual unicode characters in the string (read: plain strlen() will do) BOOL SHARED_EXPORT uExtTextOut(HDC dc,int x,int y,UINT flags,const RECT * rect,const char * text,UINT cb,const int * lpdx); BOOL SHARED_EXPORT uTextOutColors(HDC dc,const char * src,UINT len,int x,int y,const RECT * clip,BOOL is_selected,DWORD default_color); BOOL SHARED_EXPORT uTextOutColorsTabbed(HDC dc,const char * src,UINT src_len,const RECT * item,int border,const RECT * clip,BOOL selected,DWORD default_color,BOOL use_columns); UINT SHARED_EXPORT uGetTextHeight(HDC dc); UINT SHARED_EXPORT uGetFontHeight(HFONT font); BOOL SHARED_EXPORT uChooseColor(DWORD * p_color,HWND parent,DWORD * p_custom_colors); HCURSOR SHARED_EXPORT uLoadCursor(HINSTANCE hIns,const char * name); HICON SHARED_EXPORT uLoadIcon(HINSTANCE hIns,const char * name); HMENU SHARED_EXPORT uLoadMenu(HINSTANCE hIns,const char * name); BOOL SHARED_EXPORT uGetEnvironmentVariable(const char * name,pfc::string_base & out); HMODULE SHARED_EXPORT uGetModuleHandle(const char * name); UINT SHARED_EXPORT uRegisterWindowMessage(const char * name); BOOL SHARED_EXPORT uMoveFile(const char * src,const char * dst); BOOL SHARED_EXPORT uDeleteFile(const char * fn); DWORD SHARED_EXPORT uGetFileAttributes(const char * fn); BOOL SHARED_EXPORT uFileExists(const char * fn); BOOL SHARED_EXPORT uRemoveDirectory(const char * fn); HANDLE SHARED_EXPORT uCreateFile(const char * p_path,DWORD p_access,DWORD p_sharemode,LPSECURITY_ATTRIBUTES p_security_attributes,DWORD p_createmode,DWORD p_flags,HANDLE p_template); HANDLE SHARED_EXPORT uCreateFileMapping(HANDLE hFile,LPSECURITY_ATTRIBUTES lpFileMappingAttributes,DWORD flProtect,DWORD dwMaximumSizeHigh,DWORD dwMaximumSizeLow,const char * lpName); BOOL SHARED_EXPORT uCreateDirectory(const char * fn,LPSECURITY_ATTRIBUTES blah); HANDLE SHARED_EXPORT uCreateMutex(LPSECURITY_ATTRIBUTES blah,BOOL bInitialOwner,const char * name); BOOL SHARED_EXPORT uGetLongPathName(const char * name,pfc::string_base & out);//may just fail to work on old windows versions, present on win98/win2k+ BOOL SHARED_EXPORT uGetFullPathName(const char * name,pfc::string_base & out); BOOL SHARED_EXPORT uSearchPath(const char * path, const char * filename, const char * extension, pfc::string_base & p_out); BOOL SHARED_EXPORT uFixPathCaps(const char * path,pfc::string_base & p_out); void SHARED_EXPORT uGetCommandLine(pfc::string_base & out); BOOL SHARED_EXPORT uGetTempPath(pfc::string_base & out); BOOL SHARED_EXPORT uGetTempFileName(const char * path_name,const char * prefix,UINT unique,pfc::string_base & out); BOOL SHARED_EXPORT uGetOpenFileName(HWND parent,const char * p_ext_mask,unsigned def_ext_mask,const char * p_def_ext,const char * p_title,const char * p_directory,pfc::string_base & p_filename,BOOL b_save); //note: uGetOpenFileName extension mask uses | as separator, not null HANDLE SHARED_EXPORT uLoadImage(HINSTANCE hIns,const char * name,UINT type,int x,int y,UINT flags); UINT SHARED_EXPORT uRegisterClipboardFormat(const char * name); BOOL SHARED_EXPORT uGetClipboardFormatName(UINT format,pfc::string_base & out); BOOL SHARED_EXPORT uFormatSystemErrorMessage(pfc::string_base & p_out,DWORD p_code); HANDLE SHARED_EXPORT uSortStringCreate(const char * src); int SHARED_EXPORT uSortStringCompare(HANDLE string1,HANDLE string2); int SHARED_EXPORT uSortStringCompareEx(HANDLE string1,HANDLE string2,DWORD flags);//flags - see win32 CompareString int SHARED_EXPORT uSortPathCompare(HANDLE string1,HANDLE string2); void SHARED_EXPORT uSortStringFree(HANDLE string); int SHARED_EXPORT uCompareString(DWORD flags,const char * str1,unsigned len1,const char * str2,unsigned len2); class NOVTABLE uGetOpenFileNameMultiResult : public pfc::list_base_const_t<const char*> { public: inline t_size GetCount() {return get_count();} inline const char * GetFileName(t_size index) {return get_item(index);} virtual ~uGetOpenFileNameMultiResult() {} }; typedef uGetOpenFileNameMultiResult * puGetOpenFileNameMultiResult; puGetOpenFileNameMultiResult SHARED_EXPORT uGetOpenFileNameMulti(HWND parent,const char * p_ext_mask,unsigned def_ext_mask,const char * p_def_ext,const char * p_title,const char * p_directory); class NOVTABLE uFindFile { protected: uFindFile() {} public: virtual BOOL FindNext()=0; virtual const char * GetFileName()=0; virtual t_uint64 GetFileSize()=0; virtual DWORD GetAttributes()=0; virtual FILETIME GetCreationTime()=0; virtual FILETIME GetLastAccessTime()=0; virtual FILETIME GetLastWriteTime()=0; virtual ~uFindFile() {}; inline bool IsDirectory() {return (GetAttributes() & FILE_ATTRIBUTE_DIRECTORY) ? true : false;} }; typedef uFindFile * puFindFile; puFindFile SHARED_EXPORT uFindFirstFile(const char * path); HINSTANCE SHARED_EXPORT uShellExecute(HWND wnd,const char * oper,const char * file,const char * params,const char * dir,int cmd); HWND SHARED_EXPORT uCreateStatusWindow(LONG style,const char * text,HWND parent,UINT id); BOOL SHARED_EXPORT uShellNotifyIcon(DWORD dwMessage,HWND wnd,UINT id,UINT callbackmsg,HICON icon,const char * tip); BOOL SHARED_EXPORT uShellNotifyIconEx(DWORD dwMessage,HWND wnd,UINT id,UINT callbackmsg,HICON icon,const char * tip,const char * balloon_title,const char * balloon_msg); HWND SHARED_EXPORT uCreateWindowEx(DWORD dwExStyle,const char * lpClassName,const char * lpWindowName,DWORD dwStyle,int x,int y,int nWidth,int nHeight,HWND hWndParent,HMENU hMenu,HINSTANCE hInstance,LPVOID lpParam); BOOL SHARED_EXPORT uGetSystemDirectory(pfc::string_base & out); BOOL SHARED_EXPORT uGetWindowsDirectory(pfc::string_base & out); BOOL SHARED_EXPORT uSetCurrentDirectory(const char * path); BOOL SHARED_EXPORT uGetCurrentDirectory(pfc::string_base & out); BOOL SHARED_EXPORT uExpandEnvironmentStrings(const char * src,pfc::string_base & out); BOOL SHARED_EXPORT uGetUserName(pfc::string_base & out); BOOL SHARED_EXPORT uGetShortPathName(const char * src,pfc::string_base & out); HSZ SHARED_EXPORT uDdeCreateStringHandle(DWORD ins,const char * src); BOOL SHARED_EXPORT uDdeQueryString(DWORD ins,HSZ hsz,pfc::string_base & out); UINT SHARED_EXPORT uDdeInitialize(LPDWORD pidInst,PFNCALLBACK pfnCallback,DWORD afCmd,DWORD ulRes); BOOL SHARED_EXPORT uDdeAccessData_Text(HDDEDATA data,pfc::string_base & out); HIMAGELIST SHARED_EXPORT uImageList_LoadImage(HINSTANCE hi, const char * lpbmp, int cx, int cGrow, COLORREF crMask, UINT uType, UINT uFlags); #define uDdeFreeStringHandle DdeFreeStringHandle #define uDdeCmpStringHandles DdeCmpStringHandles #define uDdeKeepStringHandle DdeKeepStringHandle #define uDdeUninitialize DdeUninitialize #define uDdeNameService DdeNameService #define uDdeFreeDataHandle DdeFreeDataHandle typedef TVINSERTSTRUCTA uTVINSERTSTRUCT; HTREEITEM SHARED_EXPORT uTreeView_InsertItem(HWND wnd,const uTVINSERTSTRUCT * param); LPARAM SHARED_EXPORT uTreeView_GetUserData(HWND wnd,HTREEITEM item); bool SHARED_EXPORT uTreeView_GetText(HWND wnd,HTREEITEM item,pfc::string_base & out); #define uSetWindowsHookEx SetWindowsHookEx #define uUnhookWindowsHookEx UnhookWindowsHookEx #define uCallNextHookEx CallNextHookEx /* usage: const char * src = "something"; void * temp = malloc(uOSStringEstimateSize(src)); uOSStringConvert(src,temp); //now temp contains OS-friendly (TCHAR) version of src */ typedef TCITEMA uTCITEM; int SHARED_EXPORT uTabCtrl_InsertItem(HWND wnd,t_size idx,const uTCITEM * item); int SHARED_EXPORT uTabCtrl_SetItem(HWND wnd,t_size idx,const uTCITEM * item); int SHARED_EXPORT uGetKeyNameText(LONG lparam,pfc::string_base & out); void SHARED_EXPORT uFixAmpersandChars(const char * src,pfc::string_base & out);//for systray void SHARED_EXPORT uFixAmpersandChars_v2(const char * src,pfc::string_base & out);//for other controls //deprecated t_size SHARED_EXPORT uPrintCrashInfo(LPEXCEPTION_POINTERS param,const char * extrainfo,char * out); enum {uPrintCrashInfo_max_length = 1024}; void SHARED_EXPORT uPrintCrashInfo_Init(const char * name);//called only by exe on startup void SHARED_EXPORT uPrintCrashInfo_AddInfo(const char * p_info);//called only by exe on startup void SHARED_EXPORT uPrintCrashInfo_SetDumpPath(const char * name);//called only by exe on startup void SHARED_EXPORT uDumpCrashInfo(LPEXCEPTION_POINTERS param); BOOL SHARED_EXPORT uListBox_GetText(HWND listbox,UINT index,pfc::string_base & out); void SHARED_EXPORT uPrintfV(pfc::string_base & out,const char * fmt,va_list arglist); static inline void uPrintf(pfc::string_base & out,const char * fmt,...) {va_list list;va_start(list,fmt);uPrintfV(out,fmt,list);va_end(list);} class NOVTABLE uResource { public: virtual const void * GetPointer() = 0; virtual unsigned GetSize() = 0; virtual ~uResource() {} }; typedef uResource* puResource; puResource SHARED_EXPORT uLoadResource(HMODULE hMod,const char * name,const char * type,WORD wLang = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) ); puResource SHARED_EXPORT LoadResourceEx(HMODULE hMod,const TCHAR * name,const TCHAR * type,WORD wLang = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) ); HRSRC SHARED_EXPORT uFindResource(HMODULE hMod,const char * name,const char * type,WORD wLang = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) ); BOOL SHARED_EXPORT uLoadString(HINSTANCE ins,UINT id,pfc::string_base & out); UINT SHARED_EXPORT uCharLower(UINT c); UINT SHARED_EXPORT uCharUpper(UINT c); BOOL SHARED_EXPORT uGetMenuString(HMENU menu,UINT id,pfc::string_base & out,UINT flag); BOOL SHARED_EXPORT uModifyMenu(HMENU menu,UINT id,UINT flags,UINT newitem,const char * data); UINT SHARED_EXPORT uGetMenuItemType(HMENU menu,UINT position); }//extern "C" inline char * uCharNext(char * src) {return src+uCharLength(src);} inline const char * uCharNext(const char * src) {return src+uCharLength(src);} class string_utf8_from_window { public: string_utf8_from_window(HWND wnd) { uGetWindowText(wnd,m_data); } string_utf8_from_window(HWND wnd,UINT id) { uGetDlgItemText(wnd,id,m_data); } inline operator const char * () const {return m_data.get_ptr();} inline t_size length() const {return m_data.length();} inline bool is_empty() const {return length() == 0;} inline const char * get_ptr() const {return m_data.get_ptr();} private: pfc::string8 m_data; }; #define uMAKEINTRESOURCE(x) ((const char*)LOWORD(x)) #ifdef _DEBUG class critical_section { private: CRITICAL_SECTION sec; int count; public: int enter() {EnterCriticalSection(&sec);return ++count;} int leave() {int rv = --count;LeaveCriticalSection(&sec);return rv;} int get_lock_count() {return count;} int get_lock_count_check() {enter();return leave();} inline void assert_locked() {assert(get_lock_count_check()>0);} inline void assert_not_locked() {assert(get_lock_count_check()==0);} critical_section() {InitializeCriticalSection(&sec);count=0;} ~critical_section() {DeleteCriticalSection(&sec);} private: critical_section(const critical_section&) {throw pfc::exception_not_implemented();} const critical_section & operator=(const critical_section &) {throw pfc::exception_not_implemented();} }; #else class critical_section { private: CRITICAL_SECTION sec; public: void enter() throw() {EnterCriticalSection(&sec);} void leave() throw() {LeaveCriticalSection(&sec);} critical_section() {InitializeCriticalSection(&sec);} ~critical_section() {DeleteCriticalSection(&sec);} private: critical_section(const critical_section&) {throw pfc::exception_not_implemented();} const critical_section & operator=(const critical_section &) {throw pfc::exception_not_implemented();} }; #endif class c_insync { private: critical_section & m_section; public: c_insync(critical_section * p_section) throw() : m_section(*p_section) {m_section.enter();} c_insync(critical_section & p_section) throw() : m_section(p_section) {m_section.enter();} ~c_insync() throw() {m_section.leave();} }; #define insync(X) c_insync blah____sync(X) class critical_section2 //smarter version, has try_enter() { private: HANDLE hMutex; int count; public: int enter() {return enter_timeout(INFINITE);} int leave() {int rv = --count;ReleaseMutex(hMutex);return rv;} int get_lock_count() {return count;} int get_lock_count_check() { int val = try_enter(); if (val>0) val = leave(); return val; } int enter_timeout(DWORD t) {return WaitForSingleObject(hMutex,t)==WAIT_OBJECT_0 ? ++count : 0;} int try_enter() {return enter_timeout(0);} int check_count() {enter();return leave();} critical_section2() { hMutex = uCreateMutex(0,0,0); count=0; } ~critical_section2() {CloseHandle(hMutex);} inline void assert_locked() {assert(get_lock_count_check()>0);} inline void assert_not_locked() {assert(get_lock_count_check()==0);} }; class c_insync2 { private: critical_section2 * ptr; public: c_insync2(critical_section2 * p) {ptr=p;ptr->enter();} c_insync2(critical_section2 & p) {ptr=&p;ptr->enter();} ~c_insync2() {ptr->leave();} }; #define insync2(X) c_insync2 blah____sync2(X) //other #define uIsDialogMessage IsDialogMessage #define uGetMessage GetMessage #define uPeekMessage PeekMessage #define uDispatchMessage DispatchMessage #define uCallWindowProc CallWindowProc #define uDefWindowProc DefWindowProc #define uGetWindowLong GetWindowLong #define uSetWindowLong SetWindowLong #define uEndDialog EndDialog #define uDestroyWindow DestroyWindow #define uGetDlgItem GetDlgItem #define uEnableWindow EnableWindow #define uGetDlgItemInt GetDlgItemInt #define uSetDlgItemInt SetDlgItemInt #define __uHookWindowProc(WND,PROC) ((WNDPROC)SetWindowLongPtr(WND,GWLP_WNDPROC,(LONG_PTR)(PROC))) static WNDPROC uHookWindowProc(HWND p_wnd,WNDPROC p_proc) {return __uHookWindowProc(p_wnd,p_proc);} #define uCreateToolbarEx CreateToolbarEx #define uIsBadStringPtr IsBadStringPtrA #define uSendMessage SendMessage #define uSendDlgItemMessage SendDlgItemMessage #define uSendMessageTimeout SendMessageTimeout #define uSendNotifyMessage SendNotifyMessage #define uSendMessageCallback SendMessageCallback #define uPostMessage PostMessage #define uPostThreadMessage PostThreadMessage class string_print_crash { char block[uPrintCrashInfo_max_length]; public: inline operator const char * () const {return block;} inline const char * get_ptr() const {return block;} inline t_size length() {return strlen(block);} inline string_print_crash(LPEXCEPTION_POINTERS param,const char * extrainfo = 0) {uPrintCrashInfo(param,extrainfo,block);} }; class uStringPrintf { public: inline explicit uStringPrintf(const char * fmt,...) { va_list list; va_start(list,fmt); uPrintfV(m_data,fmt,list); va_end(list); } inline operator const char * () const {return m_data.get_ptr();} inline t_size length() const {return m_data.length();} inline bool is_empty() const {return length() == 0;} inline const char * get_ptr() const {return m_data.get_ptr();} private: pfc::string8_fastalloc m_data; }; #pragma deprecated(uStringPrintf, uPrintf, uPrintfV) inline LRESULT uButton_SetCheck(HWND wnd,UINT id,bool state) {return uSendDlgItemMessage(wnd,id,BM_SETCHECK,state ? BST_CHECKED : BST_UNCHECKED,0); } inline bool uButton_GetCheck(HWND wnd,UINT id) {return uSendDlgItemMessage(wnd,id,BM_GETCHECK,0,0) == BST_CHECKED;} class uCallStackTracker { t_size param; public: explicit SHARED_EXPORT uCallStackTracker(const char * name); SHARED_EXPORT ~uCallStackTracker(); }; extern "C" { LPCSTR SHARED_EXPORT uGetCallStackPath(); } #if 1 #define TRACK_CALL(X) uCallStackTracker TRACKER__##X(#X) #define TRACK_CALL_TEXT(X) uCallStackTracker TRACKER__BLAH(X) #define TRACK_CODE(description,code) {uCallStackTracker __call_tracker(description); code;} #else #define TRACK_CALL(X) #define TRACK_CALL_TEXT(X) #define TRACK_CODE(description,code) {code;} #endif extern "C" { int SHARED_EXPORT stricmp_utf8(const char * p1,const char * p2); int SHARED_EXPORT stricmp_utf8_ex(const char * p1,t_size len1,const char * p2,t_size len2); int SHARED_EXPORT stricmp_utf8_stringtoblock(const char * p1,const char * p2,t_size p2_bytes); int SHARED_EXPORT stricmp_utf8_partial(const char * p1,const char * p2,t_size num = ~0); int SHARED_EXPORT stricmp_utf8_max(const char * p1,const char * p2,t_size p1_bytes); t_size SHARED_EXPORT uReplaceStringAdd(pfc::string_base & out,const char * src,t_size src_len,const char * s1,t_size len1,const char * s2,t_size len2,bool casesens); t_size SHARED_EXPORT uReplaceCharAdd(pfc::string_base & out,const char * src,t_size src_len,unsigned c1,unsigned c2,bool casesens); //all lengths in uReplaceString functions are optional, set to -1 if parameters is a simple null-terminated string void SHARED_EXPORT uAddStringLower(pfc::string_base & out,const char * src,t_size len = ~0); void SHARED_EXPORT uAddStringUpper(pfc::string_base & out,const char * src,t_size len = ~0); } class comparator_stricmp_utf8 { public: static int compare(const char * p_string1,const char * p_string2) {return stricmp_utf8(p_string1,p_string2);} }; inline void uStringLower(pfc::string_base & out,const char * src,t_size len = ~0) {out.reset();uAddStringLower(out,src,len);} inline void uStringUpper(pfc::string_base & out,const char * src,t_size len = ~0) {out.reset();uAddStringUpper(out,src,len);} inline t_size uReplaceString(pfc::string_base & out,const char * src,t_size src_len,const char * s1,t_size len1,const char * s2,t_size len2,bool casesens) { out.reset(); return uReplaceStringAdd(out,src,src_len,s1,len1,s2,len2,casesens); } inline t_size uReplaceChar(pfc::string_base & out,const char * src,t_size src_len,unsigned c1,unsigned c2,bool casesens) { out.reset(); return uReplaceCharAdd(out,src,src_len,c1,c2,casesens); } class string_lower { public: explicit string_lower(const char * ptr,t_size p_count = ~0) {uAddStringLower(m_data,ptr,p_count);} inline operator const char * () const {return m_data.get_ptr();} inline t_size length() const {return m_data.length();} inline bool is_empty() const {return length() == 0;} inline const char * get_ptr() const {return m_data.get_ptr();} private: pfc::string8 m_data; }; class string_upper { public: explicit string_upper(const char * ptr,t_size p_count = ~0) {uAddStringUpper(m_data,ptr,p_count);} inline operator const char * () const {return m_data.get_ptr();} inline t_size length() const {return m_data.length();} inline bool is_empty() const {return length() == 0;} inline const char * get_ptr() const {return m_data.get_ptr();} private: pfc::string8 m_data; }; inline UINT char_lower(UINT c) {return uCharLower(c);} inline UINT char_upper(UINT c) {return uCharUpper(c);} inline BOOL uGetLongPathNameEx(const char * name,pfc::string_base & out) { if (uGetLongPathName(name,out)) return TRUE; return uGetFullPathName(name,out); } struct t_font_description { enum { m_facename_length = LF_FACESIZE*2, m_height_dpi = 480, }; t_uint32 m_height; t_uint32 m_weight; t_uint8 m_italic; t_uint8 m_charset; char m_facename[m_facename_length]; HFONT SHARED_EXPORT create() const; bool SHARED_EXPORT popup_dialog(HWND p_parent); void SHARED_EXPORT from_font(HFONT p_font); static t_font_description SHARED_EXPORT g_from_font(HFONT p_font); }; struct t_modal_dialog_entry { HWND m_wnd_to_poke; bool m_in_use; }; extern "C" { void SHARED_EXPORT ModalDialog_Switch(t_modal_dialog_entry & p_entry); void SHARED_EXPORT ModalDialog_PokeExisting(); bool SHARED_EXPORT ModalDialog_CanCreateNew(); HWND SHARED_EXPORT FindOwningPopup(HWND p_wnd); void SHARED_EXPORT PokeWindow(HWND p_wnd); }; //! The purpose of modal_dialog_scope is to help to avoid the modal dialog recursion problem. Current toplevel modal dialog handle is stored globally, so when creation of a new modal dialog is blocked, it can be activated to indicate the reason for the task being blocked. class modal_dialog_scope { public: //! This constructor initializes the modal dialog scope with specified dialog handle. inline modal_dialog_scope(HWND p_wnd) : m_initialized(false) {initialize(p_wnd);} //! This constructor leaves the scope uninitialized (you can call initialize() later with your window handle). inline modal_dialog_scope() : m_initialized(false) {} inline ~modal_dialog_scope() {deinitialize();} //! Returns whether creation of a new modal dialog is allowed (false when there's another one active).\n //! NOTE: when calling context is already inside a modal dialog that you own, you should not be checking this before creating a new modal dialog. inline static bool can_create() {return ModalDialog_CanCreateNew();} //! Activates the top-level modal dialog existing, if one exists. inline static void poke_existing() {ModalDialog_PokeExisting();} //! Initializes the scope with specified window handle. void initialize(HWND p_wnd) { if (!m_initialized) { m_initialized = true; m_entry.m_in_use = true; m_entry.m_wnd_to_poke = p_wnd; ModalDialog_Switch(m_entry); } } void deinitialize() { if (m_initialized) { ModalDialog_Switch(m_entry); m_initialized = false; } } private: modal_dialog_scope(const modal_dialog_scope & p_scope) {assert(0);} const modal_dialog_scope & operator=(const modal_dialog_scope &) {assert(0); return *this;} t_modal_dialog_entry m_entry; bool m_initialized; }; class format_win32_error { public: format_win32_error(DWORD p_code) { if (!uFormatSystemErrorMessage(m_buffer,p_code)) m_buffer << "Unknown error code (" << (unsigned)p_code << ")"; } const char * get_ptr() const {return m_buffer.get_ptr();} operator const char*() const {return m_buffer.get_ptr();} private: pfc::string8 m_buffer; }; struct exception_win32 : public std::exception { exception_win32(DWORD p_code) : std::exception(format_win32_error(p_code)), m_code(p_code) {} DWORD get_code() const {return m_code;} private: DWORD m_code; }; class uDebugLog : public pfc::string_formatter { public: ~uDebugLog() {*this << "\n"; uOutputDebugString(get_ptr());} }; static void uAddWindowStyle(HWND p_wnd,LONG p_style) { SetWindowLong(p_wnd,GWL_STYLE, GetWindowLong(p_wnd,GWL_STYLE) | p_style); } static void uRemoveWindowStyle(HWND p_wnd,LONG p_style) { SetWindowLong(p_wnd,GWL_STYLE, GetWindowLong(p_wnd,GWL_STYLE) & ~p_style); } static void uAddWindowExStyle(HWND p_wnd,LONG p_style) { SetWindowLong(p_wnd,GWL_EXSTYLE, GetWindowLong(p_wnd,GWL_EXSTYLE) | p_style); } static void uRemoveWindowExStyle(HWND p_wnd,LONG p_style) { SetWindowLong(p_wnd,GWL_EXSTYLE, GetWindowLong(p_wnd,GWL_EXSTYLE) & ~p_style); } static unsigned MapDialogWidth(HWND p_dialog,unsigned p_value) { RECT temp; temp.left = 0; temp.right = p_value; temp.top = temp.bottom = 0; if (!MapDialogRect(p_dialog,&temp)) return 0; return temp.right; } static bool IsKeyPressed(unsigned vk) { return (GetKeyState(vk) & 0x8000) ? true : false; } #include "audio_math.h" #include "win32_misc.h" #endif //_SHARED_DLL__SHARED_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/foobar2000/shared/win32_misc.h ================================================ class win32_menu { public: win32_menu(HMENU p_initval) : m_menu(p_initval) {} win32_menu() : m_menu(NULL) {} ~win32_menu() {release();} void release() { if (m_menu != NULL) { DestroyMenu(m_menu); m_menu = NULL; } } void set(HMENU p_menu) {release(); m_menu = p_menu;} void create_popup() { release(); SetLastError(NO_ERROR); m_menu = CreatePopupMenu(); if (m_menu == NULL) throw exception_win32(GetLastError()); } HMENU get() const {return m_menu;} HMENU detach() {return pfc::replace_t(m_menu,(HMENU)NULL);} bool is_valid() const {return m_menu != NULL;} private: win32_menu(const win32_menu &) {throw pfc::exception_not_implemented();} const win32_menu & operator=(const win32_menu &) {throw pfc::exception_not_implemented();} HMENU m_menu; }; class win32_font { public: win32_font(HFONT p_initval) : m_font(p_initval) {} win32_font() : m_font(NULL) {} ~win32_font() {release();} void release() { HFONT temp = detach(); if (temp != NULL) DeleteObject(temp); } void set(HFONT p_font) {release(); m_font = p_font;} HFONT get() const {return m_font;} HFONT detach() {return pfc::replace_t(m_font,(HFONT)NULL);} void create(const t_font_description & p_desc) { SetLastError(NO_ERROR); HFONT temp = p_desc.create(); if (temp == NULL) throw exception_win32(GetLastError()); set(temp); } bool is_valid() const {return m_font != NULL;} private: win32_font(const win32_font&) {throw pfc::exception_not_implemented();} const win32_font & operator=(const win32_font &) {throw pfc::exception_not_implemented();} HFONT m_font; }; class win32_event { public: win32_event() : m_handle(NULL) {} ~win32_event() {release();} void create(bool p_manualreset,bool p_initialstate) { release(); SetLastError(NO_ERROR); m_handle = CreateEvent(NULL,p_manualreset ? TRUE : FALSE, p_initialstate ? TRUE : FALSE,NULL); if (m_handle == NULL) throw exception_win32(GetLastError()); } void set(HANDLE p_handle) {release(); m_handle = p_handle;} HANDLE get() const {return m_handle;} HANDLE detach() {return pfc::replace_t(m_handle,(HANDLE)NULL);} bool is_valid() const {return m_handle != NULL;} void release() { HANDLE temp = detach(); if (temp != NULL) CloseHandle(temp); } //! Returns true when signaled, false on timeout bool wait_for(double p_timeout_seconds) {return g_wait_for(get(),p_timeout_seconds);} static DWORD g_calculate_wait_time(double p_seconds) { DWORD time = 0; if (p_seconds> 0) { time = audio_math::rint32((audio_sample)(p_seconds * 1000.0)); if (time == 0) time = 1; } else if (p_seconds < 0) { time = INFINITE; } return time; } //! Returns true when signaled, false on timeout static bool g_wait_for(HANDLE p_event,double p_timeout_seconds) { SetLastError(NO_ERROR); DWORD status = WaitForSingleObject(p_event,g_calculate_wait_time(p_timeout_seconds)); switch(status) { case WAIT_FAILED: throw exception_win32(GetLastError()); default: throw pfc::exception_bug_check(); case WAIT_OBJECT_0: return true; case WAIT_TIMEOUT: return false; } } void set_state(bool p_state) { PFC_ASSERT(m_handle != NULL); if (p_state) SetEvent(m_handle); else ResetEvent(m_handle); } private: win32_event(const win32_event&) {throw pfc::exception_not_implemented();} const win32_event & operator=(const win32_event &) {throw pfc::exception_not_implemented();} HANDLE m_handle; }; static void uSleepSeconds(double p_time,bool p_alertable) { SleepEx(win32_event::g_calculate_wait_time(p_time),p_alertable ? TRUE : FALSE); } class win32_icon { public: win32_icon(HICON p_initval) : m_icon(p_initval) {} win32_icon() : m_icon(NULL) {} ~win32_icon() {release();} void release() { HICON temp = detach(); if (temp != NULL) DestroyIcon(temp); } void set(HICON p_icon) {release(); m_icon = p_icon;} HICON get() const {return m_icon;} HICON detach() {return pfc::replace_t(m_icon,(HICON)NULL);} bool is_valid() const {return m_icon != NULL;} private: win32_icon(const win32_icon&) {throw pfc::exception_not_implemented();} const win32_icon & operator=(const win32_icon &) {throw pfc::exception_not_implemented();} HICON m_icon; }; class win32_accelerator { public: win32_accelerator() : m_accel(NULL) {} ~win32_accelerator() {release();} HACCEL get() const {return m_accel;} void load(HINSTANCE p_inst,const TCHAR * p_id) { release(); SetLastError(NO_ERROR); m_accel = LoadAccelerators(p_inst,p_id); if (m_accel == NULL) { throw exception_win32(GetLastError()); } } void release() { if (m_accel != NULL) { DestroyAcceleratorTable(m_accel); m_accel = NULL; } } private: HACCEL m_accel; PFC_CLASS_NOT_COPYABLE(win32_accelerator,win32_accelerator); }; class SelectObjectScope { public: SelectObjectScope(HDC p_dc,HGDIOBJ p_obj) : m_dc(p_dc), m_obj(SelectObject(p_dc,p_obj)) {} ~SelectObjectScope() {SelectObject(m_dc,m_obj);} private: SelectObjectScope(const SelectObjectScope&) {throw pfc::exception_not_implemented();} const SelectObjectScope & operator=(const SelectObjectScope&) {throw pfc::exception_not_implemented();} HDC m_dc; HGDIOBJ m_obj; }; ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/alloc.h ================================================ namespace pfc { static void * raw_malloc(t_size p_size) { return p_size > 0 ? new_ptr_check_t(malloc(p_size)) : NULL; } static void raw_free(void * p_block) throw() {free(p_block);} static void* raw_realloc(void * p_ptr,t_size p_size) { if (p_size == 0) {raw_free(p_ptr); return NULL;} else if (p_ptr == NULL) return raw_malloc(p_size); else return pfc::new_ptr_check_t(::realloc(p_ptr,p_size)); } static bool raw_realloc_inplace(void * p_block,t_size p_size) throw() { if (p_block == NULL) return p_size == 0; #ifdef _MSC_VER if (p_size == 0) return false; return _expand(p_block,p_size) != NULL; #else return false; #endif } template<typename T> t_size calc_array_width(t_size p_width) { return pfc::mul_safe_t<std::bad_alloc,t_size>(p_width,sizeof(T)); } template<typename T> T * __raw_malloc_t(t_size p_size) { return reinterpret_cast<T*>(raw_malloc(calc_array_width<T>(p_size))); } template<typename T> void __raw_free_t(T * p_block) throw() { raw_free(reinterpret_cast<void*>(p_block)); } template<typename T> T * __raw_realloc_t(T * p_block,t_size p_size) { return reinterpret_cast<T*>(raw_realloc(p_block,calc_array_width<T>(p_size))); } template<typename T> bool __raw_realloc_inplace_t(T * p_block,t_size p_size) { return raw_realloc_inplace(p_block,calc_array_width<T>(p_size)); } template<typename t_exception,typename t_int> inline t_int safe_shift_left_t(t_int p_val,t_size p_shift = 1) { t_int newval = p_val << p_shift; if (newval >> p_shift != p_val) throw t_exception(); return newval; } template<typename t_item> class alloc_dummy { private: typedef alloc_dummy<t_item> t_self; public: alloc_dummy() {} void set_size(t_size p_size) {throw pfc::exception_not_implemented();} t_size get_size() const {throw pfc::exception_not_implemented();} const t_item & operator[](t_size p_index) const {throw pfc::exception_not_implemented();} t_item & operator[](t_size p_index) {throw pfc::exception_not_implemented();} bool is_ptr_owned(const void * p_item) const {return false;} //not mandatory const t_item * get_ptr() const {throw pfc::exception_not_implemented();} t_item * get_ptr() {throw pfc::exception_not_implemented();} void prealloc(t_size) {throw pfc::exception_not_implemented();} void force_reset() {throw pfc::exception_not_implemented();} private: const t_self & operator=(const t_self &) {throw pfc::exception_not_implemented();} alloc_dummy(const t_self&) {throw pfc::exception_not_implemented();} }; template<typename t_item> bool is_pointer_in_range(const t_item * p_buffer,t_size p_buffer_size,const void * p_pointer) { return p_pointer >= reinterpret_cast<const void*>(p_buffer) && p_pointer < reinterpret_cast<const void*>(p_buffer + p_buffer_size); } //! Simple inefficient fully portable allocator. template<typename t_item> class alloc_simple { private: typedef alloc_simple<t_item> t_self; public: alloc_simple() : m_data(NULL), m_size(0) {} void set_size(t_size p_size) { if (p_size != m_size) { t_item * l_data = NULL; if (p_size > 0) l_data = new t_item[p_size]; try { pfc::memcpy_t(l_data,m_data,pfc::min_t(m_size,p_size)); } catch(...) { delete[] l_data; throw; } delete[] m_data; m_data = l_data; m_size = p_size; } } t_size get_size() const {return m_size;} const t_item & operator[](t_size p_index) const {PFC_ASSERT(p_index < m_size); return m_data[p_index];} t_item & operator[](t_size p_index) {PFC_ASSERT(p_index < m_size); return m_data[p_index];} bool is_ptr_owned(const void * p_item) const {return is_pointer_in_range(get_ptr(),get_size(),p_item);} t_item * get_ptr() {return m_data;} const t_item * get_ptr() const {return m_data;} void prealloc(t_size) {} void force_reset() {set_size(0);} ~alloc_simple() {delete[] m_data;} private: const t_self & operator=(const t_self &) {throw pfc::exception_not_implemented();} alloc_simple(const t_self&) {throw pfc::exception_not_implemented();} t_item * m_data; t_size m_size; }; template<typename t_item> class __array_fast_helper_t { private: typedef __array_fast_helper_t<t_item> t_self; public: __array_fast_helper_t() : m_buffer(NULL), m_size_total(0), m_size(0) {} void set_size(t_size p_size,t_size p_size_total) { PFC_ASSERT(p_size <= p_size_total); PFC_ASSERT(m_size <= m_size_total); if (p_size_total > m_size_total) { resize_storage(p_size_total); resize_content(p_size); } else { resize_content(p_size); resize_storage(p_size_total); } } t_size get_size() const {return m_size;} t_size get_size_total() const {return m_size_total;} const t_item & operator[](t_size p_index) const {PFC_ASSERT(p_index < m_size); return m_buffer[p_index];} t_item & operator[](t_size p_index) {PFC_ASSERT(p_index < m_size); return m_buffer[p_index];} ~__array_fast_helper_t() { set_size(0,0); } t_item * get_ptr() {return m_buffer;} const t_item * get_ptr() const {return m_buffer;} bool is_ptr_owned(const void * p_item) const {return is_pointer_in_range(m_buffer,m_size_total,p_item);} private: const t_self & operator=(const t_self &) {throw pfc::exception_not_implemented();} __array_fast_helper_t(const t_self &) {throw pfc::exception_not_implemented();} void resize_content(t_size p_size) { if (traits_t<t_item>::needs_constructor || traits_t<t_item>::needs_destructor) { if (p_size > m_size) {//expand do { __unsafe__in_place_constructor_t(m_buffer[m_size]); m_size++; } while(m_size < p_size); } else if (p_size < m_size) { __unsafe__in_place_destructor_array_t(m_buffer + p_size, m_size - p_size); m_size = p_size; } } else { m_size = p_size; } } void resize_storage(t_size p_size) { PFC_ASSERT( m_size <= m_size_total ); PFC_ASSERT( m_size <= p_size ); if (m_size_total != p_size) { if (pfc::traits_t<t_item>::realloc_safe) { m_buffer = pfc::__raw_realloc_t(m_buffer,p_size); m_size_total = p_size; } else if (__raw_realloc_inplace_t(m_buffer,p_size)) { //success m_size_total = p_size; } else { t_item * newbuffer = pfc::__raw_malloc_t<t_item>(p_size); try { pfc::__unsafe__in_place_constructor_array_copy_t(newbuffer,m_size,m_buffer); } catch(...) { pfc::__raw_free_t(newbuffer); throw; } pfc::__unsafe__in_place_destructor_array_t(m_buffer,m_size); pfc::__raw_free_t(m_buffer); m_buffer = newbuffer; m_size_total = p_size; } } } t_item * m_buffer; t_size m_size,m_size_total; }; template<typename t_item> class alloc_standard { private: typedef alloc_standard<t_item> t_self; public: alloc_standard() {} void set_size(t_size p_size) {m_content.set_size(p_size,p_size);} t_size get_size() const {return m_content.get_size();} const t_item & operator[](t_size p_index) const {return m_content[p_index];} t_item & operator[](t_size p_index) {return m_content[p_index];} const t_item * get_ptr() const {return m_content.get_ptr();} t_item * get_ptr() {return m_content.get_ptr();} bool is_ptr_owned(const void * p_item) const {return m_content.is_ptr_owned(p_item);} void prealloc(t_size p_size) {} void force_reset() {set_size(0);} private: alloc_standard(const t_self &) {throw pfc::exception_not_implemented();} const t_self & operator=(const t_self&) {throw pfc::exception_not_implemented();} __array_fast_helper_t<t_item> m_content; }; template<typename t_item> class alloc_fast { private: typedef alloc_fast<t_item> t_self; public: alloc_fast() {} void set_size(t_size p_size) { t_size size_base = m_data.get_size_total(); if (size_base == 0) size_base = 1; while(size_base < p_size) { size_base = safe_shift_left_t<std::bad_alloc,t_size>(size_base,1); } while(size_base >> 2 > p_size) { size_base >>= 1; } m_data.set_size(p_size,size_base); } t_size get_size() const {return m_data.get_size();} const t_item & operator[](t_size p_index) const {return m_data[p_index];} t_item & operator[](t_size p_index) {return m_data[p_index];} const t_item * get_ptr() const {return m_data.get_ptr();} t_item * get_ptr() {return m_data.get_ptr();} bool is_ptr_owned(const void * p_item) const {return m_data.is_ptr_owned(p_item);} void prealloc(t_size) {} void force_reset() {m_data.set_size(0,0);} private: alloc_fast(const t_self &) {throw pfc::exception_not_implemented();} const t_self & operator=(const t_self&) {throw pfc::exception_not_implemented();} __array_fast_helper_t<t_item> m_data; }; template<typename t_item> class alloc_fast_aggressive { private: typedef alloc_fast_aggressive<t_item> t_self; public: alloc_fast_aggressive() {} void set_size(t_size p_size) { t_size size_base = m_data.get_size_total(); if (size_base == 0) size_base = 1; while(size_base < p_size) { size_base = safe_shift_left_t<std::bad_alloc,t_size>(size_base,1); } m_data.set_size(p_size,size_base); } void prealloc(t_size p_size) { if (p_size > 0) { t_size size_base = m_data.get_size_total(); if (size_base == 0) size_base = 1; while(size_base < p_size) { size_base = safe_shift_left_t<std::bad_alloc,t_size>(size_base,1); } m_data.set_size(m_data.get_size(),size_base); } } t_size get_size() const {return m_data.get_size();} const t_item & operator[](t_size p_index) const {;return m_data[p_index];} t_item & operator[](t_size p_index) {return m_data[p_index];} const t_item * get_ptr() const {return m_data.get_ptr();} t_item * get_ptr() {return m_data.get_ptr();} bool is_ptr_owned(const void * p_item) const {return m_data.is_ptr_owned(p_item);} void force_reset() {m_data.set_size(0,0);} private: alloc_fast_aggressive(const t_self &) {throw pfc::exception_not_implemented();} const t_self & operator=(const t_self&) {throw pfc::exception_not_implemented();} __array_fast_helper_t<t_item> m_data; }; template<t_size p_width> class alloc_fixed { public: template<typename t_item> class alloc { private: typedef alloc<t_item> t_self; public: alloc() : m_size(0) {} void set_size(t_size p_size) { static_assert_t<sizeof(m_array) == sizeof(t_item[p_width])>(); if (p_size > p_width) throw pfc::exception_overflow(); else if (p_size > m_size) { __unsafe__in_place_constructor_array_t(get_ptr()+m_size,p_size-m_size); m_size = p_size; } else if (p_size < m_size) { __unsafe__in_place_destructor_array_t(get_ptr()+p_size,m_size-p_size); m_size = p_size; } } ~alloc() { if (pfc::traits_t<t_item>::needs_destructor) set_size(0); } t_size get_size() const {return m_size;} t_item * get_ptr() {return reinterpret_cast<t_item*>(&m_array);} const t_item * get_ptr() const {return reinterpret_cast<const t_item*>(&m_array);} const t_item & operator[](t_size n) const {return get_ptr()[n];} t_item & operator[](t_size n) {return get_ptr()[n];} bool is_ptr_owned(const void * p_item) const {return is_pointer_in_range(get_ptr(),p_width,p_item);} void prealloc(t_size) {} void force_reset() {set_size(0);} private: alloc(const t_self&) {throw pfc::exception_not_implemented();} const t_self& operator=(const t_self&) {throw pfc::exception_not_implemented();} t_uint8 m_array[sizeof(t_item[p_width])]; t_size m_size; }; }; template<t_size p_width, template<typename> class t_alloc = alloc_standard > class alloc_hybrid { public: template<typename t_item> class alloc { private: typedef alloc<t_item> t_self; public: alloc() {} void set_size(t_size p_size) { if (p_size > p_width) { m_fixed.set_size(p_width); m_variable.set_size(p_size - p_width); } else { m_fixed.set_size(p_size); m_variable.set_size(0); } } t_item & operator[](t_size p_index) { PFC_ASSERT(p_index < get_size()); if (p_index < p_width) return m_fixed[p_index]; else return m_variable[p_index - p_width]; } const t_item & operator[](t_size p_index) const { PFC_ASSERT(p_index < get_size()); if (p_index < p_width) return m_fixed[p_index]; else return m_variable[p_index - p_width]; } t_size get_size() const {return m_fixed.get_size() + m_variable.get_size();} bool is_ptr_owned(const void * p_item) const {return m_fixed.is_ptr_owned(p_item) || m_variable.is_ptr_owned(p_item);} void prealloc(t_size p_size) { if (p_size > p_width) m_variable.prealloc(p_size - p_width); } void force_reset() { m_fixed.force_reset(); m_variable.force_reset(); } private: alloc(const t_self&) {throw pfc::exception_not_implemented();} const t_self& operator=(const t_self&) {throw pfc::exception_not_implemented();} typename alloc_fixed<p_width>::template alloc<t_item> m_fixed; t_alloc<t_item> m_variable; }; }; template<typename t_item> class traits_t<alloc_simple<t_item> > : public traits_default_movable {}; template<typename t_item> class traits_t<__array_fast_helper_t<t_item> > : public traits_default_movable {}; template<typename t_item> class traits_t<alloc_standard<t_item> > : public pfc::traits_t<__array_fast_helper_t<t_item> > {}; template<typename t_item> class traits_t<alloc_fast<t_item> > : public pfc::traits_t<__array_fast_helper_t<t_item> > {}; template<typename t_item> class traits_t<alloc_fast_aggressive<t_item> > : public pfc::traits_t<__array_fast_helper_t<t_item> > {}; #if 0//not working (compiler bug?) template<t_size p_width,typename t_item> class traits_t<typename alloc_fixed<p_width>::template alloc<t_item> > : public pfc::traits_t<t_item> { public: enum { needs_constructor = true, }; }; template<t_size p_width,template<typename> class t_alloc,typename t_item> class traits_t<typename alloc_hybrid<p_width,t_alloc>::template alloc<t_item> > : public traits_combined<t_alloc,typename alloc_fixed<p_width>::template alloc<t_item> > {}; #endif }; ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/array.h ================================================ #ifndef _PFC_ARRAY_H_ #define _PFC_ARRAY_H_ namespace pfc { template<typename t_item, template<typename> class t_alloc = alloc_standard> class array_t; //! Special simplififed version of array class that avoids stepping on landmines with classes without public copy operators/constructors. template<typename _t_item> class array_staticsize_t { public: typedef _t_item t_item; private: typedef array_staticsize_t<t_item> t_self; public: array_staticsize_t() : m_size(0), m_array(NULL) {} array_staticsize_t(t_size p_size) : m_size(0), m_array(NULL) {set_size_discard(p_size);} ~array_staticsize_t() {__release();} //! Copy constructor nonfunctional when data type is not copyable. array_staticsize_t(const t_self & p_source) : m_size(0), m_array(NULL) { *this = p_source; } //! Copy operator nonfunctional when data type is not copyable. const t_self & operator=(const t_self & p_source) { __release(); //m_array = pfc::malloc_copy_t(p_source.get_size(),p_source.get_ptr()); const t_size newsize = p_source.get_size() m_array = new t_item[newsize] m_size = newsize; for(t_size n = 0; n < newsize; n++) m_array[n] = p_source[n]; return *this; } void set_size_discard(t_size p_size) { __release(); if (p_size > 0) { m_array = new t_item[p_size]; m_size = p_size; } } t_size get_size() const {return m_size;} const t_item * get_ptr() const {return m_array;} t_item * get_ptr() {return m_array;} const t_item & operator[](t_size p_index) const {PFC_ASSERT(p_index < get_size());return m_array[p_index];} t_item & operator[](t_size p_index) {PFC_ASSERT(p_index < get_size());return m_array[p_index];} template<typename t_source> bool is_owned(const t_source & p_item) {return pfc::is_pointer_in_range(get_ptr(),get_size(),&p_item);} private: void __release() { m_size = 0; delete[] pfc::replace_null_t(m_array); } t_item * m_array; t_size m_size; }; template<typename t_to,typename t_from> inline void copy_array_t(t_to & p_to,const t_from & p_from) { const t_size size = array_size_t(p_from); if (p_to.has_owned_items(p_from)) {//avoid landmines with actual array data overlapping, or p_from being same as p_to array_staticsize_t<typename t_to::t_item> temp; temp.set_size_discard(size); pfc::copy_array_loop_t(temp,p_from,size); p_to.set_size(size); pfc::copy_array_loop_t(p_to,temp,size); } else { p_to.set_size(size); pfc::copy_array_loop_t(p_to,p_from,size); } } template<typename t_array,typename t_value> inline void fill_array_t(t_array & p_array,const t_value & p_value) { const t_size size = array_size_t(p_array); for(t_size n=0;n<size;n++) p_array[n] = p_value; } template<typename _t_item, template<typename> class t_alloc> class array_t { public: typedef _t_item t_item; private: typedef array_t<t_item,t_alloc> t_self; public: array_t() {} array_t(const t_self & p_source) {copy_array_t(*this,p_source);} template<typename t_source> array_t(const t_source & p_source) {copy_array_t(*this,p_source);} const t_self & operator=(const t_self & p_source) {copy_array_t(*this,p_source); return *this;} template<typename t_source> const t_self & operator=(const t_source & p_source) {copy_array_t(*this,p_source); return *this;} void set_size(t_size p_size) {m_alloc.set_size(p_size);} void set_count(t_size p_count) {m_alloc.set_size(p_count);} t_size get_size() const {return m_alloc.get_size();} t_size get_count() const {return m_alloc.get_size();} void force_reset() {m_alloc.force_reset();} const t_item & operator[](t_size p_index) const {PFC_ASSERT(p_index < get_size());return m_alloc[p_index];} t_item & operator[](t_size p_index) {PFC_ASSERT(p_index < get_size());return m_alloc[p_index];} //! Warning: buffer pointer must not point to buffer allocated by this array (fixme). template<typename t_source> void set_data_fromptr(const t_source * p_buffer,t_size p_count) { set_size(p_count); pfc::copy_array_loop_t(*this,p_buffer,p_count); } template<typename t_array> void append(const t_array & p_source) { if (has_owned_items(p_source)) append(array_t<t_item>(p_source)); else { const t_size source_size = array_size_t(p_source); const t_size base = get_size(); increase_size(source_size); for(t_size n=0;n<source_size;n++) m_alloc[base+n] = p_source[n]; } } //! Warning: buffer pointer must not point to buffer allocated by this array (fixme). template<typename t_append> void append_fromptr(const t_append * p_buffer,t_size p_count) { PFC_ASSERT( !is_owned(&p_buffer[0]) ); t_size base = get_size(); increase_size(p_count); for(t_size n=0;n<p_count;n++) m_alloc[base+n] = p_buffer[n]; } void increase_size(t_size p_delta) { t_size new_size = get_size() + p_delta; if (new_size < p_delta) throw std::bad_alloc(); set_size(new_size); } template<typename t_append> void append_single(const t_append & p_item) { if (is_owned(p_item)) append_single(t_append(p_item)); else { const t_size base = get_size(); increase_size(1); m_alloc[base] = p_item; } } template<typename t_filler> void fill(const t_filler & p_filler) { const t_size max = get_size(); for(t_size n=0;n<max;n++) m_alloc[n] = p_filler; } void fill_null() { const t_size max = get_size(); for(t_size n=0;n<max;n++) m_alloc[n] = 0; } void grow_size(t_size p_size) { if (p_size > get_size()) set_size(p_size); } //not supported by some allocs const t_item * get_ptr() const {return m_alloc.get_ptr();} t_item * get_ptr() {return m_alloc.get_ptr();} void prealloc(t_size p_size) {m_alloc.prealloc(p_size);} template<typename t_array> bool has_owned_items(const t_array & p_source) { if (array_size_t(p_source) == 0) return false; //how the hell would we properly check if any of source items is owned by us, in case source array implements some weird mixing of references of items from different sources? //the most obvious way means evil bottleneck here (whether it matters or not from caller's point of view which does something O(n) already is another question) //at least this will work fine with all standard classes which don't crossreference anyhow and always use own storage //perhaps we'll traitify this someday later return is_owned(p_source[0]); } template<typename t_source> bool is_owned(const t_source & p_item) { return m_alloc.is_ptr_owned(&p_item); } template<typename t_item> void set_single(const t_item & p_item) { set_size(1); (*this)[0] = p_item; } template<typename t_callback> void enumerate(t_callback & p_callback) const { for(t_size n = 0; n < get_size(); n++ ) { p_callback((*this)[n]); } } private: t_alloc<t_item> m_alloc; }; template<typename t_item,t_size p_width,template<typename> class t_alloc = alloc_standard > class array_hybrid_t : public array_t<t_item, pfc::alloc_hybrid<p_width,t_alloc>::template alloc > {}; template<typename t_item> class traits_t<array_staticsize_t<t_item> > : public traits_default_movable {}; template<typename t_item,template<typename> class t_alloc> class traits_t<array_t<t_item,t_alloc> > : public pfc::traits_t<t_alloc<t_item> > {}; template<typename t_comparator = comparator_default> class comparator_array { public: template<typename t_array1, typename t_array2> static int compare(const t_array1 & p_array1, const t_array2 & p_array2) { t_size walk = 0; for(;;) { if (walk >= p_array1.get_size() && walk >= p_array2.get_size()) return 0; else if (walk >= p_array1.get_size()) return -1; else if (walk >= p_array2.get_size()) return 1; else { int state = t_comparator::compare(p_array1[walk],p_array2[walk]); if (state != 0) return state; } ++walk; } } }; } #endif //_PFC_ARRAY_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/avltree.h ================================================ #ifndef _AVLTREE_T_H_INCLUDED_ #define _AVLTREE_T_H_INCLUDED_ namespace pfc { template<typename t_storage,typename t_comparator = comparator_default> class avltree_t { typedef avltree_t<t_storage,t_comparator> t_self; template<typename t_item1,typename t_item2> inline static int compare(const t_item1 & p_item1, const t_item2 & p_item2) { return t_comparator::compare(p_item1,p_item2); } struct t_node { t_node *m_left,*m_right; t_size m_depth; t_storage m_data; template<bool p_which> t_node * & child() {return p_which ? m_right : m_left;} template<bool p_which> t_node * child() const {return p_which ? m_right : m_left;} ~t_node() { if (m_left != NULL) delete m_left; if (m_right != NULL) delete m_right; } template<typename T> t_node(T const & p_param) : m_data(p_param), m_left(NULL), m_right(NULL), m_depth(0) {} }; t_node * m_root; static t_size calc_depth(const t_node * ptr) { return ptr ? 1+ptr->m_depth : 0; } static void recalc_depth(t_node * ptr) { ptr->m_depth = pfc::max_t(calc_depth(ptr->m_left), calc_depth(ptr->m_right)); } static void assert_children(t_node * ptr) { #ifdef _DEBUG PFC_ASSERT(ptr->m_depth == pfc::max_t(calc_depth(ptr->m_left),calc_depth(ptr->m_right)) ); #endif } static int test_depth(t_node * ptr) { if (ptr==0) return 0; else return calc_depth(ptr->m_right) - calc_depth(ptr->m_left); } static t_node * extract_left_leaf(t_node * & p_base) { if (p_base->m_left != NULL) { t_node * ret = extract_left_leaf(p_base->m_left); recalc_depth(p_base); g_rebalance(p_base); return ret; } else { t_node * node = p_base; p_base = node->m_right; node->m_right = 0; node->m_depth = 0; return node; } } static t_node * extract_right_leaf(t_node ** p_base) { t_node * node = *p_base; if (node->m_right != NULL) { t_node * ret = extract_right_leaf(&node->m_right); recalc_depth(node); g_rebalance(p_base); return ret; } else { *p_base = node->m_left; node->m_left = 0; node->m_depth = 0; return node; } } static void remove_internal(t_node* & p_node) { t_node * deleteme = p_node; if (p_node->m_left==0) p_node = p_node->m_right; else if (p_node->m_right==0) p_node = p_node->m_left; else { t_node * swap = extract_left_leaf(p_node->m_right); swap->m_left = deleteme->m_left; swap->m_right = deleteme->m_right; recalc_depth(swap); p_node = swap; } deleteme->m_left = deleteme->m_right = NULL; deleteme->m_depth = 0; delete deleteme; } template<typename t_nodewalk,typename t_callback> static void __enum_items_recur(t_nodewalk * p_node,t_callback & p_callback) { if (p_node != NULL) { __enum_items_recur<t_nodewalk>(p_node->m_left,p_callback); p_callback (p_node->m_data); __enum_items_recur<t_nodewalk>(p_node->m_right,p_callback); } } template<typename t_search> static t_storage * g_find_or_add(t_node * & p_base,t_search const & p_search,bool & p_new) { if (p_base == NULL) { p_base = new t_node(p_search); p_new = true; return &p_base->m_data; } int result = compare(p_base->m_data,p_search); if (result > 0) { t_storage * ret = g_find_or_add<t_search>(p_base->m_left,p_search,p_new); if (p_new) { recalc_depth(p_base); g_rebalance(p_base); } return ret; } else if (result < 0) { t_storage * ret = g_find_or_add<t_search>(p_base->m_right,p_search,p_new); if (p_new) { recalc_depth(p_base); g_rebalance(p_base); } return ret; } else { p_new = false; return &p_base->m_data; } } static void g_rotate_right(t_node * & p_node) { t_node * oldroot = p_node; t_node * newroot = oldroot->m_right; PFC_ASSERT(newroot != NULL); oldroot->m_right = newroot->m_left; newroot->m_left = oldroot; recalc_depth(oldroot); recalc_depth(newroot); p_node = newroot; } static void g_rotate_left(t_node * & p_node) { t_node * oldroot = p_node; t_node * newroot = oldroot->m_left; PFC_ASSERT(newroot != NULL); oldroot->m_left = newroot->m_right; newroot->m_right = oldroot; recalc_depth(oldroot); recalc_depth(newroot); p_node = newroot; } static void g_rebalance(t_node * & p_node) { int balance = test_depth(p_node); if (balance > 1) { //right becomes root if (test_depth((p_node)->m_right) < 0) { g_rotate_left((p_node)->m_right); } g_rotate_right(p_node); } else if (balance < -1) { //left becomes root if (test_depth((p_node)->m_left) > 0) { g_rotate_right((p_node)->m_left); } g_rotate_left(p_node); } } template<typename t_search> static bool g_remove(t_node * & p_node,t_search const & p_search) { if (p_node == NULL) return false; int result = compare(p_node->m_data,p_search); if (result == 0) { remove_internal(p_node); if (p_node != NULL) { recalc_depth(p_node); g_rebalance(p_node); selftest(p_node); } return true; } else { if (g_remove<t_search>(result > 0 ? p_node->m_left : p_node->m_right,p_search)) { recalc_depth(p_node); g_rebalance(p_node); selftest(p_node); return true; } else { return false; } } } static void selftest(t_node * p_node) { #if 0 //def _DEBUG if (p_node != NULL) { selftest(p_node->m_left); selftest(p_node->m_right); assert_children(p_node); int delta = test_depth(p_node); assert(delta >= -1 && delta <= 1); } #endif } static t_size calc_count(const t_node * p_node) { if (p_node != NULL) { return 1 + calc_count(p_node->m_left) + calc_count(p_node->m_right); } else { return 0; } } template<typename t_item> t_storage * __find_item_ptr(t_item const & p_item) const { t_node* ptr = m_root; while(ptr != NULL) { int result = compare(ptr->m_data,p_item); if (result > 0) ptr=ptr->m_left; else if (result < 0) ptr=ptr->m_right; else return &ptr->m_data; } return NULL; } template<bool inclusive,bool above,typename t_search> t_storage * __find_nearest(const t_search & p_search) const { t_node * ptr = m_root; t_storage * found = NULL; while(ptr != NULL) { int result = compare(ptr->m_data,p_search); if (above) result = -result; if (inclusive && result == 0) { //direct hit found = &ptr->m_data; break; } else if (result < 0) { //match found = &ptr->m_data; ptr = ptr->child<!above>(); } else { //mismatch ptr = ptr->child<above>(); } } return found; } public: avltree_t() : m_root(NULL) {} ~avltree_t() {reset();} const t_self & operator=(const t_self & p_other) {__copy(p_other);return *this;} avltree_t(const t_self & p_other) : m_root(NULL) {__copy(p_other);} template<typename t_other> const t_self & operator=(const t_other & p_other) {copy_list_enumerated(*this,p_other);return *this;} template<typename t_other> avltree_t(const t_other & p_other) : m_root(NULL) {copy_list_enumerated(*this,p_other);} template<bool inclusive,bool above,typename t_search> const t_storage * find_nearest_item(const t_search & p_search) const { return __find_nearest<inclusive,above>(p_search); } template<bool inclusive,bool above,typename t_search> t_storage * find_nearest_item(const t_search & p_search) { return __find_nearest<inclusive,above>(p_search); } template<typename t_item> t_storage & add_item(t_item const & p_item) { bool dummy; return add_item_ex(p_item,dummy); } template<typename t_item> t_storage & add_item_ex(t_item const & p_item,bool & p_isnew) { t_storage * ret = g_find_or_add(m_root,p_item,p_isnew); selftest(m_root); return *ret; } template<typename t_item> void set_item(const t_item & p_item) { bool isnew; t_storage & found = add_item_ex(p_item,isnew); if (isnew) found = p_item; } template<typename t_item> const t_storage * find_item_ptr(t_item const & p_item) const { return __find_item_ptr(p_item); } //! WARNING: caller must not alter the item in a way that changes the sort order. template<typename t_item> t_storage * find_item_ptr(t_item const & p_item) { return __find_item_ptr(p_item); } template<typename t_item> bool have_item(const t_item & p_item) const { return find_item_ptr(p_item) != NULL; } void remove_all() { delete pfc::replace_null_t(m_root); } template<typename t_item> void remove_item(t_item const & p_item) { g_remove<t_item>(m_root,p_item); selftest(m_root); } t_size get_count() const { return calc_count(m_root); } template<typename t_callback> void enumerate(t_callback & p_callback) const { __enum_items_recur<const t_node>(m_root,p_callback); } //! Allows callback to modify the tree content. //! WARNING: items must not be altered in a way that changes their sort order. template<typename t_callback> void __enumerate(t_callback & p_callback) { __enum_items_recur<t_node>(m_root,p_callback); } //deprecated backwards compatibility method wrappers template<typename t_item> t_storage & add(const t_item & p_item) {return add_item(p_item);} template<typename t_item> t_storage & add_ex(const t_item & p_item,bool & p_isnew) {return add_item_ex(p_item,p_isnew);} template<typename t_item> const t_storage * find_ptr(t_item const & p_item) const {return find_item_ptr(p_item);} template<typename t_item> t_storage * find_ptr(t_item const & p_item) {return find_item_ptr(p_item);} template<typename t_item> bool exists(t_item const & p_item) const {return have_item(p_item);} void reset() {remove_all();} template<typename t_item> void remove(t_item const & p_item) {remove_item(p_item);} private: static t_node * __copy_recur(const t_node * p_source) { if (p_source == NULL) { return NULL; } else { pfc::ptrholder_t<t_node> newnode = new t_node(p_source->m_data); newnode->m_depth = p_source->m_depth; newnode->m_left = __copy_recur(p_source->m_left); newnode->m_right = __copy_recur(p_source->m_right); return newnode.detach(); } } void __copy(const t_self & p_other) { reset(); m_root = __copy_recur(p_other.m_root); } }; template<typename t_storage,typename t_comparator> class traits_t<avltree_t<t_storage,t_comparator> > : public traits_default_movable {}; } #endif //_AVLTREE_T_H_INCLUDED_ ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/bit_array.h ================================================ #ifndef _PFC_BIT_ARRAY_H_ #define _PFC_BIT_ARRAY_H_ class NOVTABLE bit_array { public: virtual bool get(t_size n) const = 0; virtual t_size find(bool val,t_size start,t_ssize count) const//can be overridden for improved speed; returns first occurance of val between start and start+count (excluding start+count), or start+count if not found; count may be negative if we're searching back { t_ssize d, todo, ptr = start; if (count==0) return start; else if (count<0) {d = -1; todo = -count;} else {d = 1; todo = count;} while(todo>0 && get(ptr)!=val) {ptr+=d;todo--;} return ptr; } inline bool operator[](t_size n) const {return get(n);} t_size calc_count(bool val,t_size start,t_size count,t_size count_max = ~0) const//counts number of vals for start<=n<start+count { t_size found = 0; t_size max = start+count; t_size ptr; for(ptr=find(val,start,count);found<count_max && ptr<max;ptr=find(val,ptr+1,max-ptr-1)) found++; return found; } inline t_size find_first(bool val,t_size start,t_size max) const {return find(val,start,max-start);} inline t_size find_next(bool val,t_size previous,t_size max) const {return find(val,previous+1,max-(previous+1));} //for(n = mask.find_first(true,0,m); n < m; n = mask.find_next(true,n,m) ) protected: bit_array() {} ~bit_array() {} }; class NOVTABLE bit_array_var : public bit_array { public: virtual void set(t_size n,bool val)=0; protected: bit_array_var() {} ~bit_array_var() {} }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/bit_array_impl.h ================================================ #ifndef _PFC_BIT_ARRAY_IMPL_H_ #define _PFC_BIT_ARRAY_IMPL_H_ template<class T> class bit_array_table_t : public bit_array { const T * data; t_size count; bool after; public: inline bit_array_table_t(const T * p_data,t_size p_count,bool p_after = false) : data(p_data), count(p_count), after(p_after) { } bool get(t_size n) const { if (n<count) return !!data[n]; else return after; } }; template<class T> class bit_array_var_table_t : public bit_array_var { T * data; t_size count; bool after; public: inline bit_array_var_table_t(T * p_data,t_size p_count,bool p_after = false) : data(p_data), count(p_count), after(p_after) { } bool get(t_size n) const { if (n<count) return !!data[n]; else return after; } void set(t_size n,bool val) { if (n<count) data[n] = !!val; } }; typedef bit_array_table_t<bool> bit_array_table; typedef bit_array_var_table_t<bool> bit_array_var_table; class bit_array_range : public bit_array { t_size begin,end; bool state; public: bit_array_range(t_size first,t_size count,bool p_state = true) : begin(first), end(first+count), state(p_state) {} bool get(t_size n) const { bool rv = n>=begin && n<end; if (!state) rv = !rv; return rv; } }; class bit_array_and : public bit_array { const bit_array & a1, & a2; public: bit_array_and(const bit_array & p_a1, const bit_array & p_a2) : a1(p_a1), a2(p_a2) {} bool get(t_size n) const {return a1.get(n) && a2.get(n);} }; class bit_array_or : public bit_array { const bit_array & a1, & a2; public: bit_array_or(const bit_array & p_a1, const bit_array & p_a2) : a1(p_a1), a2(p_a2) {} bool get(t_size n) const {return a1.get(n) || a2.get(n);} }; class bit_array_xor : public bit_array { const bit_array & a1, & a2; public: bit_array_xor(const bit_array & p_a1, const bit_array & p_a2) : a1(p_a1), a2(p_a2) {} bool get(t_size n) const { bool v1 = a1.get(n), v2 = a2.get(n); return (v1 && !v2) || (!v1 && v2); } }; class bit_array_not : public bit_array { const bit_array & a1; public: bit_array_not(const bit_array & p_a1) : a1(p_a1) {} bool get(t_size n) const {return !a1.get(n);} t_size find(bool val,t_size start,t_ssize count) const {return a1.find(!val,start,count);} }; class bit_array_true : public bit_array { public: bool get(t_size n) const {return true;} t_size find(bool val,t_size start,t_ssize count) const {return val ? start : start+count;} }; class bit_array_false : public bit_array { public: bool get(t_size n) const {return false;} t_size find(bool val,t_size start,t_ssize count) const {return val ? start+count : start;} }; class bit_array_val : public bit_array { bool val; public: bit_array_val(bool v) : val(v) {} bool get(t_size n) const {return val;} t_size find(bool p_val,t_size start,t_ssize count) const {return val==p_val ? start : start+count;} }; class bit_array_one : public bit_array { t_size val; public: bit_array_one(t_size p_val) : val(p_val) {} virtual bool get(t_size n) const {return n==val;} virtual t_size find(bool p_val,t_size start,t_ssize count) const { if (count==0) return start; else if (p_val) { if (count>0) return (val>=start && val<start+count) ? val : start+count; else return (val<=start && val>start+count) ? val : start+count; } else { if (start == val) return count>0 ? start+1 : start-1; else return start; } } }; class bit_array_bittable : public bit_array_var { pfc::array_t<t_uint8> m_data; t_size m_count; public: //helpers template<typename t_array> inline static bool g_get(const t_array & p_array,t_size idx) { return !! (p_array[idx>>3] & (1<<(idx&7))); } template<typename t_array> inline static void g_set(t_array & p_array,t_size idx,bool val) { unsigned char & dst = p_array[idx>>3]; unsigned char mask = 1<<(idx&7); dst = val ? dst|mask : dst&~mask; } inline static t_size g_estimate_size(t_size p_count) {return (p_count+7)>>3;} void resize(t_size p_count) { t_size old_bytes = g_estimate_size(m_count); m_count = p_count; t_size bytes = g_estimate_size(m_count); m_data.set_size(bytes); if (bytes > old_bytes) pfc::memset_null_t(m_data.get_ptr()+old_bytes,bytes-old_bytes); } bit_array_bittable(t_size p_count) : m_count(0) {resize(p_count);} void set(t_size n,bool val) { if (n<m_count) { g_set(m_data,n,val); } } bool get(t_size n) const { bool rv = false; if (n<m_count) { rv = g_get(m_data,n); } return rv; } }; class bit_array_order_changed : public bit_array { public: bit_array_order_changed(const t_size * p_order) : m_order(p_order) {} bool get(t_size n) const { return m_order[n] != n; } private: const t_size * m_order; }; #define for_each_bit_array(var,mask,val,start,count) for(var = mask.find(val,start,count);var<start+count;var=mask.find(val,var+1,count-(var+1-start))) #endif //_PFC_BIT_ARRAY_IMPL_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/bsearch.cpp ================================================ #include "pfc.h" /* class NOVTABLE bsearch_callback { public: virtual int test(t_size p_index) const = 0; }; */ namespace pfc { bool pfc::bsearch(t_size p_count, bsearch_callback const & p_callback,t_size & p_result) { return bsearch_inline_t(p_count,p_callback,p_result); } } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/bsearch.h ================================================ namespace pfc { class NOVTABLE bsearch_callback { public: virtual int test(t_size n) const = 0; }; PFC_DLL_EXPORT bool bsearch(t_size p_count, bsearch_callback const & p_callback,t_size & p_result); template<typename t_container,typename t_compare, typename t_param> class bsearch_callback_impl_simple_t : public bsearch_callback { public: int test(t_size p_index) const { return m_compare(m_container[p_index],m_param); } bsearch_callback_impl_simple_t(const t_container & p_container,t_compare p_compare,const t_param & p_param) : m_container(p_container), m_compare(p_compare), m_param(p_param) { } private: const t_container & m_container; t_compare m_compare; const t_param & m_param; }; template<typename t_container,typename t_compare, typename t_param,typename t_permutation> class bsearch_callback_impl_permutation_t : public bsearch_callback { public: int test(t_size p_index) const { return m_compare(m_container[m_permutation[p_index]],m_param); } bsearch_callback_impl_permutation_t(const t_container & p_container,t_compare p_compare,const t_param & p_param,const t_permutation & p_permutation) : m_container(p_container), m_compare(p_compare), m_param(p_param), m_permutation(p_permutation) { } private: const t_container & m_container; t_compare m_compare; const t_param & m_param; const t_permutation & m_permutation; }; template<typename t_container,typename t_compare, typename t_param> bool bsearch_t(t_size p_count,const t_container & p_container,t_compare p_compare,const t_param & p_param,t_size & p_index) { return bsearch( p_count, bsearch_callback_impl_simple_t<t_container,t_compare,t_param>(p_container,p_compare,p_param), p_index); } template<typename t_container,typename t_compare,typename t_param,typename t_permutation> bool bsearch_permutation_t(t_size p_count,const t_container & p_container,t_compare p_compare,const t_param & p_param,const t_permutation & p_permutation,t_size & p_index) { t_size index; if (bsearch( p_count, bsearch_callback_impl_permutation_t<t_container,t_compare,t_param,t_permutation>(p_container,p_compare,p_param,p_permutation), index)) { p_index = p_permutation[index]; return true; } else { return false; } } template<typename t_container,typename t_compare, typename t_param> bool bsearch_range_t(const t_size p_count,const t_container & p_container,t_compare p_compare,const t_param & p_param,t_size & p_range_base,t_size & p_range_count) { t_size probe; if (!bsearch( p_count, bsearch_callback_impl_simple_t<t_container,t_compare,t_param>(p_container,p_compare,p_param), probe)) return false; t_size base = probe, count = 1; while(base > 0 && p_compare(p_container[base-1],p_param) == 0) {base--; count++;} while(base + count < p_count && p_compare(p_container[base+count],p_param) == 0) {count++;} p_range_base = base; p_range_count = count; return true; } } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/bsearch_inline.h ================================================ namespace pfc { template<typename t_callback> inline bool bsearch_inline_t(t_size p_count, const t_callback & p_callback,t_size & p_result) { t_size max = p_count; t_size min = 0; t_size ptr; while(min<max) { ptr = min + ( (max-min) >> 1); int result = p_callback.test(ptr); if (result<0) min = ptr + 1; else if (result>0) max = ptr; else { p_result = ptr; return true; } } p_result = min; return false; } template<typename t_buffer,typename t_value> inline bool bsearch_simple_inline_t(const t_buffer & p_buffer,t_size p_count,t_value const & p_value,t_size & p_result) { t_size max = p_count; t_size min = 0; t_size ptr; while(min<max) { ptr = min + ( (max-min) >> 1); if (p_value > p_buffer[ptr]) min = ptr + 1; else if (p_value < p_buffer[ptr]) max = ptr; else { p_result = ptr; return true; } } p_result = min; return false; } } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/byte_order_helper.h ================================================ #ifndef _PFC_BYTE_ORDER_HELPER_ #define _PFC_BYTE_ORDER_HELPER_ namespace pfc { void byteswap_raw(void * p_buffer,t_size p_bytes); template<typename T> T byteswap_t(T p_source); template<> inline char byteswap_t<char>(char p_source) {return p_source;} template<> inline unsigned char byteswap_t<unsigned char>(unsigned char p_source) {return p_source;} #ifdef _MSC_VER//does this even help with performance/size? template<> inline wchar_t byteswap_t<wchar_t>(wchar_t p_source) {return _byteswap_ushort(p_source);} template<> inline short byteswap_t<short>(short p_source) {return _byteswap_ushort(p_source);} template<> inline unsigned short byteswap_t<unsigned short>(unsigned short p_source) {return _byteswap_ushort(p_source);} template<> inline int byteswap_t<int>(int p_source) {return _byteswap_ulong(p_source);} template<> inline unsigned int byteswap_t<unsigned int>(unsigned int p_source) {return _byteswap_ulong(p_source);} template<> inline long byteswap_t<long>(long p_source) {return _byteswap_ulong(p_source);} template<> inline unsigned long byteswap_t<unsigned long>(unsigned long p_source) {return _byteswap_ulong(p_source);} template<> inline long long byteswap_t<long long>(long long p_source) {return _byteswap_uint64(p_source);} template<> inline unsigned long long byteswap_t<unsigned long long>(unsigned long long p_source) {return _byteswap_uint64(p_source);} #else template<> inline t_uint16 byteswap_t<t_uint16>(t_uint16 p_source) {return ((p_source & 0xFF00) >> 8) | ((p_source & 0x00FF) << 8);} template<> inline t_int16 byteswap_t<t_int16>(t_int16 p_source) {return byteswap_t<t_uint16>(p_source);} template<> inline t_uint32 byteswap_t<t_uint32>(t_uint32 p_source) {return ((p_source & 0xFF000000) >> 24) | ((p_source & 0x00FF0000) >> 8) | ((p_source & 0x0000FF00) << 8) | ((p_source & 0x000000FF) << 24);} template<> inline t_int32 byteswap_t<t_int32>(t_int32 p_source) {return byteswap_t<t_uint32>(p_source);} template<> inline t_uint64 byteswap_t<t_uint64>(t_uint64 p_source) { //optimizeme byteswap_raw(&p_source,sizeof(p_source)); return p_source; } template<> inline t_int64 byteswap_t<t_int64>(t_int64 p_source) {return byteswap_t<t_uint64>(p_source);} template<> inline wchar_t byteswap_t<wchar_t>(wchar_t p_source) { return byteswap_t<pfc::sized_int_t<sizeof(wchar_t)>::t_unsigned>(p_source); } #endif template<> inline float byteswap_t<float>(float p_source) { float ret; *(t_uint32*) &ret = byteswap_t(*(const t_uint32*)&p_source ); return ret; } template<> inline double byteswap_t<double>(double p_source) { double ret; *(t_uint64*) &ret = byteswap_t(*(const t_uint64*)&p_source ); return ret; } //blargh at GUID byteswap issue template<> inline GUID byteswap_t<GUID>(GUID p_guid) { GUID ret; ret.Data1 = pfc::byteswap_t(p_guid.Data1); ret.Data2 = pfc::byteswap_t(p_guid.Data2); ret.Data3 = pfc::byteswap_t(p_guid.Data3); ret.Data4[0] = p_guid.Data4[0]; ret.Data4[1] = p_guid.Data4[1]; ret.Data4[2] = p_guid.Data4[2]; ret.Data4[3] = p_guid.Data4[3]; ret.Data4[4] = p_guid.Data4[4]; ret.Data4[5] = p_guid.Data4[5]; ret.Data4[6] = p_guid.Data4[6]; ret.Data4[7] = p_guid.Data4[7]; return ret; } }; #ifdef _MSC_VER #if defined(_M_IX86) || defined(_M_X64) #define PFC_BYTE_ORDER_IS_BIG_ENDIAN 0 #endif #else//_MSC_VER #include <endian.h> #if __BYTE_ORDER == __LITTLE_ENDIAN #define PFC_BYTE_ORDER_IS_BIG_ENDIAN 1 #else #define PFC_BYTE_ORDER_IS_BIG_ENDIAN 0 #endif #endif//_MSC_VER #ifdef PFC_BYTE_ORDER_IS_BIG_ENDIAN #define PFC_BYTE_ORDER_IS_LITTLE_ENDIAN (!(PFC_BYTE_ORDER_IS_BIG_ENDIAN)) #else #error please update byte order #defines #endif namespace pfc { enum { byte_order_is_big_endian = PFC_BYTE_ORDER_IS_BIG_ENDIAN, byte_order_is_little_endian = PFC_BYTE_ORDER_IS_LITTLE_ENDIAN, }; template<typename T> T byteswap_if_be_t(T p_param) {return byte_order_is_big_endian ? byteswap_t(p_param) : p_param;} template<typename T> T byteswap_if_le_t(T p_param) {return byte_order_is_little_endian ? byteswap_t(p_param) : p_param;} } namespace byte_order { #if PFC_BYTE_ORDER_IS_BIG_ENDIAN//big endian template<typename T> inline void order_native_to_le_t(T& param) {param = pfc::byteswap_t(param);} template<typename T> inline void order_native_to_be_t(T& param) {} template<typename T> inline void order_le_to_native_t(T& param) {param = pfc::byteswap_t(param);} template<typename T> inline void order_be_to_native_t(T& param) {} #else//little endian template<typename T> inline void order_native_to_le_t(T& param) {} template<typename T> inline void order_native_to_be_t(T& param) {param = pfc::byteswap_t(param);} template<typename T> inline void order_le_to_native_t(T& param) {} template<typename T> inline void order_be_to_native_t(T& param) {param = pfc::byteswap_t(param);} #endif }; namespace pfc { template<typename TInt,unsigned width,bool IsBigEndian> class __EncodeIntHelper { public: inline static void Run(TInt p_value,t_uint8 * p_out) { *p_out = (t_uint8)(p_value); __EncodeIntHelper<TInt,width-1,IsBigEndian>::Run(p_value >> 8,p_out + (IsBigEndian ? -1 : 1)); } }; template<typename TInt,bool IsBigEndian> class __EncodeIntHelper<TInt,1,IsBigEndian> { public: inline static void Run(TInt p_value,t_uint8* p_out) { *p_out = (t_uint8)(p_value); } }; template<typename TInt,bool IsBigEndian> class __EncodeIntHelper<TInt,0,IsBigEndian> { public: inline static void Run(TInt,t_uint8*) {} }; template<typename TInt> inline void encode_little_endian(t_uint8 * p_buffer,TInt p_value) { __EncodeIntHelper<TInt,sizeof(TInt),false>::Run(p_value,p_buffer); } template<typename TInt> inline void encode_big_endian(t_uint8 * p_buffer,TInt p_value) { __EncodeIntHelper<TInt,sizeof(TInt),true>::Run(p_value,p_buffer + (sizeof(TInt) - 1)); } template<typename TInt,unsigned width,bool IsBigEndian> class __DecodeIntHelper { public: inline static TInt Run(const t_uint8 * p_in) { return (__DecodeIntHelper<TInt,width-1,IsBigEndian>::Run(p_in + (IsBigEndian ? -1 : 1)) << 8) + *p_in; } }; template<typename TInt,bool IsBigEndian> class __DecodeIntHelper<TInt,1,IsBigEndian> { public: inline static TInt Run(const t_uint8* p_in) {return *p_in;} }; template<typename TInt,bool IsBigEndian> class __DecodeIntHelper<TInt,0,IsBigEndian> { public: inline static TInt Run(const t_uint8*) {return 0;} }; template<typename TInt> inline void decode_little_endian(TInt & p_out,const t_uint8 * p_buffer) { p_out = __DecodeIntHelper<TInt,sizeof(TInt),false>::Run(p_buffer); } template<typename TInt> inline void decode_big_endian(TInt & p_out,const t_uint8 * p_buffer) { p_out = __DecodeIntHelper<TInt,sizeof(TInt),true>::Run(p_buffer + (sizeof(TInt) - 1)); } template<typename TInt> inline TInt decode_little_endian(const t_uint8 * p_buffer) { TInt temp; decode_little_endian(temp,p_buffer); return temp; } template<typename TInt> inline TInt decode_big_endian(const t_uint8 * p_buffer) { TInt temp; decode_big_endian(temp,p_buffer); return temp; } } #endif ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/chainlist.h ================================================ #ifndef _PFC_CHAINLIST_H_ #define _PFC_CHAINLIST_H_ namespace pfc { template<typename T> class chain_list_simple_t { private: typedef chain_list_simple_t<T> t_self; public: chain_list_simple_t() : m_first(0), m_last(0), m_count(0) {} chain_list_simple_t(const t_self & p_source) : m_first(0), m_last(0), m_count(0) { t_iter iter; for(iter = p_source.first();iter;iter = p_source.next(iter)) insert_last(p_source.get_item(iter)); } t_self const & operator=(t_self const & p_source) { remove_all(); for(t_iter iter = p_source.first();iter;iter = p_source.next(iter)) insert_last(p_source.get_item(iter)); return *this; } typedef void* t_iter; inline t_size get_count() const {return m_count;} inline t_iter first() const {return reinterpret_cast<t_iter>(m_first);} inline t_iter last () const {return reinterpret_cast<t_iter>(m_last );} inline t_iter next(t_iter param) {return reinterpret_cast<elem*>(param)->m_next;} inline t_iter prev(t_iter param) {return reinterpret_cast<elem*>(param)->m_prev;} inline T & get_item(t_iter param) {return reinterpret_cast<elem*>(param)->m_data;} inline const T & get_item(t_iter param) const {return reinterpret_cast<elem*>(param)->m_data;} inline t_iter insert_first() {return _insert_first(new elem());} inline t_iter insert_first(const T& param) {return _insert_first(new elem(param));} inline t_iter insert_last() {return _insert_last(new elem());} inline t_iter insert_last(const T& param) {return _insert_last(new elem(param));} inline t_iter insert_before(t_iter p_iter) {return _insert_before(p_iter,new elem());} inline t_iter insert_before(t_iter p_iter,const T& param) {return _insert_before(p_iter,new elem(param));} inline t_iter insert_after(t_iter p_iter) {return _insert_after(p_iter,new elem());} inline t_iter insert_after(t_iter p_iter,const T& param) {return _insert_after(p_iter,new elem(param));} inline void remove(t_iter p_iter) {elem * item = reinterpret_cast<elem*>(p_iter); _unlink(item); delete item;} inline void unlink(t_iter p_iter) {_unlink(reinterpret_cast<elem*>(p_iter));} inline void link_first(t_iter p_iter) {_insert_first(reinterpret_cast<elem*>(p_iter));} inline void link_last(t_iter p_iter) {_insert_last(reinterpret_cast<elem*>(p_iter));} inline void link_before(t_iter p_next,t_iter p_iter) {_insert_before(p_next,reinterpret_cast<elem*>(p_iter));} inline void link_after(t_iter p_prev,t_iter p_iter) {_insert_after(p_prev,reinterpret_cast<elem*>(p_iter));} void remove_all() {while(m_count > 0) remove(first());} ~chain_list_simple_t() {remove_all();} private: struct elem; t_iter _insert_first(elem * p_elem) { p_elem->m_prev = 0; p_elem->m_next = m_first; (m_first ? m_first->m_prev : m_last) = p_elem; m_first = p_elem; m_count++; return reinterpret_cast<t_iter>(p_elem); } t_iter _insert_last(elem * p_elem) { p_elem->m_prev = m_last; p_elem->m_next = 0; (m_last ? m_last->m_next : m_first) = p_elem; m_last = p_elem; m_count++; return reinterpret_cast<t_iter>(p_elem); } t_iter _insert_before(t_iter p_iter,elem * p_elem) { elem * p_next = reinterpret_cast<elem*>(p_iter); p_elem->m_next = p_next; p_elem->m_prev = p_next->m_prev; (p_next->m_prev ? p_next->m_prev->m_next : m_first) = p_elem; p_next->m_prev = p_elem; m_count++; return reinterpret_cast<t_iter>(p_elem); } t_iter _insert_after(t_iter p_iter,elem * p_elem) { elem * p_prev = reinterpret_cast<elem*>(p_iter); p_elem->m_next = p_prev->m_next; p_elem->m_prev = p_prev; (p_prev->m_next ? p_prev->m_next->m_prev : m_last) = p_elem; p_prev->m_next = p_elem; m_count++; return reinterpret_cast<t_iter>(p_elem); } void _unlink(elem * p_elem) { (p_elem->m_prev ? p_elem->m_prev->m_next : m_first) = p_elem->m_next; (p_elem->m_next ? p_elem->m_next->m_prev : m_last) = p_elem->m_prev; p_elem->m_next = p_elem->m_prev = 0; m_count--; } struct elem { elem() : m_prev(0), m_next(0) {} elem(const elem & p_src) {*this = p_src;} elem(const T& p_src) : m_data(p_src), m_prev(NULL), m_next(NULL) {} T m_data; elem * m_prev, * m_next; }; elem * m_first, * m_last; t_size m_count; }; template<typename t_comparator = comparator_default> class comparator_chainlist { public: template<typename t_list1, typename t_list2> static int compare(const t_list1 & p_list1, const t_list2 p_list2) { typename t_list1::const_iterator iter1 = p_list1.first(); typename t_list2::const_iterator iter2 = p_list2.first(); for(;;) { if (iter1.is_empty() && iter2.is_empty()) return 0; else if (iter1.is_empty()) return -1; else if (iter2.is_empty()) return 1; else { int state = t_comparator::compare(*iter1,*iter2); if (state != 0) return state; } ++iter1; ++iter2; } } }; template<typename T> class chain_list_t { public: class const_iterator; class iterator; friend class const_iterator; friend class iterator; private: class elem; typedef chain_list_t<T> t_self; public: class const_iterator { public: friend class chain_list_t<T>; inline const T & operator*() const {return m_ptr->m_data;} inline const T * operator->() const {return &m_ptr->m_data;} inline const_iterator() : m_owner(0), m_ptr(0) {} inline const_iterator(const const_iterator & p_source) : m_owner(0), m_ptr(0) {*this = p_source;} inline ~const_iterator() {if (m_owner) m_owner->_remove_iterator(this);} inline const_iterator(const chain_list_t<T> * p_owner,elem * p_ptr) : m_owner(p_owner), m_ptr(p_ptr) { if (m_owner != NULL) m_owner->_add_iterator(this); } inline const const_iterator & operator=(const const_iterator & p_source) { if (m_owner != NULL) m_owner->_remove_iterator(this); m_owner = p_source.m_owner; m_ptr = p_source.m_ptr; if (m_owner != NULL) m_owner->_add_iterator(this); return *this; } inline void next() {if (m_ptr != NULL) m_ptr = m_ptr->m_next;} inline void prev() {if (m_ptr != NULL) m_ptr = m_ptr->m_prev;} inline const const_iterator & operator++() {next();return *this;} inline const_iterator operator++(int) {const_iterator old = *this; next(); return old;} inline const const_iterator & operator--() {prev();return *this;} inline const_iterator operator--(int) {const_iterator old = *this; prev(); return old;} inline bool is_valid() const {return m_ptr != NULL;} inline bool is_empty() const {return m_ptr == NULL;} inline bool operator==(const const_iterator & p_iter) const {return m_ptr == p_iter.m_ptr;} inline bool operator!=(const const_iterator & p_iter) const {return m_ptr != p_iter.m_ptr;} protected: const chain_list_t<T> * m_owner; elem * m_ptr; void orphan() {m_ptr = 0;m_owner=0;} }; class iterator : public const_iterator { public: inline iterator() {} inline iterator(const iterator & p_source) : const_iterator(p_source) {} inline iterator(const chain_list_t<T> * p_owner,elem * p_ptr) : const_iterator(p_owner,p_ptr) {} inline const iterator & operator=(const iterator & p_source) { *(const_iterator*)this = *(const const_iterator*) & p_source; return *this;} inline T & operator*() const {return this->m_ptr->m_data;} inline T * operator->() const {return &this->m_ptr->m_data;} inline bool operator==(const iterator & p_iter) const {return this->m_ptr == p_iter.m_ptr;} inline bool operator!=(const iterator & p_iter) const {return this->m_ptr != p_iter.m_ptr;} inline const iterator & operator++() {this->next();return *this;} inline iterator operator++(int) {iterator old = *this; this->next(); return old;} inline const iterator & operator--() {this->prev();return *this;} inline iterator operator--(int) {iterator old = *this; this->prev(); return old;} }; inline iterator first() {return iterator(this,m_first);} inline iterator last() {return iterator(this,m_last);} inline const_iterator first() const {return const_iterator(this,m_first);} inline const_iterator last() const {return const_iterator(this,m_last);} inline t_size get_count() const {return m_count;} inline chain_list_t() {_init();} inline chain_list_t(const t_self & p_other) { _init(); *this = p_other; } template<typename t_other> inline chain_list_t(const t_other & p_other) { _init(); *this = p_other; } inline ~chain_list_t() { remove_all(); } inline const t_self & operator=(const t_self & p_other) { copy_list_enumerated(*this,p_other); return *this; } template<typename t_other> inline const t_self & operator=(const t_other & p_other) { copy_list_enumerated(*this,p_other); return *this; } template<typename t_insert> iterator insert_after(iterator const & p_iter,const t_insert & p_item) { dynamic_assert(p_iter.is_valid() && p_iter.m_owner == this); elem * new_elem = new elem(p_item,0); __link_after(p_iter,new_elem); return iterator(this,new_elem); } template<typename t_insert> iterator insert_before(iterator const & p_iter,const t_insert & p_item) { dynamic_assert(p_iter.is_valid() && p_iter.m_owner == this); elem * new_elem = new elem(p_item,0); __link_before(p_iter,new_elem); return iterator(this,new_elem); } template<typename t_insert> iterator insert_last(const t_insert & p_item) { elem * new_elem = new elem(p_item,0); __link_last(new_elem); return iterator(this,new_elem); } template<typename t_insert> iterator insert_first(const t_insert & p_item) { elem * new_elem = new elem(p_item,0); __link_first(new_elem); return iterator(this,new_elem); } iterator insert_after(iterator const & p_iter) { dynamic_assert(p_iter.is_valid() && p_iter.m_owner == this); elem * new_elem = new elem(); __link_after(p_iter,new_elem); return iterator(this,new_elem); } iterator insert_before(iterator const & p_iter) { dynamic_assert(p_iter.is_valid() && p_iter.m_owner == this); elem * new_elem = new elem(); __link_before(p_iter,new_elem); return iterator(this,new_elem); } iterator insert_last() { elem * new_elem = new elem(); __link_last(new_elem); return iterator(this,new_elem); } iterator insert_first() { elem * new_elem = new elem(); __link_first(new_elem); return iterator(this,new_elem); } template<typename t_insert> void insert_last_multi(const chain_list_t<t_insert> & p_list) { typename chain_list_t<t_insert>::const_iterator iter; for(iter = p_list.first(); iter.is_valid(); ++iter) { __link_last(new elem(*iter,0)); } } template<typename t_insert> void insert_first_multi(const chain_list_t<t_insert> & p_list) { typename chain_list_t<t_insert>::const_iterator iter; for(iter = p_list.last(); iter.is_valid(); --iter) { __link_first(new elem(*iter,0)); } } void remove_single(iterator const & p_iter); void remove_range(iterator const & p_from,iterator const & p_to); void remove_all(); inline iterator find_forward(iterator const & p_iter,const T & p_item) const {return iterator(this,_find_forward(p_iter.m_ptr,p_item));} inline const_iterator find_forward(const_iterator const & p_iter,const T & p_item) const {return const_iterator(this,_find_forward(p_iter.m_ptr,p_item));} inline iterator find_back(iterator const & p_iter,const T & p_item) const {return iterator(this,_find_back(p_iter.m_ptr,p_item));} inline const_iterator find_back(const_iterator const & p_iter,const T & p_item) const {return const_iterator(this,_find_back(p_iter.m_ptr,p_item));} inline iterator find_first(const T & p_item) {return find_forward(first(),p_item);} inline const_iterator find_first(const T & p_item) const {return find_forward(first(),p_item);} inline iterator find_next(iterator p_iter,const T & p_item) const {p_iter++; return find_forward(p_iter,p_item);} inline const_iterator find_next(const_iterator p_iter,const T & p_item) const {p_iter++; return find_forward(p_iter,p_item);} inline iterator find_last(const T & p_item) {return find_back(last(),p_item);} inline const_iterator find_last(const T & p_item) const {return find_back(last(),p_item);} inline iterator find_prev(iterator p_iter,const T & p_item) const {p_iter++; return find_back(p_iter,p_item);} inline const_iterator find_prev(const_iterator p_iter,const T & p_item) const {p_iter++; return find_back(p_iter,p_item);} inline bool find_first_and_remove(const T & p_item) { iterator iter = find_first(p_item); if (iter.is_valid()) {remove_single(iter); return true;} else return false; } const_iterator by_index(unsigned p_idx) const { const_iterator iter = first(); while(iter.is_valid() && p_idx) { ++iter; p_idx--; } return iter; } iterator by_index(unsigned p_idx) { iterator iter = first(); while(iter.is_valid() && p_idx) { ++iter; p_idx--; } return iter; } bool operator==(const t_self & p_other) const {return comparator_chainlist::compare(*this,p_other) == 0;} bool operator!=(const t_self & p_other) const {return comparator_chainlist::compare(*this,p_other) != 0;} bool operator<(const t_self & p_other) const {return comparator_chainlist::compare(*this,p_ohter) < 0;} bool operator>(const t_self & p_other) const {return comparator_chainlist::compare(*this,p_ohter) > 0;} bool operator<=(const t_self & p_other) const {return comparator_chainlist::compare(*this,p_ohter) <= 0;} bool operator>=(const t_self & p_other) const {return comparator_chainlist::compare(*this,p_ohter) >= 0;} template<typename t_callback> void enumerate(t_callback & p_callback) const { __enumerate_chain<const elem>(m_first,p_callback); } template<typename t_callback> void enumerate(t_callback & p_callback) { __enumerate_chain<elem>(m_first,p_callback); } template<typename t_value> iterator set_single(const t_value & p_value) { remove_all(); return insert_last(p_value); } template<typename t_value> iterator set_single() { remove_all(); return insert_last(); } template<typename t_value> void add_item(const t_value & p_value) { __link_last(new elem(p_value,0)); } private: struct elem { elem() : m_prev(NULL), m_next(NULL) {} template<typename t_param> elem(const t_param & p_source,int) : m_data(p_source), m_prev(0), m_next(0) {} T m_data; elem * m_prev, * m_next; }; void __link_after(iterator const & p_iter,elem * new_elem) { elem * ptr = p_iter.m_ptr; new_elem->m_prev = ptr; new_elem->m_next = ptr->m_next; (ptr->m_next ? ptr->m_next->m_prev : m_last) = new_elem; ptr->m_next = new_elem; m_count++; } void __link_before(iterator const & p_iter,elem * new_elem) { elem * ptr = p_iter.m_ptr; new_elem->m_next = ptr; new_elem->m_prev = ptr->m_prev; (ptr->m_prev ? ptr->m_prev->m_next : m_first) = new_elem; ptr->m_prev = new_elem; m_count++; } void __link_last(elem * new_elem) { new_elem->m_prev = m_last; (m_last ? m_last->m_next : m_first) = new_elem; m_last = new_elem; m_count++; } void __link_first(elem * new_elem) { new_elem->m_next = m_first; (m_first ? m_first->m_prev : m_last) = new_elem; m_first = new_elem; m_count++; } template<typename t_elemwalk,typename t_callback> static void __enumerate_chain(t_elemwalk * p_elem,t_callback & p_callback) { t_elemwalk * walk = p_elem; while(walk != NULL) { p_callback(walk->m_data); walk = walk->m_next; } } elem * m_first, * m_last; t_size m_count; typedef typename chain_list_simple_t<const_iterator*>::t_iter t_iter_entry; mutable chain_list_simple_t<const_iterator*> m_iterators,m_iterators_recycle; enum {iterators_recycle_max = 32}; elem * _find_forward(elem * p_base,const T & p_item) const { elem * walk = p_base; while(walk) { if (walk->m_data == p_item) return walk; walk = walk->m_next; } return 0; } elem * _find_back(elem * p_base,const T & p_item) const { elem * walk = p_base; while(walk) { if (walk->m_data == p_item) return walk; walk = walk->m_prev; } return 0; } void _init() { m_first = m_last = 0; m_count = 0; } void _delete_iter_entry(t_iter_entry p_iter) const { if (m_iterators_recycle.get_count() < iterators_recycle_max) { m_iterators.unlink(p_iter); m_iterators_recycle.link_last(p_iter); } else { m_iterators.remove(p_iter); } } void _add_iterator(const_iterator * p_iter) const { if (m_iterators_recycle.get_count() > 0) { t_iter_entry temp = m_iterators_recycle.first(); m_iterators_recycle.unlink(temp); m_iterators.link_last(temp); m_iterators.get_item(temp) = p_iter; } else { m_iterators.insert_last(p_iter); } } void _remove_iterator(const_iterator * p_iter) const { t_iter_entry entry = m_iterators.first(); while(entry) { if (m_iterators.get_item(entry) == p_iter) { _delete_iter_entry(entry); break; } entry = m_iterators.next(entry); } } void _update_iterators_on_removed(const elem * ptr) { t_iter_entry entry = m_iterators.first(); while(entry) { t_iter_entry entry_next = m_iterators.next(entry); const_iterator * iter = m_iterators.get_item(entry); if (iter->m_ptr == ptr) { iter->orphan(); _delete_iter_entry(entry); } entry = entry_next; } } };//chain_list_t template<typename T> void chain_list_t<T>::remove_single(iterator const & p_iter) { elem * ptr = p_iter.m_ptr; (ptr->m_prev ? ptr->m_prev->m_next : m_first) = ptr->m_next; (ptr->m_next ? ptr->m_next->m_prev : m_last) = ptr->m_prev; m_count--; _update_iterators_on_removed(ptr); delete ptr; } template<typename T> void chain_list_t<T>::remove_range(iterator const & p_from,iterator const & p_to) { if (p_from.is_valid() && p_to.is_valid()) { bug_check_assert(p_from.m_owner == this && p_to.m_owner == this); iterator walk = p_from; while(walk != p_to) { iterator axeme = walk; walk.next(); bug_check_assert(walk.is_valid()); remove_single(axeme); } remove_single(walk); } } template<typename T> void chain_list_t<T>::remove_all() {remove_range(first(),last());} } #endif //_PFC_CHAINLIST_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/com_ptr_t.h ================================================ namespace pfc { //this is windows-only //update me to new conventions template<class T> class com_ptr_t { public: inline com_ptr_t() : m_ptr(0) {} inline com_ptr_t(T* p_ptr) : m_ptr(p_ptr) {if (m_ptr) m_ptr->AddRef();} inline com_ptr_t(const com_ptr_t<T> & p_source) : m_ptr(p_source.m_ptr) {if (m_ptr) m_ptr->AddRef();} inline ~com_ptr_t() {if (m_ptr) m_ptr->Release();} inline void copy(T * p_ptr) { if (m_ptr) m_ptr->Release(); m_ptr = p_ptr; if (m_ptr) m_ptr->AddRef(); } inline void copy(const com_ptr_t<T> & p_source) {copy(p_source.m_ptr);} inline void set(T * p_ptr)//should not be used ! temporary ! { if (m_ptr) m_ptr->Release(); m_ptr = p_ptr; } inline const com_ptr_t<T> & operator=(const com_ptr_t<T> & p_source) {copy(p_source); return *this;} inline const com_ptr_t<T> & operator=(T * p_ptr) {copy(p_ptr); return *this;} inline void release() { if (m_ptr) m_ptr->Release(); m_ptr = 0; } inline T* operator->() const {assert(m_ptr);return m_ptr;} inline T* get_ptr() const {return m_ptr;} inline T* duplicate_ptr() const//should not be used ! temporary ! { if (m_ptr) m_ptr->AddRef(); return m_ptr; } inline T* duplicate_ptr_release() { T* ret = m_ptr; m_ptr = 0; return ret; } inline bool is_valid() const {return m_ptr != 0;} inline bool is_empty() const {return m_ptr == 0;} inline bool operator==(const com_ptr_t<T> & p_item) const {return m_ptr == p_item.m_ptr;} inline bool operator!=(const com_ptr_t<T> & p_item) const {return m_ptr != p_item.m_ptr;} inline bool operator>(const com_ptr_t<T> & p_item) const {return m_ptr > p_item.m_ptr;} inline bool operator<(const com_ptr_t<T> & p_item) const {return m_ptr < p_item.m_ptr;} inline static void g_swap(com_ptr_t<T> & item1, com_ptr_t<T> & item2) { pfc::swap_t(item1.m_ptr,item2.m_ptr); } inline T** receive_ptr() {release();return &m_ptr;} private: T* m_ptr; }; } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/guid.cpp ================================================ #include "pfc.h" namespace pfc { /* 6B29FC40-CA47-1067-B31D-00DD010662DA . typedef struct _GUID { // size is 16 DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[8]; } GUID; // {B296CF59-4D51-466f-8E0B-E57D3F91D908} static const GUID <<name>> = { 0xb296cf59, 0x4d51, 0x466f, { 0x8e, 0xb, 0xe5, 0x7d, 0x3f, 0x91, 0xd9, 0x8 } }; */ unsigned GUID_from_text::read_hex(char c) { if (c>='0' && c<='9') return (unsigned)c - '0'; else if (c>='a' && c<='f') return 0xa + (unsigned)c - 'a'; else if (c>='A' && c<='F') return 0xa + (unsigned)c - 'A'; else return 0; } unsigned GUID_from_text::read_byte(const char * ptr) { return (read_hex(ptr[0])<<4) | read_hex(ptr[1]); } unsigned GUID_from_text::read_word(const char * ptr) { return (read_byte(ptr)<<8) | read_byte(ptr+2); } unsigned GUID_from_text::read_dword(const char * ptr) { return (read_word(ptr)<<16) | read_word(ptr+4); } void GUID_from_text::read_bytes(BYTE * out,unsigned num,const char * ptr) { for(;num;num--) { *out = read_byte(ptr); out++;ptr+=2; } } GUID_from_text::GUID_from_text(const char * text) { if (*text=='{') text++; const char * max; { const char * t = strchr(text,'}'); if (t) max = t; else max = text + strlen(text); } (GUID)*this = pfc::guid_null; do { if (text+8>max) break; Data1 = read_dword(text); text += 8; while(*text=='-') text++; if (text+4>max) break; Data2 = read_word(text); text += 4; while(*text=='-') text++; if (text+4>max) break; Data3 = read_word(text); text += 4; while(*text=='-') text++; if (text+4>max) break; read_bytes(Data4,2,text); text += 4; while(*text=='-') text++; if (text+12>max) break; read_bytes(Data4+2,6,text); } while(false); } static inline char print_hex_digit(unsigned val) { static const char table[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; assert((val & ~0xF) == 0); return table[val]; } static void print_hex(unsigned val,char * &out,unsigned bytes) { unsigned n; for(n=0;n<bytes;n++) { unsigned char c = (unsigned char)((val >> ((bytes - 1 - n) << 3)) & 0xFF); *(out++) = print_hex_digit( c >> 4 ); *(out++) = print_hex_digit( c & 0xF ); } *out = 0; } print_guid::print_guid(const GUID & p_guid) { char * out = m_data; print_hex(p_guid.Data1,out,4); *(out++) = '-'; print_hex(p_guid.Data2,out,2); *(out++) = '-'; print_hex(p_guid.Data3,out,2); *(out++) = '-'; print_hex(p_guid.Data4[0],out,1); print_hex(p_guid.Data4[1],out,1); *(out++) = '-'; print_hex(p_guid.Data4[2],out,1); print_hex(p_guid.Data4[3],out,1); print_hex(p_guid.Data4[4],out,1); print_hex(p_guid.Data4[5],out,1); print_hex(p_guid.Data4[6],out,1); print_hex(p_guid.Data4[7],out,1); *out = 0; } PFC_DLL_EXPORT void print_hex_raw(const void * buffer,unsigned bytes,char * p_out) { char * out = p_out; const unsigned char * in = (const unsigned char *) buffer; unsigned n; for(n=0;n<bytes;n++) print_hex(in[n],out,1); } } const GUID pfc::guid_null = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } }; ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/guid.h ================================================ #ifndef _PFC_GUID_H_ #define _PFC_GUID_H_ namespace pfc { class GUID_from_text : public GUID { unsigned read_hex(char c); unsigned read_byte(const char * ptr); unsigned read_word(const char * ptr); unsigned read_dword(const char * ptr); void read_bytes(unsigned char * out,unsigned num,const char * ptr); public: GUID_from_text(const char * text); }; class print_guid { public: print_guid(const GUID & p_guid); inline operator const char * () const {return m_data;} inline const char * get_ptr() {return m_data;} private: char m_data[64]; }; inline int guid_compare(const GUID & g1,const GUID & g2) {return memcmp(&g1,&g2,sizeof(GUID));} inline bool guid_equal(const GUID & g1,const GUID & g2) {return (g1 == g2) ? true : false;} template<> inline int compare_t<GUID,GUID>(const GUID & p_item1,const GUID & p_item2) {return guid_compare(p_item1,p_item2);} extern const GUID guid_null; PFC_DLL_EXPORT void print_hex_raw(const void * buffer,unsigned bytes,char * p_out); } #endif ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/instance_tracker.h ================================================ namespace pfc { template<typename t_object> class instance_tracker_server_t { public: void add(t_object * p_object) { m_list.add_item(p_object); } void remove(t_object * p_object) { m_list.remove_item(p_object); } t_size get_count() const {return m_list.get_count();} t_object * get_item(t_size p_index) {return m_list[p_index];} t_object * operator[](t_size p_index) {return m_list[p_index];} private: ptr_list_hybrid_t<t_object,4> m_list; }; template<typename t_object,instance_tracker_server_t<t_object> & p_server> class instance_tracker_client_t { public: instance_tracker_client_t(t_object* p_ptr) : m_ptr(NULL), m_added(false) {initialize(p_ptr);} instance_tracker_client_t() : m_ptr(NULL), m_added(false) {} void initialize(t_object * p_ptr) { uninitialize(); p_server.add(p_ptr); m_ptr = p_ptr; m_added = true; } void uninitialize() { if (m_added) { p_server.remove(m_ptr); m_ptr = NULL; m_added = false; } } ~instance_tracker_client_t() { uninitialize(); } private: bool m_added; t_object * m_ptr; }; } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/int_types.h ================================================ #if !defined(_MSC_VER) && !defined(_EVC_VER) #include <stdint.h> typedef int64_t t_int64; typedef uint64_t t_uint64; typedef int32_t t_int32; typedef uint32_t t_uint32; typedef int16_t t_int16; typedef uint16_t t_uint16; typedef int8_t t_int8; typedef uint8_t t_uint8; #else typedef __int64 t_int64; typedef unsigned __int64 t_uint64; typedef __int32 t_int32; typedef unsigned __int32 t_uint32; typedef __int16 t_int16; typedef unsigned __int16 t_uint16; typedef __int8 t_int8; typedef unsigned __int8 t_uint8; #endif typedef int t_int; typedef unsigned int t_uint; typedef float t_float32; typedef double t_float64; #if defined(_WIN32) && !defined(_WIN64) #define __PFC_WP64 __w64 #else #define __PFC_WP64 #endif namespace pfc { template<unsigned t_bytes> class sized_int_t; template<> class sized_int_t<1> { public: typedef t_uint8 t_unsigned; typedef t_int8 t_signed; }; template<> class sized_int_t<2> { public: typedef t_uint16 t_unsigned; typedef t_int16 t_signed; }; template<> class sized_int_t<4> { public: typedef t_uint32 t_unsigned; typedef t_int32 t_signed; }; template<> class sized_int_t<8> { public: typedef t_uint64 t_unsigned; typedef t_int64 t_signed; }; } typedef pfc::sized_int_t<sizeof(void*)>::t_unsigned __PFC_WP64 t_size; typedef pfc::sized_int_t<sizeof(void*)>::t_signed __PFC_WP64 t_ssize; #define infinite (~0) const t_uint16 infinite16 = (t_uint16)(~0); const t_uint32 infinite32 = (t_uint32)(~0); const t_uint64 infinite64 = (t_uint64)(~0); const t_size infinite_size = (t_size)(~0); #if defined(_WIN32) && !defined(_WIN64) inline t_size MulDiv_Size(t_size x,t_size y,t_size z) {return (t_size) ( ((t_uint64)x * (t_uint64)y) / (t_uint64)z );} #elif defined(_WIN64) inline t_size MulDiv_Size(t_size x,t_size y,t_size z) {return (x*y)/z;} #else #error portme #endif namespace pfc { template<typename T> class int_specs_t; template<typename T> class int_specs_signed_t { public: inline static T get_min() {return ((T)1<<(sizeof(T)*8-1));} inline static T get_max() {return ~((T)1<<(sizeof(T)*8-1));} enum {is_signed = true}; }; template<typename T> class int_specs_unsigned_t { public: inline static T get_min() {return (T)0;} inline static T get_max() {return (T)~0;} enum {is_signed = false}; }; template<> class int_specs_t<char> : public int_specs_signed_t<char> {}; template<> class int_specs_t<unsigned char> : public int_specs_unsigned_t<unsigned char> {}; template<> class int_specs_t<short> : public int_specs_signed_t<short> {}; template<> class int_specs_t<unsigned short> : public int_specs_unsigned_t<unsigned short> {}; template<> class int_specs_t<int> : public int_specs_signed_t<int> {}; template<> class int_specs_t<unsigned int> : public int_specs_unsigned_t<unsigned int> {}; template<> class int_specs_t<long> : public int_specs_signed_t<long> {}; template<> class int_specs_t<unsigned long> : public int_specs_unsigned_t<unsigned long> {}; template<> class int_specs_t<long long> : public int_specs_signed_t<long long> {}; template<> class int_specs_t<unsigned long long> : public int_specs_unsigned_t<unsigned long long> {}; template<> class int_specs_t<wchar_t> : public int_specs_unsigned_t<wchar_t> {}; }; ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/license.txt ================================================ Copyright (c) 2001-2006, Peter Pawlowski All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/list.h ================================================ #ifndef _PFC_LIST_H_ #define _PFC_LIST_H_ namespace pfc { template<typename T> class NOVTABLE list_base_const_t { private: typedef list_base_const_t<T> t_self; public: virtual t_size get_count() const = 0; virtual void get_item_ex(T& p_out, t_size n) const = 0; inline t_size get_size() const {return get_count();} inline T get_item(t_size n) const {T temp; get_item_ex(temp,n); return temp;} inline T operator[](t_size n) const {T temp; get_item_ex(temp,n); return temp;} template<typename t_compare> t_size find_duplicates_sorted_t(t_compare p_compare,bit_array_var & p_out) const { return pfc::find_duplicates_sorted_t<list_base_const_t<T> const &,t_compare>(*this,get_count(),p_compare,p_out); } template<typename t_compare,typename t_permutation> t_size find_duplicates_sorted_permutation_t(t_compare p_compare,t_permutation const & p_permutation,bit_array_var & p_out) { return pfc::find_duplicates_sorted_permutation_t<list_base_const_t<T> const &,t_compare,t_permutation>(*this,get_count(),p_compare,p_permutation,p_out); } template<typename t_search> t_size find_item(const t_search & p_item) const//returns index of first occurance, infinite if not found { t_size n,max = get_count(); for(n=0;n<max;n++) if (get_item(n)==p_item) return n; return ~0; } template<typename t_search> inline bool have_item(const t_search & p_item) const {return find_item<t_search>(p_item)!=~0;} template<typename t_compare, typename t_param> bool bsearch_t(t_compare p_compare,t_param const & p_param,t_size &p_index) const { return pfc::bsearch_t(get_count(),*this,p_compare,p_param,p_index); } template<typename t_compare,typename t_param,typename t_permutation> bool bsearch_permutation_t(t_compare p_compare,t_param const & p_param,const t_permutation & p_permutation,t_size & p_index) { return pfc::bsearch_permutation_t(get_count(),*this,p_compare,p_param,p_permutation,p_index); } template<typename t_compare,typename t_permutation> void sort_get_permutation_t(t_compare p_compare,t_permutation const & p_permutation) const { pfc::sort_get_permutation_t<list_base_const_t<T>,t_compare,t_permutation>(*this,p_compare,get_count(),p_permutation); } template<typename t_compare,typename t_permutation> void sort_stable_get_permutation_t(t_compare p_compare,t_permutation const & p_permutation) const { pfc::sort_stable_get_permutation_t<list_base_const_t<T>,t_compare,t_permutation>(*this,p_compare,get_count(),p_permutation); } template<typename t_callback> void enumerate(t_callback & p_callback) const { for(t_size n = 0, m = get_count(); n < m; ++n ) { p_callback( (*this)[n] ); } } protected: list_base_const_t() {} ~list_base_const_t() {} private: const t_self & operator=(const t_self &) {throw pfc::exception_not_implemented();} }; template<typename T> class list_single_ref_t : public list_base_const_t<T> { public: list_single_ref_t(const T & p_item,t_size p_count = 1) : m_item(p_item), m_count(p_count) {} t_size get_count() const {return m_count;} void get_item_ex(T& p_out,t_size n) const {PFC_ASSERT(n<m_count); p_out = m_item;} private: const T & m_item; t_size m_count; }; template<typename T> class list_partial_ref_t : public list_base_const_t<T> { public: list_partial_ref_t(const list_base_const_t<T> & p_list,t_size p_base,t_size p_count) : m_list(p_list), m_base(p_base), m_count(p_count) { PFC_ASSERT(m_base + m_count <= m_list.get_count()); } private: const list_base_const_t<T> & m_list; t_size m_base,m_count; t_size get_count() const {return m_count;} void get_item_ex(T & p_out,t_size n) const {m_list.get_item_ex(p_out,n+m_base);} }; template<typename T,typename A> class list_const_array_t : public list_base_const_t<T> { public: inline list_const_array_t(A p_data,t_size p_count) : m_data(p_data), m_count(p_count) {} t_size get_count() const {return m_count;} void get_item_ex(T & p_out,t_size n) const {p_out = m_data[n];} private: A m_data; t_size m_count; }; template<typename to,typename from> class list_const_cast_t : public list_base_const_t<to> { public: list_const_cast_t(const list_base_const_t<from> & p_from) : m_from(p_from) {} t_size get_count() const {return m_from.get_count();} void get_item_ex(to & p_out,t_size n) const { from temp; m_from.get_item_ex(temp,n); p_out = temp; } private: const list_base_const_t<from> & m_from; }; template<typename T,typename A> class ptr_list_const_array_t : public list_base_const_t<T*> { public: inline ptr_list_const_array_t(A p_data,t_size p_count) : m_data(p_data), m_count(p_count) {} t_size get_count() const {return m_count;} void get_item_ex(T* & p_out,t_size n) const {p_out = &m_data[n];} private: A m_data; t_size m_count; }; template<typename T> class list_const_ptr_t : public list_base_const_t<T> { public: inline list_const_ptr_t(const T * p_data,t_size p_count) : m_data(p_data), m_count(p_count) {} t_size get_count() const {return m_count;} void get_item_ex(T & p_out,t_size n) const {p_out = m_data[n];} private: const T * m_data; t_size m_count; }; template<typename T> class NOVTABLE list_base_t : public list_base_const_t<T> { private: typedef list_base_t<T> t_self; public: class NOVTABLE sort_callback { public: virtual int compare(const T& p_item1,const T& p_item2) = 0; }; virtual void filter_mask(const bit_array & mask) = 0; virtual t_size insert_items(const list_base_const_t<T> & items,t_size base) = 0; virtual void reorder_partial(t_size p_base,const t_size * p_data,t_size p_count) = 0; virtual void sort(sort_callback & p_callback) = 0; virtual void sort_stable(sort_callback & p_callback) = 0; virtual void replace_item(t_size p_index,const T& p_item) = 0; virtual void swap_item_with(t_size p_index,T & p_item) = 0; virtual void swap_items(t_size p_index1,t_size p_index2) = 0; inline void reorder(const t_size * p_data) {reorder_partial(0,p_data,this->get_count());} inline t_size insert_item(const T & item,t_size base) {return insert_items(list_single_ref_t<T>(item),base);} t_size insert_items_repeat(const T & item,t_size num,t_size base) {return insert_items(list_single_ref_t<T>(item,num),base);} inline t_size add_items_repeat(T item,t_size num) {return insert_items_repeat(item,num,this->get_count());} t_size insert_items_fromptr(const T* source,t_size num,t_size base) {return insert_items(list_const_ptr_t<T>(source,num),base);} inline t_size add_items_fromptr(const T* source,t_size num) {return insert_items_fromptr(source,num,this->get_count());} inline t_size add_items(const list_base_const_t<T> & items) {return insert_items(items,this->get_count());} inline t_size add_item(const T& item) {return insert_item(item,this->get_count());} inline void remove_mask(const bit_array & mask) {filter_mask(bit_array_not(mask));} inline void remove_all() {filter_mask(bit_array_false());} inline void truncate(t_size val) {if (val < this->get_count()) remove_mask(bit_array_range(val,this->get_count()-val,true));} inline T replace_item_ex(t_size p_index,const T & p_item) {T ret = p_item;swap_item_with(p_index,ret);return ret;} inline T operator[](t_size n) const {return this->get_item(n);} template<typename t_compare> class sort_callback_impl_t : public sort_callback { public: sort_callback_impl_t(t_compare p_compare) : m_compare(p_compare) {} int compare(const T& p_item1,const T& p_item2) {return m_compare(p_item1,p_item2);} private: t_compare m_compare; }; class sort_callback_auto : public sort_callback { public: int compare(const T& p_item1,const T& p_item2) {return pfc::compare_t(p_item1,p_item2);} }; void sort() {sort(sort_callback_auto());} template<typename t_compare> void sort_t(t_compare p_compare) {sort(sort_callback_impl_t<t_compare>(p_compare));} template<typename t_compare> void sort_stable_t(t_compare p_compare) {sort_stable(sort_callback_impl_t<t_compare>(p_compare));} template<typename t_compare> void sort_remove_duplicates_t(t_compare p_compare) { sort_t<t_compare>(p_compare); bit_array_bittable array(this->get_count()); if (this->template find_duplicates_sorted_t<t_compare>(p_compare,array) > 0) remove_mask(array); } template<typename t_compare> void sort_stable_remove_duplicates_t(t_compare p_compare) { sort_stable_t<t_compare>(p_compare); bit_array_bittable array(this->get_count()); if (this->template find_duplicates_sorted_t<t_compare>(p_compare,array) > 0) remove_mask(array); } template<typename t_compare> void remove_duplicates_t(t_compare p_compare) { order_helper order(this->get_count()); sort_get_permutation_t<t_compare,order_helper&>(p_compare,order); bit_array_bittable array(this->get_count()); if (this->template find_duplicates_sorted_permutation_t<t_compare,order_helper const&>(p_compare,order,array) > 0) remove_mask(array); } template<typename t_func> void for_each(t_func p_func) { t_size n,max=this->get_count(); for(n=0;n<max;n++) p_func(this->get_item(n)); } template<typename t_func> void for_each(t_func p_func,const bit_array & p_mask) { t_size n,max=this->get_count(); for(n=p_mask.find(true,0,max);n<max;n=p_mask.find(true,n+1,max-n-1)) { p_func(this->get_item(n)); } } template<typename t_releasefunc> void remove_mask_ex(const bit_array & p_mask,t_releasefunc p_func) { this->template for_each<t_releasefunc>(p_func,p_mask); remove_mask(p_mask); } template<typename t_releasefunc> void remove_all_ex(t_releasefunc p_func) { this->template for_each<t_releasefunc>(p_func); remove_all(); } const t_self & operator=(const t_self & p_source) {remove_all(); add_items(p_source);return *this;} protected: list_base_t() {} ~list_base_t() {} }; template<typename T,typename t_storage> class list_impl_t : public list_base_t<T> { public: list_impl_t() {} list_impl_t(const list_impl_t<T,t_storage> & p_source) { *this = p_source; } void prealloc(t_size count) {m_buffer.prealloc(count);} void set_count(t_size p_count) {m_buffer.set_size(p_count);} void set_size(t_size p_count) {m_buffer.set_size(p_count);} t_size insert_item(const T& item,t_size idx) { t_size max = m_buffer.get_size(); if (idx > max) idx = max; max++; m_buffer.set_size(max); t_size n; for(n=max-1;n>idx;n--) m_buffer[n]=m_buffer[n-1]; m_buffer[idx]=item; return idx; } T remove_by_idx(t_size idx) { T ret = m_buffer[idx]; t_size n; t_size max = m_buffer.get_size(); for(n=idx+1;n<max;n++) { pfc::swap_t(m_buffer[n-1],m_buffer[n]); } m_buffer.set_size(max-1); return ret; } inline void get_item_ex(T& p_out,t_size n) const { PFC_ASSERT(n>=0); PFC_ASSERT(n<get_count()); p_out = m_buffer[n]; } inline const T& get_item_ref(t_size n) const { PFC_ASSERT(n>=0); PFC_ASSERT(n<get_count()); return m_buffer[n]; } inline T get_item(t_size n) const { PFC_ASSERT(n >= 0); PFC_ASSERT(n < get_count() ); return m_buffer[n]; }; inline t_size get_count() const {return m_buffer.get_size();} inline t_size get_size() const {return get_count();} inline const T & operator[](t_size n) const { PFC_ASSERT(n>=0); PFC_ASSERT(n<get_count()); return m_buffer[n]; } inline const T* get_ptr() const {return m_buffer.get_ptr();} inline T* get_ptr() {return m_buffer.get_ptr();} inline T& operator[](t_size n) {return m_buffer[n];} inline void remove_from_idx(t_size idx,t_size num) { remove_mask(bit_array_range(idx,num)); } t_size insert_items(const list_base_const_t<T> & source,t_size base) { t_size count = get_count(); if (base>count) base = count; t_size num = source.get_count(); m_buffer.set_size(count+num); if (count > base) { t_size n; for(n=count-1;(int)n>=(int)base;n--) { pfc::swap_t(m_buffer[n+num],m_buffer[n]); } } { t_size n; for(n=0;n<num;n++) { source.get_item_ex(m_buffer[n+base],n); } } return base; } void get_items_mask(list_impl_t<T,t_storage> & out,const bit_array & mask) { t_size n,count = get_count(); for_each_bit_array(n,mask,true,0,count) out.add_item(m_buffer[n]); } void filter_mask(const bit_array & mask) { t_size n,count = get_count(), total = 0; n = total = mask.find(false,0,count); if (n<count) { for(n=mask.find(true,n+1,count-n-1);n<count;n=mask.find(true,n+1,count-n-1)) pfc::swap_t(m_buffer[total++],m_buffer[n]); m_buffer.set_size(total); } } void replace_item(t_size idx,const T& item) { PFC_ASSERT(idx>=0); PFC_ASSERT(idx<get_count()); m_buffer[idx] = item; } void sort() { pfc::sort_callback_impl_auto_wrap_t<t_storage> wrapper(m_buffer); pfc::sort(wrapper,get_count()); } template<typename t_compare> void sort_t(t_compare p_compare) { pfc::sort_callback_impl_simple_wrap_t<t_storage,t_compare> wrapper(m_buffer,p_compare); pfc::sort(wrapper,get_count()); } template<typename t_compare> void sort_stable_t(t_compare p_compare) { pfc::sort_callback_impl_simple_wrap_t<t_storage,t_compare> wrapper(m_buffer,p_compare); pfc::sort_stable(wrapper,get_count()); } inline void reorder_partial(t_size p_base,const t_size * p_order,t_size p_count) { PFC_ASSERT(p_base+p_count<=get_count()); pfc::reorder_partial_t(m_buffer,p_base,p_order,p_count); } template<typename t_compare> t_size find_duplicates_sorted_t(t_compare p_compare,bit_array_var & p_out) const { return pfc::find_duplicates_sorted_t<list_impl_t<T,t_storage> const &,t_compare>(*this,get_count(),p_compare,p_out); } template<typename t_compare,typename t_permutation> t_size find_duplicates_sorted_permutation_t(t_compare p_compare,t_permutation p_permutation,bit_array_var & p_out) { return pfc::find_duplicates_sorted_permutation_t<list_impl_t<T,t_storage> const &,t_compare,t_permutation>(*this,get_count(),p_compare,p_permutation,p_out); } private: class sort_callback_wrapper { public: explicit inline sort_callback_wrapper(sort_callback & p_callback) : m_callback(p_callback) {} inline int operator()(const T& item1,const T& item2) const {return m_callback.compare(item1,item2);} private: sort_callback & m_callback; }; public: void sort(sort_callback & p_callback) { sort_t(sort_callback_wrapper(p_callback)); } void sort_stable(sort_callback & p_callback) { sort_stable_t(sort_callback_wrapper(p_callback)); } void remove_mask(const bit_array & mask) {filter_mask(bit_array_not(mask));} void remove_mask(const bool * mask) {remove_mask(bit_array_table(mask,get_count()));} void filter_mask(const bool * mask) {filter_mask(bit_array_table(mask,get_count()));} t_size add_item(const T& item) { t_size idx = get_count(); insert_item(item,idx); return idx; } void remove_all() {remove_mask(bit_array_true());} void remove_item(const T& item) { t_size n,max = get_count(); bit_array_bittable mask(max); for(n=0;n<max;n++) mask.set(n,get_item(n)==item); remove_mask(mask); } void swap_item_with(t_size p_index,T & p_item) { PFC_ASSERT(p_index < get_count()); pfc::swap_t(m_buffer[p_index],p_item); } void swap_items(t_size p_index1,t_size p_index2) { PFC_ASSERT(p_index1 < get_count()); PFC_ASSERT(p_index1 < get_count()); pfc::swap_t(m_buffer[p_index1],m_buffer[p_index2]); } inline static void g_swap(list_impl_t<T,t_storage> & p_item1,list_impl_t<T,t_storage> & p_item2) { pfc::swap_t(p_item1.m_buffer,p_item2.m_buffer); } template<typename t_search> t_size find_item(const t_search & p_item) const//returns index of first occurance, infinite if not found { t_size n,max = get_count(); for(n=0;n<max;n++) if (m_buffer[n]==p_item) return n; return ~0; } template<typename t_search> inline bool have_item(const t_search & p_item) const {return this->template find_item<t_search>(p_item)!=~0;} protected: t_storage m_buffer; }; template<typename t_item, template<typename> class t_alloc = pfc::alloc_fast > class list_t : public list_impl_t<t_item,pfc::array_t<t_item,t_alloc> > { }; template<typename t_item, t_size p_fixed_count, template<typename> class t_alloc = pfc::alloc_fast > class list_hybrid_t : public list_impl_t<t_item,pfc::array_hybrid_t<t_item,p_fixed_count,t_alloc> > {}; template<typename T> class ptr_list_const_cast_t : public list_base_const_t<const T *> { public: inline ptr_list_const_cast_t(const list_base_const_t<T*> & p_param) : m_param(p_param) {} t_size get_count() const {return m_param.get_count();} void get_item_ex(const T * & p_out,t_size n) const {T* temp; m_param.get_item_ex(temp,n); p_out = temp;} private: const list_base_const_t<T*> & m_param; }; template<typename T,typename P> class list_const_permutation_t : public list_base_const_t<T> { public: inline list_const_permutation_t(const list_base_const_t<T> & p_list,P p_permutation) : m_list(p_list), m_permutation(p_permutation) {} t_size get_count() const {return m_list.get_count();} void get_item_ex(T & p_out,t_size n) const {m_list.get_item_ex(p_out,m_permutation[n]);} private: P m_permutation; const list_base_const_t<T> & m_list; }; template<class T> class list_permutation_t : public list_base_const_t<T> { public: t_size get_count() const {return m_count;} void get_item_ex(T & p_out,t_size n) const {m_base.get_item_ex(p_out,m_order[n]);} list_permutation_t(const list_base_const_t<T> & p_base,const t_size * p_order,t_size p_count) : m_base(p_base), m_order(p_order), m_count(p_count) { PFC_ASSERT(m_base.get_count() >= m_count); } private: const list_base_const_t<T> & m_base; const t_size * m_order; t_size m_count; }; } #endif //_PFC_LIST_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/map.h ================================================ #ifndef _MAP_T_H_INCLUDED_ #define _MAP_T_H_INCLUDED_ namespace pfc { template<typename t_destination> class __map_overwrite_wrapper { public: __map_overwrite_wrapper(t_destination & p_destination) : m_destination(p_destination) {} template<typename t_from,typename t_to> void operator() (const t_from & p_from,const t_to & p_to) {m_destination.set(p_from,p_to);} private: t_destination & m_destination; }; template<typename t_storage_from, typename t_storage_to, typename t_comparator = comparator_default> class map_t { private: typedef map_t<t_storage_from,t_storage_to,t_comparator> t_self; public: template<typename t_from,typename t_to> void set(const t_from & p_from, const t_to & p_to) { bool isnew; t_storage & storage = m_data.add_ex(t_search_set<t_from,t_to>(p_from,p_to), isnew); if (!isnew) storage.m_to = p_to; } template<typename t_from> t_storage_to & find_or_add(t_from const & p_from) { return m_data.add(t_search_query<t_from>(p_from)).m_to; } template<typename t_from> t_storage_to & find_or_add_ex(t_from const & p_from,bool & p_isnew) { return m_data.add_ex(t_search_query<t_from>(p_from),p_isnew).m_to; } template<typename t_from> bool have_item(const t_from & p_from) const { return m_data.have_item(t_search_query<t_from>(p_from)); } template<typename t_from,typename t_to> bool query(const t_from & p_from,t_to & p_to) const { const t_storage * storage = m_data.find_ptr(t_search_query<t_from>(p_from)); if (storage == NULL) return false; p_to = storage->m_to; return true; } template<typename t_from> const t_storage_to * query_ptr(const t_from & p_from) const { const t_storage * storage = m_data.find_ptr(t_search_query<t_from>(p_from)); if (storage == NULL) return NULL; return &storage->m_to; } template<typename t_from> t_storage_to * query_ptr(const t_from & p_from) { t_storage * storage = m_data.find_ptr(t_search_query<t_from>(p_from)); if (storage == NULL) return NULL; return &storage->m_to; } template<bool inclusive,bool above,typename t_from> const t_storage_to * query_nearest_ptr(t_from & p_from) const { const t_storage * storage = m_data.find_nearest_item<inclusive,above>(t_search_query<t_from>(p_from)); if (storage == NULL) return NULL; p_from = storage->m_from; return &storage->m_to; } template<bool inclusive,bool above,typename t_from> t_storage_to * query_nearest_ptr(t_from & p_from) { t_storage * storage = m_data.find_nearest_item<inclusive,above>(t_search_query<t_from>(p_from)); if (storage == NULL) return NULL; p_from = storage->m_from; return &storage->m_to; } template<bool inclusive,bool above,typename t_from,typename t_to> bool query_nearest(t_from & p_from,t_to & p_to) const { const t_storage * storage = m_data.find_nearest_item<inclusive,above>(t_search_query<t_from>(p_from)); if (storage == NULL) return false; p_from = storage->m_from; p_to = storage->m_to; return true; } template<typename t_from> void remove(const t_from & p_from) { m_data.remove_item(t_search_query<t_from>(p_from)); } template<typename t_callback> void enumerate(t_callback & p_callback) const { m_data.enumerate(enumeration_wrapper<t_callback>(p_callback)); } template<typename t_callback> void enumerate(t_callback & p_callback) { m_data.__enumerate(enumeration_wrapper_var<t_callback>(p_callback)); } t_size get_count() const {return m_data.get_count();} void remove_all() {m_data.remove_all();} template<typename t_source> void overwrite(const t_source & p_source) { __map_overwrite_wrapper<t_self> wrapper(*this); p_source.enumerate(wrapper); } //backwards compatibility method wrappers template<typename t_from> bool exists(const t_from & p_from) const {return have_item(p_from);} private: template<typename t_from> struct t_search_query { t_search_query(const t_from & p_from) : m_from(p_from) {} t_from const & m_from; }; template<typename t_from,typename t_to> struct t_search_set { t_search_set(const t_from & p_from, const t_to & p_to) : m_from(p_from), m_to(p_to) {} t_from const & m_from; t_to const & m_to; }; struct t_storage { t_storage_from m_from; t_storage_to m_to; template<typename t_from> t_storage(t_search_query<t_from> const & p_source) : m_from(p_source.m_from), m_to() {} template<typename t_from,typename t_to> t_storage(t_search_set<t_from,t_to> const & p_source) : m_from(p_source.m_from), m_to(p_source.m_to) {} }; class comparator_wrapper { public: template<typename t_other> inline static int compare(const t_storage & p_item1,const t_other & p_item2) { return t_comparator::compare(p_item1.m_from,p_item2.m_from); } }; template<typename t_callback> class enumeration_wrapper { public: enumeration_wrapper(t_callback & p_callback) : m_callback(p_callback) {} void operator()(const t_storage & p_item) {m_callback(p_item.m_from,p_item.m_to);} private: t_callback & m_callback; }; template<typename t_callback> class enumeration_wrapper_var { public: enumeration_wrapper_var(t_callback & p_callback) : m_callback(p_callback) {} void operator()(t_storage & p_item) {m_callback(safe_cast<t_storage_from const&>(p_item.m_from),p_item.m_to);} private: t_callback & m_callback; }; typedef avltree_t<t_storage,comparator_wrapper> t_content; t_content m_data; public: typedef traits_t<t_content> traits; }; template<typename t_storage_from, typename t_storage_to, typename t_comparator> class traits_t<map_t<t_storage_from,t_storage_to,t_comparator> > : public map_t<t_storage_from,t_storage_to,t_comparator>::traits {}; } #endif //_MAP_T_H_INCLUDED_ ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/mem_block_mgr.h ================================================ #ifndef _MEM_BLOCK_MGR_H_ #define _MEM_BLOCK_MGR_H_ #error DEPRECATED template<class T> class mem_block_manager { struct entry { mem_block_t<T> block; bool used; }; ptr_list_t<entry> list; public: T * copy(const T* ptr,int size) { int n; int found_size = -1,found_index = -1; for(n=0;n<list.get_count();n++) { if (!list[n]->used) { int block_size = list[n]->block.get_size(); if (found_size<0) { found_index=n; found_size = block_size; } else if (found_size<size) { if (block_size>found_size) { found_index=n; found_size = block_size; } } else if (found_size>size) { if (block_size>=size && block_size<found_size) { found_index=n; found_size = block_size; } } if (found_size==size) break; } } if (found_index>=0) { list[found_index]->used = true; return list[found_index]->block.copy(ptr,size); } entry * new_entry = new entry; new_entry->used = true; list.add_item(new_entry); return new_entry->block.copy(ptr,size); } void mark_as_free() { int n; for(n=0;n<list.get_count();n++) { list[n]->used = false; } } ~mem_block_manager() {list.delete_all();} }; #endif ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/order_helper.h ================================================ class order_helper { pfc::array_t<t_size> m_data; public: order_helper(t_size p_size) { m_data.set_size(p_size); for(t_size n=0;n<p_size;n++) m_data[n]=n; } order_helper(const order_helper & p_order) {*this = p_order;} template<typename t_int> static void g_fill(t_int * p_order,const t_size p_count) { t_size n; for(n=0;n<p_count;n++) p_order[n] = (t_int)n; } template<typename t_array> static void g_fill(t_array & p_array) { t_size n; const t_size max = pfc::array_size_t(p_array); for(n=0;n<max;n++) p_array[n] = n; } t_size get_item(t_size ptr) const {return m_data[ptr];} t_size & operator[](t_size ptr) {return m_data[ptr];} t_size operator[](t_size ptr) const {return m_data[ptr];} static void g_swap(t_size * p_data,t_size ptr1,t_size ptr2); inline void swap(t_size ptr1,t_size ptr2) {pfc::swap_t(m_data[ptr1],m_data[ptr2]);} const t_size * get_ptr() const {return m_data.get_ptr();} static t_size g_find_reverse(const t_size * order,t_size val); inline t_size find_reverse(t_size val) {return g_find_reverse(m_data.get_ptr(),val);} static void g_reverse(t_size * order,t_size base,t_size count); inline void reverse(t_size base,t_size count) {g_reverse(m_data.get_ptr(),base,count);} t_size get_count() const {return m_data.get_size();} }; ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/other.cpp ================================================ #include "pfc.h" #include <intrin.h> void order_helper::g_swap(t_size * data,t_size ptr1,t_size ptr2) { t_size temp = data[ptr1]; data[ptr1] = data[ptr2]; data[ptr2] = temp; } t_size order_helper::g_find_reverse(const t_size * order,t_size val) { t_size prev = val, next = order[val]; while(next != val) { prev = next; next = order[next]; } return prev; } void order_helper::g_reverse(t_size * order,t_size base,t_size count) { t_size max = count>>1; t_size n; t_size base2 = base+count-1; for(n=0;n<max;n++) g_swap(order,base+n,base2-n); } void pfc::crash() { #if 0 //def _MSC_VER __debugbreak(); #else *(char*)NULL = 0; #endif } void pfc::byteswap_raw(void * p_buffer,const t_size p_bytes) { t_uint8 * ptr = (t_uint8*)p_buffer; t_size n; for(n=0;n<p_bytes>>1;n++) swap_t(ptr[n],ptr[p_bytes-n-1]); } #if defined(_DEBUG) && defined(_WIN32) void pfc::myassert(const wchar_t * _Message, const wchar_t *_File, unsigned _Line) { if (IsDebuggerPresent()) pfc::crash(); _wassert(_Message,_File,_Line); } #endif ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/other.h ================================================ #ifndef _PFC_OTHER_H_ #define _PFC_OTHER_H_ namespace pfc { template<class T> class vartoggle_t { T oldval; T & var; public: vartoggle_t(T & p_var,const T & val) : var(p_var) { oldval = var; var = val; } ~vartoggle_t() {var = oldval;} }; typedef vartoggle_t<bool> booltoggle; }; #ifdef _MSC_VER class fpu_control { unsigned old_val; unsigned mask; public: inline fpu_control(unsigned p_mask,unsigned p_val) { mask = p_mask; _controlfp_s(&old_val,p_val,mask); } inline ~fpu_control() { unsigned dummy; _controlfp_s(&dummy,old_val,mask); } }; class fpu_control_roundnearest : private fpu_control { public: fpu_control_roundnearest() : fpu_control(_MCW_RC,_RC_NEAR) {} }; class fpu_control_flushdenormal : private fpu_control { public: fpu_control_flushdenormal() : fpu_control(_MCW_DN,_DN_FLUSH) {} }; class fpu_control_default : private fpu_control { public: fpu_control_default() : fpu_control(_MCW_DN|_MCW_RC,_DN_FLUSH|_RC_NEAR) {} }; #ifdef _M_IX86 class sse_control { public: sse_control(unsigned p_mask,unsigned p_val) : m_mask(p_mask) { __control87_2(p_val,p_mask,NULL,&m_oldval); } ~sse_control() { __control87_2(m_oldval,m_mask,NULL,&m_oldval); } private: unsigned m_mask,m_oldval; }; class sse_control_flushdenormal : private sse_control { public: sse_control_flushdenormal() : sse_control(_MCW_DN,_DN_FLUSH) {} }; #endif #endif namespace pfc { class refcounter { public: refcounter(long p_val = 0) : m_val(p_val) {} #ifdef _WINDOWS long operator++() {return InterlockedIncrement(&m_val);} long operator--() {return InterlockedDecrement(&m_val);} #else long operator++() {return ++m_val;} long operator--() {return --m_val;} #pragma message("PORTME") #endif private: long m_val; }; class releaser_delete { public: template<typename T> static void release(T* p_ptr) {delete p_ptr;} }; class releaser_delete_array { public: template<typename T> static void release(T* p_ptr) {delete[] p_ptr;} }; class releaser_free { public: static void release(void * p_ptr) {free(p_ptr);} }; //! Assumes t_freefunc to never throw exceptions. template<typename T,typename t_releaser = releaser_delete > class ptrholder_t { private: typedef ptrholder_t<T,t_releaser> t_self; public: inline ptrholder_t(T* p_ptr) : m_ptr(p_ptr) {} inline ptrholder_t() : m_ptr(NULL) {} inline ~ptrholder_t() {t_releaser::release(m_ptr);} inline bool is_valid() const {return m_ptr != NULL;} inline bool is_empty() const {return m_ptr == NULL;} inline T* operator->() const {return m_ptr;} inline T* get_ptr() const {return m_ptr;} inline void release() {t_releaser::release(replace_null_t(m_ptr));;} inline void attach(T * p_ptr) {release(); m_ptr = p_ptr;} inline const t_self & operator=(T * p_ptr) {set(p_ptr);return *this;} inline T* detach() {return pfc::replace_null_t(m_ptr);} inline T& operator*() const {return *m_ptr;} inline t_self & operator<<(t_self & p_source) {attach(p_source.detach());return *this;} inline t_self & operator>>(t_self & p_dest) {p_dest.attach(detach());return *this;} //deprecated inline void set(T * p_ptr) {attach(p_ptr);} private: ptrholder_t(const t_self &) {throw pfc::exception_not_implemented();} const t_self & operator=(const t_self & ) {throw pfc::exception_not_implemented();} T* m_ptr; }; //avoid "void&" breakage template<typename t_releaser> class ptrholder_t<void,t_releaser> { private: typedef void T; typedef ptrholder_t<T,t_releaser> t_self; public: inline ptrholder_t(T* p_ptr) : m_ptr(p_ptr) {} inline ptrholder_t() : m_ptr(NULL) {} inline ~ptrholder_t() {t_releaser::release(m_ptr);} inline bool is_valid() const {return m_ptr != NULL;} inline bool is_empty() const {return m_ptr == NULL;} inline T* operator->() const {return m_ptr;} inline T* get_ptr() const {return m_ptr;} inline void release() {t_releaser::release(replace_null_t(m_ptr));;} inline void attach(T * p_ptr) {release(); m_ptr = p_ptr;} inline const t_self & operator=(T * p_ptr) {set(p_ptr);return *this;} inline T* detach() {return pfc::replace_null_t(m_ptr);} inline t_self & operator<<(t_self & p_source) {attach(p_source.detach());return *this;} inline t_self & operator>>(t_self & p_dest) {p_dest.attach(detach());return *this;} //deprecated inline void set(T * p_ptr) {attach(p_ptr);} private: ptrholder_t(const t_self &) {throw pfc::exception_not_implemented();} const t_self & operator=(const t_self & ) {throw pfc::exception_not_implemented();} T* m_ptr; }; void crash(); } #endif ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/pfc.h ================================================ #ifndef ___PFC_H___ #define ___PFC_H___ #if !defined(_WINDOWS) && (defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) || defined(_WIN32_WCE)) #define _WINDOWS #endif #define PFC_DLL_EXPORT #if defined(_WIN32) || defined(_WIN32_WCE) #ifndef STRICT #define STRICT #endif #ifndef _SYS_GUID_OPERATOR_EQ_ #define _NO_SYS_GUID_OPERATOR_EQ_ //fix retarded warning with operator== on GUID returning int #endif #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x500 #endif #include <windows.h> #ifndef _SYS_GUID_OPERATOR_EQ_ __inline bool __InlineIsEqualGUID(REFGUID rguid1, REFGUID rguid2) { return ( ((unsigned long *) &rguid1)[0] == ((unsigned long *) &rguid2)[0] && ((unsigned long *) &rguid1)[1] == ((unsigned long *) &rguid2)[1] && ((unsigned long *) &rguid1)[2] == ((unsigned long *) &rguid2)[2] && ((unsigned long *) &rguid1)[3] == ((unsigned long *) &rguid2)[3]); } inline bool operator==(REFGUID guidOne, REFGUID guidOther) {return __InlineIsEqualGUID(guidOne,guidOther);} inline bool operator!=(REFGUID guidOne, REFGUID guidOther) {return !__InlineIsEqualGUID(guidOne,guidOther);} #endif #include <tchar.h> #elif defined(__GNUC__) && (defined __unix__ || defined __POSIX__) #include <stdint.h> #include <memory.h> typedef struct { uint32_t Data1; uint16_t Data2; uint16_t Data3; uint8_t Data4[ 8 ]; } GUID; //same as win32 GUID inline bool operator==(const GUID & p_item1,const GUID & p_item2) { return memcmp(&p_item1,&p_item2,sizeof(GUID)) == 0; } inline bool operator!=(const GUID & p_item1,const GUID & p_item2) { return memcmp(&p_item1,&p_item2,sizeof(GUID)) != 0; } #else #error Only win32 or unix target supported. #endif #define PFC_MEMORY_SPACE_LIMIT ((t_uint64)1<<(sizeof(void*)*8-1)) #define PFC_ALLOCA_LIMIT (4096) #define INDEX_INVALID ((unsigned)(-1)) #include <exception> #include <stdexcept> #include <new> #include <malloc.h> #include <stdio.h> #include <assert.h> #include <math.h> #include <float.h> #ifndef _DEBUG #define PFC_ASSERT(_Expression) ((void)0) #else #ifdef _WIN32 namespace pfc { void myassert(const wchar_t * _Message, const wchar_t *_File, unsigned _Line); } #define PFC_ASSERT(_Expression) (void)( (!!(_Expression)) || (pfc::myassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) ) #else #define PFC_ASSERT(_Expression) assert(_Expression) #endif #endif #ifdef _MSC_VER #ifdef _DEBUG #define NOVTABLE #else #define NOVTABLE _declspec(novtable) #endif #ifdef _DEBUG #define ASSUME(X) PFC_ASSERT(X) #else #define ASSUME(X) __assume(X) #endif #define PFC_DEPRECATE(X) __declspec(deprecated(X)) #else #define NOVTABLE #define ASSUME(X) assert(X) #define PFC_DEPRECATE(X) #endif #include "int_types.h" #include "traits.h" #include "bit_array.h" #include "primitives.h" #include "alloc.h" #include "array.h" #include "bit_array_impl.h" #include "bsearch_inline.h" #include "bsearch.h" #include "sort.h" #include "order_helper.h" #include "list.h" #include "ptr_list.h" #include "string.h" #include "string_list.h" #include "avltree.h" #include "map.h" #include "profiler.h" #include "guid.h" #include "byte_order_helper.h" #include "other.h" #include "chainlist.h" #include "ref_counter.h" #include "rcptr.h" #include "com_ptr_t.h" #include "string_conv.h" #include "instance_tracker.h" #endif //___PFC_H___ ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/primitives.h ================================================ #define tabsize(x) ((size_t)(sizeof(x)/sizeof(*x))) #define TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD_WITH_INITIALIZER(THISCLASS,MEMBER,INITIALIZER) \ THISCLASS() INITIALIZER \ template<typename t_param1> THISCLASS(const t_param1 & p_param1) : MEMBER(p_param1) INITIALIZER \ template<typename t_param1,typename t_param2> THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2) : MEMBER(p_param1,p_param2) INITIALIZER \ template<typename t_param1,typename t_param2,typename t_param3> THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3) : MEMBER(p_param1,p_param2,p_param3) INITIALIZER \ template<typename t_param1,typename t_param2,typename t_param3,typename t_param4> THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3,const t_param4 & p_param4) : MEMBER(p_param1,p_param2,p_param3,p_param4) INITIALIZER \ template<typename t_param1,typename t_param2,typename t_param3,typename t_param4,typename t_param5> THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3,const t_param4 & p_param4,const t_param5 & p_param5) : MEMBER(p_param1,p_param2,p_param3,p_param4,p_param5) INITIALIZER \ template<typename t_param1,typename t_param2,typename t_param3,typename t_param4,typename t_param5,typename t_param6> THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3,const t_param4 & p_param4,const t_param5 & p_param5,const t_param6 & p_param6) : MEMBER(p_param1,p_param2,p_param3,p_param4,p_param5,p_param6) INITIALIZER \ template<typename t_param1,typename t_param2,typename t_param3,typename t_param4,typename t_param5,typename t_param6, typename t_param7> THISCLASS(const t_param1 & p_param1,const t_param2 & p_param2,const t_param3 & p_param3,const t_param4 & p_param4,const t_param5 & p_param5,const t_param6 & p_param6,const t_param7 & p_param7) : MEMBER(p_param1,p_param2,p_param3,p_param4,p_param5,p_param6,p_param7) INITIALIZER #define TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD(THISCLASS,MEMBER) TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD_WITH_INITIALIZER(THISCLASS,MEMBER,{}) #ifdef _MSC_VER //Bah. I noticed the fact that std::exception carrying a custom message is MS-specific *after* making exception classes a part of ABI. To be nuked next time fb2k component backwards compatibility is axed. #define PFC_DECLARE_EXCEPTION(NAME,BASECLASS,DEFAULTMSG) \ class NAME : public BASECLASS { \ public: \ static const char * g_what() {return DEFAULTMSG;} \ NAME() : BASECLASS(DEFAULTMSG,0) {} \ NAME(const char * p_msg) : BASECLASS(p_msg) {} \ NAME(const char * p_msg,int) : BASECLASS(p_msg,0) {} \ NAME(const NAME & p_source) : BASECLASS(p_source) {} \ }; namespace pfc { template<typename t_exception> inline void throw_exception_with_message(const char * p_message) { throw t_exception(p_message); } } #else #define PFC_DECLARE_EXCEPTION(NAME,BASECLASS,DEFAULTMSG) \ class NAME : public BASECLASS { \ public: \ static const char * g_what() {return DEFAULTMSG;} \ const char* what() const throw() {return DEFAULTMSG;} \ }; namespace pfc { template<typename t_base> class __exception_with_message_t : public t_base { private: typedef __exception_with_message_t<t_base> t_self; public: __exception_with_message_t(const char * p_message) : m_message(NULL) { set_message(p_message); } __exception_with_message_t() : m_message(NULL) {} __exception_with_message_t(const t_self & p_source) : m_message(NULL) {set_message(p_source.m_message);} const char* what() const throw() {return m_message != NULL ? m_message : "unnamed exception";} const t_self & operator=(const t_self & p_source) {set_message(p_source.m_message);} ~__exception_with_message_t() throw() {cleanup();} private: void set_message(const char * p_message) throw() { cleanup(); if (p_message != NULL) m_message = strdup(p_message); } void cleanup() throw() { if (m_message != NULL) {free(m_message); m_message = NULL;} } char * m_message; }; template<typename t_exception> void throw_exception_with_message(const char * p_message) { throw __exception_with_message_t<t_exception>(p_message); } } #endif namespace pfc { template<typename p_type1,typename p_type2> class assert_same_type; template<typename p_type> class assert_same_type<p_type,p_type> {}; template<typename p_type1,typename p_type2> class is_same_type { public: enum {value = false}; }; template<typename p_type> class is_same_type<p_type,p_type> { public: enum {value = true}; }; template<bool p_val> class __static_assert_switcher_t; template<> class __static_assert_switcher_t<true> { public: typedef void t_assert_failed; }; //depreacted template<bool p_val> typename __static_assert_switcher_t<p_val>::t_assert_failed static_assert_t() {} template<bool p_val> typename __static_assert_switcher_t<p_val>::t_assert_failed static_assert() {} template<typename t_type> void assert_raw_type() {static_assert_t< !traits_t<t_type>::needs_constructor && !traits_t<t_type>::needs_destructor >();} template<typename t_type> void __unsafe__memcpy_t(t_type * p_dst,const t_type * p_src,t_size p_count) { ::memcpy(reinterpret_cast<void*>(p_dst), reinterpret_cast<const void*>(p_src), p_count * sizeof(t_type)); } template<typename t_type> void __unsafe__in_place_destructor_t(t_type & p_item) throw() { if (traits_t<t_type>::needs_destructor) try{ p_item.~t_type(); } catch(...) {} } template<typename t_type> void __unsafe__in_place_constructor_t(t_type & p_item) { if (traits_t<t_type>::needs_constructor) { t_type * ret = new(&p_item) t_type; PFC_ASSERT(ret == &p_item); } } template<typename t_type> void __unsafe__in_place_destructor_array_t(t_type * p_items, t_size p_count) throw() { if (traits_t<t_type>::needs_destructor) { t_type * walk = p_items; for(t_size n=p_count;n;--n) __unsafe__in_place_destructor_t(*(walk++)); } } template<typename t_type> t_type * __unsafe__in_place_constructor_array_t(t_type * p_items,t_size p_count) { if (traits_t<t_type>::needs_constructor) { t_size walkptr = 0; try { for(walkptr=0;walkptr<p_count;++walkptr) __unsafe__in_place_constructor_t(p_items[walkptr]); } catch(...) { __unsafe__in_place_destructor_array_t(p_items,walkptr); throw; } } return p_items; } template<typename t_type> t_type * __unsafe__in_place_resize_array_t(t_type * p_items,t_size p_from,t_size p_to) { if (p_from < p_to) __unsafe__in_place_constructor_array_t(p_items + p_from, p_to - p_from); else if (p_from > p_to) __unsafe__in_place_destructor_array_t(p_items + p_to, p_from - p_to); return p_items; } template<typename t_type,typename t_copy> void __unsafe__in_place_constructor_copy_t(t_type & p_item,const t_copy & p_copyfrom) { if (traits_t<t_type>::needs_constructor) { t_type * ret = new(&p_item) t_type(p_copyfrom); PFC_ASSERT(ret == &p_item); } else { p_item = p_copyfrom; } } template<typename t_type,typename t_copy> t_type * __unsafe__in_place_constructor_array_copy_t(t_type * p_items,t_size p_count, const t_copy * p_copyfrom) { t_size walkptr = 0; try { for(walkptr=0;walkptr<p_count;++walkptr) __unsafe__in_place_constructor_copy_t(p_items[walkptr],p_copyfrom[walkptr]); } catch(...) { __unsafe__in_place_destructor_array_t(p_items,walkptr); throw; } return p_items; } template<typename t_type,typename t_copy> t_type * __unsafe__in_place_constructor_array_copy_partial_t(t_type * p_items,t_size p_count, const t_copy * p_copyfrom,t_size p_copyfrom_count) { if (p_copyfrom_count > p_count) p_copyfrom_count = p_count; __unsafe__in_place_constructor_array_copy_t(p_items,p_copyfrom_count,p_copyfrom); try { __unsafe__in_place_constructor_array_t(p_items + p_copyfrom_count,p_count - p_copyfrom_count); } catch(...) { __unsafe__in_place_destructor_array_t(p_items,p_copyfrom_count); throw; } return p_items; } template<typename t_ret,typename t_param> t_ret safe_cast(t_param const & p_param) { return p_param; } template<typename t_ret,typename t_param> t_ret * safe_ptr_cast(t_param * p_param) { if (pfc::is_same_type<t_ret,t_param>::value) return p_param; else { if (p_param == NULL) return NULL; else return p_param; } } typedef std::exception exception; PFC_DECLARE_EXCEPTION(exception_overflow,exception,"Overflow"); PFC_DECLARE_EXCEPTION(exception_bug_check,exception,"Bug check"); PFC_DECLARE_EXCEPTION(exception_unexpected_recursion,exception_bug_check,"Unexpected recursion"); PFC_DECLARE_EXCEPTION(exception_not_implemented,exception_bug_check,"Feature not implemented"); PFC_DECLARE_EXCEPTION(exception_dynamic_assert,exception_bug_check,"dynamic_assert failure"); template<typename t_ret,typename t_param> t_ret downcast_guarded(const t_param & p_param) { t_ret temp = (t_ret) p_param; if ((t_param) temp != p_param) throw exception_overflow(); return temp; } template<typename t_exception,typename t_ret,typename t_param> t_ret downcast_guarded_ex(const t_param & p_param) { t_ret temp = (t_ret) p_param; if ((t_param) temp != p_param) throw t_exception(); return temp; } template<typename t_acc,typename t_add> void accumulate_guarded(t_acc & p_acc, const t_add & p_add) { t_acc delta = downcast_guarded<t_acc>(p_add); delta += p_acc; if (delta < p_acc) throw exception_overflow(); p_acc = delta; } //deprecated inline void bug_check_assert(bool p_condition, const char * p_msg) { if (!p_condition) { PFC_ASSERT(0); throw_exception_with_message<exception_bug_check>(p_msg); } } //deprecated inline void bug_check_assert(bool p_condition) { if (!p_condition) { PFC_ASSERT(0); throw exception_bug_check(); } } inline void dynamic_assert(bool p_condition, const char * p_msg) { if (!p_condition) { PFC_ASSERT(0); throw_exception_with_message<exception_dynamic_assert>(p_msg); } } inline void dynamic_assert(bool p_condition) { if (!p_condition) { PFC_ASSERT(0); throw exception_dynamic_assert(); } } template<typename T> inline void swap_multi_t(T * p_buffer1,T * p_buffer2,t_size p_size) { T * walk1 = p_buffer1, * walk2 = p_buffer2; for(t_size n=p_size;n;--n) { T temp (* walk1); *walk1 = *walk2; *walk2 = temp; walk1++; walk2++; } } template<typename T,t_size p_size> inline void swap_multi_t(T * p_buffer1,T * p_buffer2) { T * walk1 = p_buffer1, * walk2 = p_buffer2; for(t_size n=p_size;n;--n) { T temp (* walk1); *walk1 = *walk2; *walk2 = temp; walk1++; walk2++; } } template<t_size p_size> inline void __unsafe__swap_raw_t(void * p_object1, void * p_object2) { if (p_size % sizeof(t_size) == 0) { swap_multi_t<t_size,p_size/sizeof(t_size)>(reinterpret_cast<t_size*>(p_object1),reinterpret_cast<t_size*>(p_object2)); } else { swap_multi_t<t_uint8,p_size>(reinterpret_cast<t_uint8*>(p_object1),reinterpret_cast<t_uint8*>(p_object2)); } } template<typename T> inline void swap_t(T & p_item1, T & p_item2) { if (traits_t<T>::realloc_safe) { __unsafe__swap_raw_t<sizeof(T)>( reinterpret_cast<void*>( &p_item1 ), reinterpret_cast<void*>( &p_item2 ) ); } else { T temp(p_item2); p_item2 = p_item1; p_item1 = temp; } } template<typename t_array> t_size array_size_t(const t_array & p_array) {return p_array.get_size();} template<typename t_item, t_size p_width> t_size array_size_t(const t_item (&p_array)[p_width]) {return p_width;} template<typename t_array,typename t_filler> inline void fill_t(t_array & p_buffer,const t_size p_count, const t_filler & p_filler) { for(t_size n=0;n<p_count;n++) p_buffer[n] = p_filler; } template<typename t_array,typename t_filler> inline void fill_ptr_t(t_array * p_buffer,const t_size p_count, const t_filler & p_filler) { for(t_size n=0;n<p_count;n++) p_buffer[n] = p_filler; } template<typename t_item1, typename t_item2> inline int compare_t(const t_item1 & p_item1, const t_item2 & p_item2) { if (p_item1 < p_item2) return -1; else if (p_item1 > p_item2) return 1; else return 0; } //! For use with avltree/map etc. class comparator_default { public: template<typename t_item1,typename t_item2> inline static int compare(const t_item1 & p_item1,const t_item2 & p_item2) {return pfc::compare_t(p_item1,p_item2);} }; class comparator_memcmp { public: template<typename t_item1,typename t_item2> inline static int compare(const t_item1 & p_item1,const t_item2 & p_item2) { static_assert<sizeof(t_item1) == sizeof(t_item2)>(); return memcmp(&p_item1,&p_item2,sizeof(t_item1)); } }; template<typename t_source1, typename t_source2> t_size subtract_sorted_lists_calculate_count(const t_source1 & p_source1, const t_source2 & p_source2) { t_size walk1 = 0, walk2 = 0, walk_out = 0; const t_size max1 = p_source1.get_size(), max2 = p_source2.get_size(); for(;;) { int state; if (walk1 < max1 && walk2 < max2) { state = pfc::compare_t(p_source1[walk1],p_source2[walk2]); } else if (walk1 < max1) { state = -1; } else if (walk2 < max2) { state = 1; } else { break; } if (state < 0) walk_out++; if (state <= 0) walk1++; if (state >= 0) walk2++; } return walk_out; } //! Subtracts p_source2 contents from p_source1 and stores result in p_destination. Both source lists must be sorted. //! Note: duplicates will be carried over (and ignored for p_source2). template<typename t_destination, typename t_source1, typename t_source2> void subtract_sorted_lists(t_destination & p_destination,const t_source1 & p_source1, const t_source2 & p_source2) { p_destination.set_size(subtract_sorted_lists_calculate_count(p_source1,p_source2)); t_size walk1 = 0, walk2 = 0, walk_out = 0; const t_size max1 = p_source1.get_size(), max2 = p_source2.get_size(); for(;;) { int state; if (walk1 < max1 && walk2 < max2) { state = pfc::compare_t(p_source1[walk1],p_source2[walk2]); } else if (walk1 < max1) { state = -1; } else if (walk2 < max2) { state = 1; } else { break; } if (state < 0) p_destination[walk_out++] = p_source1[walk1]; if (state <= 0) walk1++; if (state >= 0) walk2++; } } template<typename t_source1, typename t_source2> t_size merge_sorted_lists_calculate_count(const t_source1 & p_source1, const t_source2 & p_source2) { t_size walk1 = 0, walk2 = 0, walk_out = 0; const t_size max1 = p_source1.get_size(), max2 = p_source2.get_size(); for(;;) { int state; if (walk1 < max1 && walk2 < max2) { state = pfc::compare_t(p_source1[walk1],p_source2[walk2]); } else if (walk1 < max1) { state = -1; } else if (walk2 < max2) { state = 1; } else { break; } if (state <= 0) walk1++; if (state >= 0) walk2++; walk_out++; } return walk_out; } //! Merges p_source1 and p_source2, storing content in p_destination. Both source lists must be sorted. //! Note: duplicates will be carried over. template<typename t_destination, typename t_source1, typename t_source2> void merge_sorted_lists(t_destination & p_destination,const t_source1 & p_source1, const t_source2 & p_source2) { p_destination.set_size(merge_sorted_lists_calculate_count(p_source1,p_source2)); t_size walk1 = 0, walk2 = 0, walk_out = 0; const t_size max1 = p_source1.get_size(), max2 = p_source2.get_size(); for(;;) { int state; if (walk1 < max1 && walk2 < max2) { state = pfc::compare_t(p_source1[walk1],p_source2[walk2]); } else if (walk1 < max1) { state = -1; } else if (walk2 < max2) { state = 1; } else { break; } if (state < 0) { p_destination[walk_out] = p_source1[walk1++]; } else if (state > 0) { p_destination[walk_out] = p_source2[walk2++]; } else { p_destination[walk_out] = p_source1[walk1]; walk1++; walk2++; } walk_out++; } } template<typename t_array,typename T> inline t_size append_t(t_array & p_array,const T & p_item) { t_size old_count = p_array.get_size(); p_array.set_size(old_count + 1); p_array[old_count] = p_item; return old_count; } template<typename t_array,typename T> inline t_size append_swap_t(t_array & p_array,T & p_item) { t_size old_count = p_array.get_size(); p_array.set_size(old_count + 1); swap_t(p_array[old_count],p_item); return old_count; } template<typename t_array,typename T> inline t_size insert_t(t_array & p_array,const T & p_item,t_size p_index) { t_size old_count = p_array.get_size(); if (p_index > old_count) p_index = old_count; p_array.set_size(old_count + 1); for(t_size n=old_count;n>p_index;n--) p_array[n] = p_array[n-1]; p_array[p_index] = p_item; return p_index; } template<typename t_array,typename T> inline t_size insert_swap_t(t_array & p_array,T & p_item,t_size p_index) { t_size old_count = p_array.get_size(); if (p_index > old_count) p_index = old_count; p_array.set_size(old_count + 1); for(t_size n=old_count;n>p_index;n--) swap_t(p_array[n],p_array[n-1]); swap_t(p_array[p_index],p_item); return p_index; } template<typename T> inline T max_t(const T & item1, const T & item2) {return item1 > item2 ? item1 : item2;}; template<typename T> inline T min_t(const T & item1, const T & item2) {return item1 < item2 ? item1 : item2;}; template<typename T> inline T abs_t(T item) {return item<0 ? -item : item;} template<typename T> inline T sqr_t(T item) {return item * item;} template<typename T> inline T clip_t(const T & p_item, const T & p_min, const T & p_max) { if (p_item < p_min) return p_min; else if (p_item <= p_max) return p_item; else return p_max; } template<typename T> inline void delete_t(T* ptr) {delete ptr;} template<typename T> inline void delete_array_t(T* ptr) {delete[] ptr;} template<typename T> inline T* clone_t(T* ptr) {return new T(*ptr);} template<typename t_exception,typename t_int> inline t_int mul_safe_t(t_int p_val1,t_int p_val2) { if (p_val1 == 0 || p_val2 == 0) return 0; t_int temp = (t_int) (p_val1 * p_val2); if (temp < p_val1 || temp < p_val2) throw t_exception(); return temp; } template<typename t_src,typename t_dst> void memcpy_t(t_dst* p_dst,const t_src* p_src,t_size p_count) { for(t_size n=0;n<p_count;n++) p_dst[n] = p_src[n]; } template<typename t_dst,typename t_src> void copy_array_loop_t(t_dst & p_dst,const t_src & p_src,t_size p_count) { for(t_size n=0;n<p_count;n++) p_dst[n] = p_src[n]; } template<typename t_src,typename t_dst> void memcpy_backwards_t(t_dst * p_dst,const t_src * p_src,t_size p_count) { p_dst += p_count; p_src += p_count; for(t_size n=0;n<p_count;n++) *(--p_dst) = *(--p_src); } template<typename T,typename t_val> void memset_t(T * p_buffer,const t_val & p_val,t_size p_count) { for(t_size n=0;n<p_count;n++) p_buffer[n] = p_val; } template<typename T,typename t_val> void memset_t(T &p_buffer,const t_val & p_val) { const t_size width = pfc::array_size_t(p_buffer); for(t_size n=0;n<width;n++) p_buffer[n] = p_val; } template<typename T> void memset_null_t(T * p_buffer,t_size p_count) { for(t_size n=0;n<p_count;n++) p_buffer[n] = 0; } template<typename T> void memset_null_t(T &p_buffer) { const t_size width = pfc::array_size_t(p_buffer); for(t_size n=0;n<width;n++) p_buffer[n] = 0; } template<typename T> void memmove_t(T* p_dst,const T* p_src,t_size p_count) { if (p_dst == p_src) {/*do nothing*/} else if (p_dst > p_src && p_dst < p_src + p_count) memcpy_backwards_t<T>(p_dst,p_src,p_count); else memcpy_t<T>(p_dst,p_src,p_count); } template<typename T> T* new_ptr_check_t(T* p_ptr) { if (p_ptr == NULL) throw std::bad_alloc(); return p_ptr; } template<typename T> int sgn_t(const T & p_val) { if (p_val < 0) return -1; else if (p_val > 0) return 1; else return 0; } template<typename T> const T* empty_string_t(); template<> inline const char * empty_string_t<char>() {return "";} template<> inline const wchar_t * empty_string_t<wchar_t>() {return L"";} template<typename t_type,typename t_newval> t_type replace_t(t_type & p_var,const t_newval & p_newval) { t_type oldval = p_var; p_var = p_newval; return oldval; } template<typename t_type> t_type replace_null_t(t_type & p_var) { t_type ret = p_var; p_var = NULL; return ret; } template<t_size p_size_pow2> inline bool is_ptr_aligned_t(const void * p_ptr) { static_assert_t< (p_size_pow2 & (p_size_pow2 - 1)) == 0 >(); return ( ((t_size)p_ptr) & (p_size_pow2-1) ) == 0; } template<typename t_array> void array_rangecheck_t(const t_array & p_array,t_size p_index) { if (p_index >= pfc::array_size_t(p_array)) throw pfc::exception_overflow(); } template<typename t_array> void array_rangecheck_t(const t_array & p_array,t_size p_from,t_size p_to) { if (p_from > p_to) throw pfc::exception_overflow(); array_rangecheck_t(p_array,p_from); array_rangecheck_t(p_array,p_to); } inline t_int32 rint32(double p_val) {return (t_int32) floor(p_val + 0.5);} inline t_int64 rint64(double p_val) {return (t_int64) floor(p_val + 0.5);} template<typename t_array> inline t_size remove_mask_t(t_array & p_array,const bit_array & p_mask)//returns amount of items left { t_size n,count = p_array.get_size(), total = 0; n = total = p_mask.find(true,0,count); if (n<count) { for(n=p_mask.find(false,n+1,count-n-1);n<count;n=p_mask.find(false,n+1,count-n-1)) swap_t(p_array[total++],p_array[n]); p_array.set_size(total); return total; } else return count; } template<typename t_array,typename t_compare> t_size find_duplicates_sorted_t(t_array p_array,t_size p_count,t_compare p_compare,bit_array_var & p_out) { t_size ret = 0; t_size n; if (p_count > 0) { p_out.set(0,false); for(n=1;n<p_count;n++) { bool found = p_compare(p_array[n-1],p_array[n]) == 0; if (found) ret++; p_out.set(n,found); } } return ret; } template<typename t_array,typename t_compare,typename t_permutation> t_size find_duplicates_sorted_permutation_t(t_array p_array,t_size p_count,t_compare p_compare,t_permutation const & p_permutation,bit_array_var & p_out) { t_size ret = 0; t_size n; if (p_count > 0) { p_out.set(p_permutation[0],false); for(n=1;n<p_count;n++) { bool found = p_compare(p_array[p_permutation[n-1]],p_array[p_permutation[n]]) == 0; if (found) ret++; p_out.set(p_permutation[n],found); } } return ret; } template<typename t_char> t_size strlen_t(const t_char * p_string,t_size p_length = infinite) { for(t_size walk = 0;;walk++) { if (walk >= p_length || p_string[walk] == 0) return walk; } } template<typename t_array> class __list_to_array_enumerator { public: __list_to_array_enumerator(t_array & p_array) : m_array(p_array), m_walk(0) {} template<typename t_item> void operator() (const t_item & p_item) { PFC_ASSERT(m_walk < m_array.get_size()); m_array[m_walk++] = p_item; } void finalize() { PFC_ASSERT(m_walk == m_array.get_size()); } private: t_size m_walk; t_array & m_array; }; template<typename t_list,typename t_array> void list_to_array(t_array & p_array,const t_list & p_list) { p_array.set_size(p_list.get_count()); __list_to_array_enumerator<t_array> enumerator(p_array); p_list.enumerate(enumerator); enumerator.finalize(); } template<typename t_receiver> class enumerator_add_item { public: enumerator_add_item(t_receiver & p_receiver) : m_receiver(p_receiver) {} template<typename t_item> void operator() (const t_item & p_item) {m_receiver.add_item(p_item);} private: t_receiver & m_receiver; }; template<typename t_receiver,typename t_giver> void overwrite_list_enumerated(t_receiver & p_receiver,const t_giver & p_giver) { enumerator_add_item<t_receiver> wrapper(p_receiver); p_giver.enumerate(wrapper); } template<typename t_receiver,typename t_giver> void copy_list_enumerated(t_receiver & p_receiver,const t_giver & p_giver) { p_receiver.remove_all(); overwrite_list_enumerated(p_receiver,p_giver); } }; #define PFC_CLASS_NOT_COPYABLE(THISCLASSNAME,THISTYPE) \ private: \ THISCLASSNAME(const THISTYPE&) {throw pfc::exception_bug_check();} \ const THISTYPE & operator=(const THISTYPE &) {throw pfc::exception_bug_check();} ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/printf.cpp ================================================ #include "pfc.h" //implementations of deprecated string_printf methods, with a pragma to disable warnings when they reference other deprecated methods. #pragma warning(disable:4996) namespace pfc { void string_printf::run(const char * fmt,va_list list) {g_run(*this,fmt,list);} string_printf_va::string_printf_va(const char * fmt,va_list list) {string_printf::g_run(*this,fmt,list);} void string_printf::g_run(string_base & out,const char * fmt,va_list list) { out.reset(); while(*fmt) { if (*fmt=='%') { fmt++; if (*fmt=='%') { out.add_char('%'); fmt++; } else { bool force_sign = false; if (*fmt=='+') { force_sign = true; fmt++; } char padchar = (*fmt == '0') ? '0' : ' '; t_size pad = 0; while(*fmt>='0' && *fmt<='9') { pad = pad * 10 + (*fmt - '0'); fmt++; } if (*fmt=='s' || *fmt=='S') { const char * ptr = va_arg(list,const char*); t_size len = strlen(ptr); if (pad>len) out.add_chars(padchar,pad-len); out.add_string(ptr); fmt++; } else if (*fmt=='i' || *fmt=='I' || *fmt=='d' || *fmt=='D') { char temp[8*sizeof(int)]; int val = va_arg(list,int); if (force_sign && val>0) out.add_char('+'); _itoa_s(val,temp,10); t_size len = strlen(temp); if (pad>len) out.add_chars(padchar,pad-len); out.add_string(temp); fmt++; } else if (*fmt=='u' || *fmt=='U') { char temp[8*sizeof(int)]; int val = va_arg(list,int); if (force_sign && val>0) out.add_char('+'); _ultoa_s(val,temp,10); t_size len = strlen(temp); if (pad>len) out.add_chars(padchar,pad-len); out.add_string(temp); fmt++; } else if (*fmt=='x' || *fmt=='X') { char temp[8*sizeof(int)]; int val = va_arg(list,int); if (force_sign && val>0) out.add_char('+'); _ultoa_s(val,temp,16); if (*fmt=='X') { char * t = temp; while(*t) { if (*t>='a' && *t<='z') *t += 'A' - 'a'; t++; } } t_size len = strlen(temp); if (pad>len) out.add_chars(padchar,pad-len); out.add_string(temp); fmt++; } else if (*fmt=='c' || *fmt=='C') { out.add_char(va_arg(list,char)); fmt++; } } } else { out.add_char(*(fmt++)); } } } string_printf::string_printf(const char * fmt,...) { va_list list; va_start(list,fmt); run(fmt,list); va_end(list); } } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/profiler.cpp ================================================ #include "pfc.h" #ifdef _WINDOWS namespace pfc { profiler_static::profiler_static(const char * p_name) { name = p_name; total_time = 0; num_called = 0; } profiler_static::~profiler_static() { try { pfc::string_fixed_t<511> message; message << "profiler: " << pfc::format_pad_left<pfc::string_fixed_t<127> >(48,' ',name) << " - " << pfc::format_pad_right<pfc::string_fixed_t<128> >(16,' ',pfc::format_uint(total_time) ) << " cycles"; if (num_called > 0) { message << " (executed " << num_called << " times, " << (total_time / num_called) << " average)"; } message << "\n"; OutputDebugStringA(message); } catch(...) { //should never happen OutputDebugString(_T("unexpected profiler failure\n")); } } } #else //PORTME #endif ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/profiler.h ================================================ #ifndef _PFC_PROFILER_H_ #define _PFC_PROFILER_H_ #ifdef _WINDOWS #include <intrin.h> namespace pfc { class profiler_static { public: profiler_static(const char * p_name); ~profiler_static(); void add_time(t_int64 delta) {total_time+=delta;num_called++;} private: const char * name; t_uint64 total_time,num_called; }; class profiler_local { public: profiler_local(profiler_static * p_owner) { owner = p_owner; start = __rdtsc(); } ~profiler_local() { t_int64 end = __rdtsc(); owner->add_time(end-start); } private: t_int64 start; profiler_static * owner; }; #define profiler(name) \ static pfc::profiler_static profiler_static_##name(#name); \ pfc::profiler_local profiler_local_##name(&profiler_static_##name); class hires_timer { public: void start() { m_start = g_query(); } double query() const { return _query( g_query() ); } double query_reset() { t_uint64 current = g_query(); double ret = _query(current); m_start = current; return ret; } private: double _query(t_uint64 p_val) const { return (double)( p_val - m_start ) / (double) g_query_freq(); } static t_uint64 g_query() { LARGE_INTEGER val; if (!QueryPerformanceCounter(&val)) throw pfc::exception_not_implemented(); return val.QuadPart; } static t_uint64 g_query_freq() { LARGE_INTEGER val; if (!QueryPerformanceFrequency(&val)) throw pfc::exception_not_implemented(); return val.QuadPart; } t_uint64 m_start; }; class lores_timer { public: void start() { _start(GetTickCount()); } double query() const { return _query(GetTickCount()); } double query_reset() { t_uint32 time = GetTickCount(); double ret = _query(time); _start(time); return ret; } private: void _start(t_uint32 p_time) {m_last_seen = m_start = p_time;} double _query(t_uint32 p_time) const { t_uint64 time = p_time; if (time < (m_last_seen & 0xFFFFFFFF)) time += 0x100000000; m_last_seen = (m_last_seen & 0xFFFFFFFF00000000) + time; return (double)(m_last_seen - m_start) / 1000.0; } t_uint64 m_start; mutable t_uint64 m_last_seen; }; } #else //PORTME #endif #endif ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/ptr_list.h ================================================ #ifndef __PFC_PTR_LIST_H_ #define __PFC_PTR_LIST_H_ namespace pfc { template<class T, class B = list_t<T*> > class ptr_list_t : public B { public: ptr_list_t() {} ptr_list_t(const ptr_list_t<T> & p_source) {*this = p_source;} void free_by_idx(t_size n) {free_mask(bit_array_one(n));} void free_all() {this->remove_all_ex(free);} void free_mask(const bit_array & p_mask) {this->remove_mask_ex(p_mask,free);} void delete_item(T* ptr) {delete_by_idx(find_item(ptr));} void delete_by_idx(t_size p_index) { delete_mask(bit_array_one(p_index)); } void delete_all() { this->remove_all_ex(pfc::delete_t<T>); } void delete_mask(const bit_array & p_mask) { this->remove_mask_ex(p_mask,pfc::delete_t<T>); } T * operator[](t_size n) const {return this->get_item(n);} }; template<typename T,t_size N> class ptr_list_hybrid_t : public ptr_list_t<T,list_hybrid_t<T*,N> > { public: ptr_list_hybrid_t() {} ptr_list_hybrid_t(const ptr_list_hybrid_t<T,N> & p_source) {*this = p_source;} }; typedef ptr_list_t<void> ptr_list; } #endif //__PFC_PTR_LIST_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/rcptr.h ================================================ namespace pfc { class rc_container_base { public: long add_ref() { return ++m_counter; } long release() { long ret = --m_counter; if (ret == 0) delete this; return ret; } protected: virtual ~rc_container_base() {} private: refcounter m_counter; }; template<typename t_object> class rc_container_t : public rc_container_base { public: TEMPLATE_CONSTRUCTOR_FORWARD_FLOOD(rc_container_t,m_object) t_object m_object; }; template<typename t_object> class rcptr_const_t { protected: typedef rc_container_base t_container; typedef rc_container_t<t_object> t_container_impl; private: typedef rcptr_const_t<t_object> t_self; public: rcptr_const_t() : m_container(NULL), m_ptr(NULL) {} rcptr_const_t(const t_self & p_source) : m_container(NULL), m_ptr(NULL) {*this = p_source;} void __set_from_cast(t_container * p_container,t_object * p_ptr) { release(); p_container->add_ref(); m_container = p_container; m_ptr = p_ptr; } bool is_valid() const {return m_container != NULL;} bool is_empty() const {return m_container == NULL;} t_self const & operator=(const t_self & p_source) { release(); if (p_source.is_valid()) { p_source.m_container->add_ref(); m_container = p_source.m_container; m_ptr = p_source.m_ptr; } return *this; } ~rcptr_const_t() {release();} void release() { t_container * temp = m_container; m_ptr = NULL; m_container = NULL; if (temp != NULL) temp->release(); } const t_object & operator*() const {return *m_ptr;} const t_object * operator->() const {return m_ptr;} template<typename t_object_cast> operator rcptr_const_t<t_object_cast>() const { rcptr_const_t<t_object_cast> temp; if (is_valid()) temp.__set_from_cast(m_container,m_ptr); return temp; } template<typename t_object_cast> rcptr_const_t<t_object_cast> static_cast_t() const { rcptr_const_t<t_object_cast> temp; if (is_valid()) temp.__set_from_cast(m_container,static_cast<t_object_cast*>(m_ptr)); return temp; } bool operator==(const t_self & p_other) const { return m_container == p_other.m_container; } bool operator!=(const t_self & p_other) const { return m_container != p_other.m_container; } protected: t_container * m_container; t_object * m_ptr; }; template<typename t_object> class rcptr_t : public rcptr_const_t<t_object> { private: typedef rcptr_t<t_object> t_self; protected: typedef rc_container_base t_container; typedef rc_container_t<t_object> t_container_impl; public: t_self const & operator=(const t_self & p_source) { this->release(); if (p_source.is_valid()) { p_source.m_container->add_ref(); this->m_container = p_source.m_container; this->m_ptr = p_source.m_ptr; } return *this; } template<typename t_object_cast> operator rcptr_t<t_object_cast>() const { rcptr_t<t_object_cast> temp; if (is_valid()) temp.__set_from_cast(this->m_container,this->m_ptr); return temp; } template<typename t_object_cast> rcptr_t<t_object_cast> static_cast_t() const { rcptr_t<t_object_cast> temp; if (is_valid()) temp.__set_from_cast(this->m_container,static_cast<t_object_cast*>(this->m_ptr)); return temp; } void new_t() { on_new(new t_container_impl()); } template<typename t_param1> void new_t(t_param1 const & p_param1) { on_new(new t_container_impl(p_param1)); } template<typename t_param1,typename t_param2> void new_t(t_param1 const & p_param1, t_param2 const & p_param2) { on_new(new t_container_impl(p_param1,p_param2)); } template<typename t_param1,typename t_param2,typename t_param3> void new_t(t_param1 const & p_param1, t_param2 const & p_param2,t_param3 const & p_param3) { on_new(new t_container_impl(p_param1,p_param2,p_param3)); } template<typename t_param1,typename t_param2,typename t_param3,typename t_param4> void new_t(t_param1 const & p_param1, t_param2 const & p_param2,t_param3 const & p_param3,t_param4 const & p_param4) { on_new(new t_container_impl(p_param1,p_param2,p_param3,p_param4)); } template<typename t_param1,typename t_param2,typename t_param3,typename t_param4,typename t_param5> void new_t(t_param1 const & p_param1, t_param2 const & p_param2,t_param3 const & p_param3,t_param4 const & p_param4,t_param5 const & p_param5) { on_new(new t_container_impl(p_param1,p_param2,p_param3,p_param4,p_param5)); } template<typename t_param1,typename t_param2,typename t_param3,typename t_param4,typename t_param5,typename t_param6> void new_t(t_param1 const & p_param1, t_param2 const & p_param2,t_param3 const & p_param3,t_param4 const & p_param4,t_param5 const & p_param5,t_param6 const & p_param6) { on_new(new t_container_impl(p_param1,p_param2,p_param3,p_param4,p_param5,p_param6)); } static t_self g_new_t() { t_self temp; temp.new_t(); return temp; } template<typename t_param1> static t_self g_new_t(t_param1 const & p_param1) { t_self temp; temp.new_t(p_param1); return temp; } template<typename t_param1,typename t_param2> static t_self g_new_t(t_param1 const & p_param1,t_param2 const & p_param2) { t_self temp; temp.new_t(p_param1,p_param2); return temp; } template<typename t_param1,typename t_param2,typename t_param3> static t_self g_new_t(t_param1 const & p_param1,t_param2 const & p_param2,t_param3 const & p_param3) { t_self temp; temp.new_t(p_param1,p_param2,p_param3); return temp; } template<typename t_param1,typename t_param2,typename t_param3,typename t_param4> static t_self g_new_t(t_param1 const & p_param1,t_param2 const & p_param2,t_param3 const & p_param3,t_param4 const & p_param4) { t_self temp; temp.new_t(p_param1,p_param2,p_param3,p_param4); return temp; } template<typename t_param1,typename t_param2,typename t_param3,typename t_param4,typename t_param5> static t_self g_new_t(t_param1 const & p_param1,t_param2 const & p_param2,t_param3 const & p_param3,t_param4 const & p_param4,t_param5 const & p_param5) { t_self temp; temp.new_t(p_param1,p_param2,p_param3,p_param4,p_param5); return temp; } t_object & operator*() const {return *this->m_ptr;} t_object * operator->() const {return this->m_ptr;} private: void on_new(t_container_impl * p_container) { this->release(); p_container->add_ref(); this->m_ptr = &p_container->m_object; this->m_container = p_container; } }; template<typename t_object> rcptr_t<t_object> rcnew_t() { rcptr_t<t_object> temp; temp.new_t(); return temp; } template<typename t_object,typename t_param1> rcptr_t<t_object> rcnew_t(t_param1 const & p_param1) { rcptr_t<t_object> temp; temp.new_t(p_param1); return temp; } template<typename t_object,typename t_param1,typename t_param2> rcptr_t<t_object> rcnew_t(t_param1 const & p_param1,t_param2 const & p_param2) { rcptr_t<t_object> temp; temp.new_t(p_param1,p_param2); return temp; } template<typename t_object,typename t_param1,typename t_param2,typename t_param3> rcptr_t<t_object> rcnew_t(t_param1 const & p_param1,t_param2 const & p_param2,t_param3 const & p_param3) { rcptr_t<t_object> temp; temp.new_t(p_param1,p_param2,p_param3); return temp; } template<typename t_object,typename t_param1,typename t_param2,typename t_param3,typename t_param4> rcptr_t<t_object> rcnew_t(t_param1 const & p_param1,t_param2 const & p_param2,t_param3 const & p_param3,t_param4 const & p_param4) { rcptr_t<t_object> temp; temp.new_t(p_param1,p_param2,p_param3,p_param4); return temp; } template<typename t_object,typename t_param1,typename t_param2,typename t_param3,typename t_param4,typename t_param5> rcptr_t<t_object> rcnew_t(t_param1 const & p_param1,t_param2 const & p_param2,t_param3 const & p_param3,t_param4 const & p_param4,t_param5 const & p_param5) { rcptr_t<t_object> temp; temp.new_t(p_param1,p_param2,p_param3,p_param4,p_param5); return temp; } template<typename t_object,typename t_param1,typename t_param2,typename t_param3,typename t_param4,typename t_param5,typename t_param6> rcptr_t<t_object> rcnew_t(t_param1 const & p_param1,t_param2 const & p_param2,t_param3 const & p_param3,t_param4 const & p_param4,t_param5 const & p_param5,t_param6 const & p_param6) { rcptr_t<t_object> temp; temp.new_t(p_param1,p_param2,p_param3,p_param4,p_param5,p_param6); return temp; } class traits_rcptr : public traits_default { public: enum { realloc_safe = true, constructor_may_fail = false }; }; template<typename T> class traits_t<rcptr_const_t<T> > : public traits_rcptr {}; template<typename T> class traits_t<rcptr_t<T> > : public traits_rcptr {}; } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/ref_counter.h ================================================ namespace pfc { class NOVTABLE refcounted_object_root { public: void refcount_add_ref() {++m_counter;} void refcount_release() {if (--m_counter == 0) delete this;} protected: refcounted_object_root() {} virtual ~refcounted_object_root() {} private: refcounter m_counter; }; template<typename T> class refcounted_object_ptr_t { private: typedef refcounted_object_ptr_t<T> t_self; public: inline refcounted_object_ptr_t() : m_ptr(NULL) {} inline refcounted_object_ptr_t(T* p_ptr) : m_ptr(NULL) {copy(p_ptr);} inline refcounted_object_ptr_t(const t_self & p_source) : m_ptr(NULL) {copy(p_source);} template<typename t_source> inline refcounted_object_ptr_t(t_source * p_ptr) : m_ptr(NULL) {copy(p_ptr);} template<typename t_source> inline refcounted_object_ptr_t(const refcounted_object_ptr_t<t_source> & p_source) : m_ptr(NULL) {copy(p_source);} inline ~refcounted_object_ptr_t() {if (m_ptr != NULL) m_ptr->refcount_release();} template<typename t_source> inline void copy(t_source * p_ptr) { T* torel = pfc::replace_t(m_ptr,pfc::safe_ptr_cast<T>(p_ptr)); if (m_ptr != NULL) m_ptr->refcount_add_ref(); if (torel != NULL) torel->refcount_release(); } template<typename t_source> inline void copy(const refcounted_object_ptr_t<t_source> & p_source) {copy(p_source.get_ptr());} inline const t_self & operator=(const t_self & p_source) {copy(p_source); return *this;} inline const t_self & operator=(T * p_ptr) {copy(p_ptr); return *this;} template<typename t_source> inline t_self & operator=(const refcounted_object_ptr_t<t_source> & p_source) {copy(p_source); return *this;} template<typename t_source> inline t_self & operator=(t_source * p_ptr) {copy(p_ptr); return *this;} inline void release() { T * temp = pfc::replace_t(m_ptr,(T*)NULL); if (temp != NULL) temp->refcount_release(); } inline T* operator->() const {PFC_ASSERT(m_ptr != NULL);return m_ptr;} inline T* get_ptr() const {return m_ptr;} inline bool is_valid() const {return m_ptr != NULL;} inline bool is_empty() const {return m_ptr == NULL;} inline bool operator==(const t_self & p_item) const {return m_ptr == p_item.get_ptr();} inline bool operator!=(const t_self & p_item) const {return m_ptr != p_item.get_ptr();} inline bool operator>(const t_self & p_item) const {return m_ptr > p_item.get_ptr();} inline bool operator<(const t_self & p_item) const {return m_ptr < p_item.get_ptr();} inline T* __unsafe_duplicate() const//should not be used ! temporary ! { if (m_ptr) m_ptr->refcount_add_ref(); return m_ptr; } inline T* __unsafe_detach() { T* ret = m_ptr; m_ptr = 0; return ret; } inline void __unsafe_set(T * p_ptr) {//should not be used ! temporary ! release(); m_ptr = p_ptr; } private: T* m_ptr; }; template<typename T> class traits_t<refcounted_object_ptr_t<T> > : public traits_default { public: enum { realloc_safe = true, constructor_may_fail = false}; }; }; ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/selftest.cpp ================================================ #include "pfc.h" static void selftest() //never called, testing done at compile time { pfc::static_assert_t<sizeof(t_uint8) == 1>(); pfc::static_assert_t<sizeof(t_uint16) == 2>(); pfc::static_assert_t<sizeof(t_uint32) == 4>(); pfc::static_assert_t<sizeof(t_uint64) == 8>(); pfc::static_assert_t<sizeof(t_int8) == 1>(); pfc::static_assert_t<sizeof(t_int16) == 2>(); pfc::static_assert_t<sizeof(t_int32) == 4>(); pfc::static_assert_t<sizeof(t_int64) == 8>(); pfc::static_assert_t<sizeof(t_float32) == 4>(); pfc::static_assert_t<sizeof(t_float64) == 8>(); pfc::static_assert_t<sizeof(t_size) == sizeof(void*)>(); pfc::static_assert_t<sizeof(t_ssize) == sizeof(void*)>(); pfc::static_assert_t<sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4>(); pfc::static_assert_t<sizeof(GUID) == 16>(); } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/sort.cpp ================================================ #include "pfc.h" namespace pfc { PFC_DLL_EXPORT void swap_void(void * item1,void * item2,t_size width) { unsigned char * ptr1 = (unsigned char*)item1, * ptr2 = (unsigned char*)item2; t_size n; unsigned char temp; for(n=0;n<width;n++) { temp = *ptr2; *ptr2 = *ptr1; *ptr1 = temp; ptr1++; ptr2++; } } PFC_DLL_EXPORT void reorder(reorder_callback & p_callback,const t_size * p_order,t_size p_count) { t_size done_size = bit_array_bittable::g_estimate_size(p_count); pfc::array_hybrid_t<unsigned char,1024> done; done.set_size(done_size); pfc::memset_t(done,(unsigned char)0); t_size n; for(n=0;n<p_count;n++) { t_size next = p_order[n]; if (next!=n && !bit_array_bittable::g_get(done,n)) { t_size prev = n; do { assert(!bit_array_bittable::g_get(done,next)); assert(next>n); assert(n<p_count); p_callback.swap(prev,next); bit_array_bittable::g_set(done,next,true); prev = next; next = p_order[next]; } while(next!=n); //bit_array_bittable::g_set(done,n,true); } } } PFC_DLL_EXPORT void reorder_void(void * data,t_size width,const t_size * order,t_size num,void (*swapfunc)(void * item1,void * item2,t_size width)) { unsigned char * base = (unsigned char *) data; t_size done_size = bit_array_bittable::g_estimate_size(num); pfc::array_hybrid_t<unsigned char,1024> done; done.set_size(done_size); pfc::memset_t(done,(unsigned char)0); t_size n; for(n=0;n<num;n++) { t_size next = order[n]; if (next!=n && !bit_array_bittable::g_get(done,n)) { t_size prev = n; do { assert(!bit_array_bittable::g_get(done,next)); assert(next>n); assert(n<num); swapfunc(base+width*prev,base+width*next,width); bit_array_bittable::g_set(done,next,true); prev = next; next = order[next]; } while(next!=n); //bit_array_bittable::g_set(done,n,true); } } } namespace { class sort_callback_impl_legacy : public sort_callback { public: sort_callback_impl_legacy( void * p_base,t_size p_width, int (*p_comp)(const void *, const void *), void (*p_swap)(void *, void *, t_size) ) : m_base((char*)p_base), m_width(p_width), m_comp(p_comp), m_swap(p_swap) { } int compare(t_size p_index1, t_size p_index2) const { return m_comp(m_base + p_index1 * m_width, m_base + p_index2 * m_width); } void swap(t_size p_index1, t_size p_index2) { m_swap(m_base + p_index1 * m_width, m_base + p_index2 * m_width, m_width); } private: char * m_base; t_size m_width; int (*m_comp)(const void *, const void *); void (*m_swap)(void *, void *, t_size); }; } PFC_DLL_EXPORT void sort_void_ex ( void *base, t_size num, t_size width, int (*comp)(const void *, const void *), void (*swap)(void *, void *, t_size) ) { sort(sort_callback_impl_legacy(base,width,comp,swap),num); } static void squaresort(pfc::sort_callback & p_callback,t_size const p_base,t_size const p_count) { const t_size max = p_base + p_count; for(t_size walk = p_base + 1; walk < max; ++walk) { for(t_size prev = p_base; prev < walk; ++prev) { p_callback.swap_check(prev,walk); } } } inline static void __sort_2elem_helper(pfc::sort_callback & p_callback,t_size & p_elem1,t_size & p_elem2) { if (p_callback.compare(p_elem1,p_elem2) > 0) pfc::swap_t(p_elem1,p_elem2); } inline static t_size __pivot_helper(pfc::sort_callback & p_callback,t_size const p_base,t_size const p_count) { PFC_ASSERT(p_count > 1); //take middle element from lowest/middle/highest ones in original order. todo: try to come up with smarter approach? t_size val1 = p_base, val2 = p_base + (p_count / 2), val3 = p_base + (p_count - 1); __sort_2elem_helper(p_callback,val1,val2); __sort_2elem_helper(p_callback,val1,val3); __sort_2elem_helper(p_callback,val2,val3); return val2; } static void newsort(pfc::sort_callback & p_callback,t_size const p_base,t_size const p_count) { if (p_count <= 4) { squaresort(p_callback,p_base,p_count); return; } t_size pivot = __pivot_helper(p_callback,p_base,p_count); { const t_size target = p_base + p_count - 1; if (pivot != target) { p_callback.swap(pivot,target); pivot = target; } } t_size partition = p_base; { bool asdf = false; for(t_size walk = p_base; walk < pivot; ++walk) { const int comp = p_callback.compare(walk,pivot); bool trigger = false; if (comp == 0) { trigger = asdf; asdf = !asdf; } else if (comp < 0) { trigger = true; } if (trigger) { if (partition != walk) p_callback.swap(partition,walk); partition++; } } } if (pivot != partition) { p_callback.swap(pivot,partition); pivot = partition; } newsort(p_callback,p_base,pivot-p_base); newsort(p_callback,pivot+1,p_count-(pivot+1-p_base)); } void sort(pfc::sort_callback & p_callback,t_size p_num) { newsort(p_callback,0,p_num); } PFC_DLL_EXPORT void sort_void(void * base,t_size num,t_size width,int (*comp)(const void *, const void *) ) { sort_void_ex(base,num,width,comp,swap_void); } sort_callback_stabilizer::sort_callback_stabilizer(sort_callback & p_chain,t_size p_count) : m_chain(p_chain) { m_order.set_size(p_count); t_size n; for(n=0;n<p_count;n++) m_order[n] = n; } int sort_callback_stabilizer::compare(t_size p_index1, t_size p_index2) const { int ret = m_chain.compare(p_index1,p_index2); if (ret == 0) ret = pfc::sgn_t((t_ssize)m_order[p_index1] - (t_ssize)m_order[p_index2]); return ret; } void sort_callback_stabilizer::swap(t_size p_index1, t_size p_index2) { m_chain.swap(p_index1,p_index2); pfc::swap_t(m_order[p_index1],m_order[p_index2]); } PFC_DLL_EXPORT void sort_stable(sort_callback & p_callback,t_size p_count) { sort(sort_callback_stabilizer(p_callback,p_count),p_count); } } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/sort.h ================================================ namespace pfc { PFC_DLL_EXPORT void swap_void(void * item1,void * item2,t_size width); PFC_DLL_EXPORT void reorder_void(void * data,t_size width,const t_size * order,t_size num,void (*swapfunc)(void * item1,void * item2,t_size width) = swap_void); class NOVTABLE reorder_callback { public: virtual void swap(t_size p_index1,t_size p_index2) = 0; }; PFC_DLL_EXPORT void reorder(reorder_callback & p_callback,const t_size * p_order,t_size p_count); template<typename t_container> class reorder_callback_impl_t : public reorder_callback { public: reorder_callback_impl_t(t_container & p_data) : m_data(p_data) {} void swap(t_size p_index1,t_size p_index2) { pfc::swap_t(m_data[p_index1],m_data[p_index2]); } private: t_container & m_data; }; class reorder_callback_impl_delta : public reorder_callback { public: reorder_callback_impl_delta(reorder_callback & p_data,t_size p_delta) : m_data(p_data), m_delta(p_delta) {} void swap(t_size p_index1,t_size p_index2) { m_data.swap(p_index1+m_delta,p_index2+m_delta); } private: reorder_callback & m_data; t_size m_delta; }; template<typename t_container> void reorder_t(t_container & p_data,const t_size * p_order,t_size p_count) { reorder(reorder_callback_impl_t<t_container>(p_data),p_order,p_count); } template<typename t_container> void reorder_partial_t(t_container & p_data,t_size p_base,const t_size * p_order,t_size p_count) { reorder(reorder_callback_impl_delta(reorder_callback_impl_t<t_container>(p_data),p_base),p_order,p_count); } template<typename T> class reorder_callback_impl_ptr_t : public reorder_callback { public: reorder_callback_impl_ptr_t(T * p_data) : m_data(p_data) {} void swap(t_size p_index1,t_size p_index2) { pfc::swap_t(m_data[p_index1],m_data[p_index2]); } private: T* m_data; }; template<typename T> void reorder_ptr_t(T* p_data,const t_size * p_order,t_size p_count) { reorder(reorder_callback_impl_ptr_t<T>(p_data),p_order,p_count); } class NOVTABLE sort_callback { public: virtual int compare(t_size p_index1, t_size p_index2) const = 0; virtual void swap(t_size p_index1, t_size p_index2) = 0; void swap_check(t_size p_index1, t_size p_index2) {if (compare(p_index1,p_index2) > 0) swap(p_index1,p_index2);} }; class sort_callback_stabilizer : public sort_callback { public: sort_callback_stabilizer(sort_callback & p_chain,t_size p_count); virtual int compare(t_size p_index1, t_size p_index2) const; virtual void swap(t_size p_index1, t_size p_index2); private: sort_callback & m_chain; array_t<t_size> m_order; }; PFC_DLL_EXPORT void sort(sort_callback & p_callback,t_size p_count); PFC_DLL_EXPORT void sort_stable(sort_callback & p_callback,t_size p_count); PFC_DLL_EXPORT void sort_void_ex(void *base,t_size num,t_size width, int (*comp)(const void *, const void *),void (*swap)(void *, void *, t_size) ); PFC_DLL_EXPORT void sort_void(void * base,t_size num,t_size width,int (*comp)(const void *, const void *) ); template<typename t_container,typename t_compare> class sort_callback_impl_simple_wrap_t : public sort_callback { public: sort_callback_impl_simple_wrap_t(t_container & p_data, t_compare p_compare) : m_data(p_data), m_compare(p_compare) {} int compare(t_size p_index1, t_size p_index2) const { return m_compare(m_data[p_index1],m_data[p_index2]); } void swap(t_size p_index1, t_size p_index2) { swap_t(m_data[p_index1],m_data[p_index2]); } private: t_container & m_data; t_compare m_compare; }; template<typename t_container> class sort_callback_impl_auto_wrap_t : public sort_callback { public: sort_callback_impl_auto_wrap_t(t_container & p_data) : m_data(p_data) {} int compare(t_size p_index1, t_size p_index2) const { return compare_t(m_data[p_index1],m_data[p_index2]); } void swap(t_size p_index1, t_size p_index2) { swap_t(m_data[p_index1],m_data[p_index2]); } private: t_container & m_data; }; template<typename t_container,typename t_compare,typename t_permutation> class sort_callback_impl_permutation_wrap_t : public sort_callback { public: sort_callback_impl_permutation_wrap_t(const t_container & p_data, t_compare p_compare,t_permutation const & p_permutation) : m_data(p_data), m_compare(p_compare), m_permutation(p_permutation) {} int compare(t_size p_index1, t_size p_index2) const { return m_compare(m_data[m_permutation[p_index1]],m_data[m_permutation[p_index2]]); } void swap(t_size p_index1, t_size p_index2) { swap_t(m_permutation[p_index1],m_permutation[p_index2]); } private: const t_container & m_data; t_compare m_compare; t_permutation const & m_permutation; }; template<typename t_container,typename t_compare> static void sort_t(t_container & p_data,t_compare p_compare,t_size p_count) { sort(sort_callback_impl_simple_wrap_t<t_container,t_compare>(p_data,p_compare),p_count); } template<typename t_container,typename t_compare> static void sort_stable_t(t_container & p_data,t_compare p_compare,t_size p_count) { sort_stable(sort_callback_impl_simple_wrap_t<t_container,t_compare>(p_data,p_compare),p_count); } template<typename t_container,typename t_compare,typename t_permutation> static void sort_get_permutation_t(const t_container & p_data,t_compare p_compare,t_size p_count,t_permutation const & p_permutation) { sort(sort_callback_impl_permutation_wrap_t<t_container,t_compare,t_permutation>(p_data,p_compare,p_permutation),p_count); } template<typename t_container,typename t_compare,typename t_permutation> static void sort_stable_get_permutation_t(const t_container & p_data,t_compare p_compare,t_size p_count,t_permutation const & p_permutation) { sort_stable(sort_callback_impl_permutation_wrap_t<t_container,t_compare,t_permutation>(p_data,p_compare,p_permutation),p_count); } } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/stdafx.cpp ================================================ //cpp used to generate precompiled header #include "pfc.h" ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/string.cpp ================================================ #include "pfc.h" namespace pfc { void string_receiver::add_char(t_uint32 p_char) { char temp[8]; t_size len = utf8_encode_char(p_char,temp); if (len>0) add_string(temp,len); } void string_base::skip_trailing_char(unsigned skip) { const char * str = get_ptr(); t_size ptr,trunc; bool need_trunc = false; for(ptr=0;str[ptr];) { unsigned c; t_size delta = utf8_decode_char(str+ptr,&c); if (delta==0) break; if (c==skip) { need_trunc = true; trunc = ptr; } else { need_trunc = false; } ptr += delta; } if (need_trunc) truncate(trunc); } format_time::format_time(t_uint64 p_seconds) { t_uint64 length = p_seconds; unsigned weeks,days,hours,minutes,seconds; weeks = (unsigned)( ( length / (60*60*24*7) ) ); days = (unsigned)( ( length / (60*60*24) ) % 7 ); hours = (unsigned) ( ( length / (60 * 60) ) % 24); minutes = (unsigned) ( ( length / (60 ) ) % 60 ); seconds = (unsigned) ( ( length ) % 60 ); if (weeks) { m_buffer << weeks << "wk "; } if (days || weeks) { m_buffer << days << "d "; } if (hours || days || weeks) { m_buffer << hours << ":" << format_uint(minutes,2) << ":" << format_uint(seconds,2); } else { m_buffer << minutes << ":" << format_uint(seconds,2); } } int strcmp_partial(const char * p_string,const char * p_substring) {return strcmp_partial_ex(p_string,infinite,p_substring,infinite);} static int __strcmp_partial_ex(const char * p_string,t_size p_string_length,const char * p_substring,t_size p_substring_length) throw() { for(t_size walk=0;walk<p_substring_length;walk++) { char stringchar = (walk>=p_string_length ? 0 : p_string[walk]); char substringchar = p_substring[walk]; int result = compare_t(stringchar,substringchar); if (result != 0) return result; } return 0; } int strcmp_partial_ex(const char * p_string,t_size p_string_length,const char * p_substring,t_size p_substring_length) /*throw()*/ { p_string_length = strlen_max(p_string,p_string_length); p_substring_length = strlen_max(p_substring,p_substring_length); return __strcmp_partial_ex(p_string,p_string_length,p_substring,p_substring_length); } bool is_path_separator(unsigned c) { return c=='\\' || c=='/' || c=='|' || c==':'; } bool is_path_bad_char(unsigned c) { #ifdef _WINDOWS return c=='\\' || c=='/' || c=='|' || c==':' || c=='*' || c=='?' || c=='\"' || c=='>' || c=='<'; #else #error portme #endif } char * strdup_n(const char * src,t_size len) { len = strlen_max(src,len); char * ret = (char*)malloc(len+1); if (ret) { memcpy(ret,src,len); ret[len]=0; } return ret; } string_filename::string_filename(const char * fn) { fn += pfc::scan_filename(fn); const char * ptr=fn,*dot=0; while(*ptr && *ptr!='?') { if (*ptr=='.') dot=ptr; ptr++; } if (dot && dot>fn) set_string(fn,dot-fn); else set_string(fn); } string_filename_ext::string_filename_ext(const char * fn) { fn += pfc::scan_filename(fn); const char * ptr = fn; while(*ptr && *ptr!='?') ptr++; set_string(fn,ptr-fn); } string_extension::string_extension(const char * src) { buffer[0]=0; const char * start = src + pfc::scan_filename(src); const char * end = start + strlen(start); const char * ptr = end-1; while(ptr>start && *ptr!='.') { if (*ptr=='?') end=ptr; ptr--; } if (ptr>=start && *ptr=='.') { ptr++; t_size len = end-ptr; if (len<tabsize(buffer)) { memcpy(buffer,ptr,len*sizeof(char)); buffer[len]=0; } } } bool has_path_bad_chars(const char * param) { while(*param) { if (is_path_bad_char(*param)) return true; param++; } return false; } void float_to_string(char * out,t_size out_max,double val,unsigned precision,bool b_sign) { pfc::string_fixed_t<63> temp; t_size outptr; if (out_max == 0) return; out_max--;//for null terminator outptr = 0; if (outptr == out_max) {out[outptr]=0;return;} if (val<0) {out[outptr++] = '-'; val = -val;} else if (b_sign) {out[outptr++] = '+';} if (outptr == out_max) {out[outptr]=0;return;} { double powval = pow((double)10.0,(double)precision); temp << (t_int64)floor(val * powval + 0.5); //_i64toa(blargh,temp,10); } const t_size temp_len = temp.length(); if (temp_len <= precision) { out[outptr++] = '0'; if (outptr == out_max) {out[outptr]=0;return;} out[outptr++] = '.'; if (outptr == out_max) {out[outptr]=0;return;} t_size d; for(d=precision-temp_len;d;d--) { out[outptr++] = '0'; if (outptr == out_max) {out[outptr]=0;return;} } for(d=0;d<temp_len;d++) { out[outptr++] = temp[d]; if (outptr == out_max) {out[outptr]=0;return;} } } else { t_size d = temp_len; const char * src = temp; while(*src) { if (d-- == precision) { out[outptr++] = '.'; if (outptr == out_max) {out[outptr]=0;return;} } out[outptr++] = *(src++); if (outptr == out_max) {out[outptr]=0;return;} } } out[outptr] = 0; } static double pfc_string_to_float_internal(const char * src) { bool neg = false; t_int64 val = 0; int div = 0; bool got_dot = false; while(*src==' ') src++; if (*src=='-') {neg = true;src++;} else if (*src=='+') src++; while(*src) { if (*src>='0' && *src<='9') { int d = *src - '0'; val = val * 10 + d; if (got_dot) div--; src++; } else if (*src=='.' || *src==',') { if (got_dot) break; got_dot = true; src++; } else if (*src=='E' || *src=='e') { src++; div += atoi(src); break; } else break; } if (neg) val = -val; return (double) val * pow(10.0,(double)div); } double string_to_float(const char * src,t_size max) { //old function wants an oldstyle nullterminated string, and i don't currently care enough to rewrite it as it works appropriately otherwise char blargh[128]; if (max > 127) max = 127; t_size walk; for(walk = 0; walk < max && src[walk]; walk++) blargh[walk] = src[walk]; blargh[walk] = 0; return pfc_string_to_float_internal(blargh); } void string_base::convert_to_lower_ascii(const char * src,char replace) { reset(); PFC_ASSERT(replace>0); while(*src) { unsigned c; t_size delta = utf8_decode_char(src,&c); if (delta==0) {c = replace; delta = 1;} else if (c>=0x80) c = replace; add_byte((char)c); src += delta; } } void convert_to_lower_ascii(const char * src,t_size max,char * out,char replace) { t_size ptr = 0; PFC_ASSERT(replace>0); while(ptr<max && src[ptr]) { unsigned c; t_size delta = utf8_decode_char(src+ptr,&c,max-ptr); if (delta==0) {c = replace; delta = 1;} else if (c>=0x80) c = replace; *(out++) = (char)c; ptr += delta; } *out = 0; } t_size strstr_ex(const char * p_string,t_size p_string_len,const char * p_substring,t_size p_substring_len) { p_string_len = strlen_max(p_string,p_string_len); p_substring_len = strlen_max(p_substring,p_substring_len); t_size index = 0; while(index + p_substring_len <= p_string_len) { if (memcmp(p_string+index,p_substring,p_substring_len) == 0) return index; t_size delta = utf8_char_len(p_string+index,p_string_len - index); if (delta == 0) break; index += delta; } return ~0; } unsigned atoui_ex(const char * p_string,t_size p_string_len) { unsigned ret = 0; t_size ptr = 0; while(ptr<p_string_len) { char c = p_string[ptr]; if (! ( c >= '0' && c <= '9' ) ) break; ret = ret * 10 + (unsigned)( c - '0' ); ptr++; } return ret; } int strcmp_ex(const char* p1,t_size n1,const char* p2,t_size n2) { t_size idx = 0; n1 = strlen_max(p1,n1); n2 = strlen_max(p2,n2); for(;;) { if (idx == n1 && idx == n2) return 0; else if (idx == n1) return -1;//end of param1 else if (idx == n2) return 1;//end of param2 char c1 = p1[idx], c2 = p2[idx]; if (c1<c2) return -1; else if (c1>c2) return 1; idx++; } } t_uint64 atoui64_ex(const char * src,t_size len) { len = strlen_max(src,len); t_uint64 ret = 0, mul = 1; t_size ptr = len; t_size start = 0; // start += skip_spacing(src+start,len-start); while(ptr>start) { char c = src[--ptr]; if (c>='0' && c<='9') { ret += (c-'0') * mul; mul *= 10; } else { ret = 0; mul = 1; } } return ret; } t_int64 atoi64_ex(const char * src,t_size len) { len = strlen_max(src,len); t_int64 ret = 0, mul = 1; t_size ptr = len; t_size start = 0; bool neg = false; // start += skip_spacing(src+start,len-start); if (start < len && src[start] == '-') {neg = true; start++;} // start += skip_spacing(src+start,len-start); while(ptr>start) { char c = src[--ptr]; if (c>='0' && c<='9') { ret += (c-'0') * mul; mul *= 10; } else { ret = 0; mul = 1; } } return neg ? -ret : ret; } int stricmp_ascii_ex(const char * const s1,t_size const len1,const char * const s2,t_size const len2) { t_size walk1 = 0, walk2 = 0; for(;;) { char c1 = (walk1 < len1) ? s1[walk1] : 0; char c2 = (walk2 < len2) ? s2[walk2] : 0; c1 = ascii_tolower(c1); c2 = ascii_tolower(c2); if (c1<c2) return -1; else if (c1>c2) return 1; else if (c1 == 0) return 0; walk1++; walk2++; } } int stricmp_ascii(const char * s1,const char * s2) { for(;;) { char c1 = ascii_tolower(*s1), c2 = ascii_tolower(*s2); if (c1<c2) return -1; else if (c1>c2) return 1; else if (c1 == 0) return 0; s1++; s2++; } } format_float::format_float(double p_val,unsigned p_width,unsigned p_prec) { char temp[64]; float_to_string(temp,64,p_val,p_prec,false); temp[63] = 0; t_size len = strlen(temp); if (len < p_width) m_buffer.add_chars(' ',p_width-len); m_buffer += temp; } static char format_hex_char(unsigned p_val) { PFC_ASSERT(p_val < 16); return (p_val < 10) ? p_val + '0' : p_val - 10 + 'A'; } format_hex::format_hex(t_uint64 p_val,unsigned p_width) { if (p_width > 16) p_width = 16; else if (p_width == 0) p_width = 1; char temp[16]; unsigned n; for(n=0;n<16;n++) { temp[15-n] = format_hex_char((unsigned)(p_val & 0xF)); p_val >>= 4; } for(n=0;n<16 && temp[n] == '0';n++) {} if (n > 16 - p_width) n = 16 - p_width; char * out = m_buffer; for(;n<16;n++) *(out++) = temp[n]; *out = 0; } static char format_hex_char_lowercase(unsigned p_val) { PFC_ASSERT(p_val < 16); return (p_val < 10) ? p_val + '0' : p_val - 10 + 'a'; } format_hex_lowercase::format_hex_lowercase(t_uint64 p_val,unsigned p_width) { if (p_width > 16) p_width = 16; else if (p_width == 0) p_width = 1; char temp[16]; unsigned n; for(n=0;n<16;n++) { temp[15-n] = format_hex_char_lowercase((unsigned)(p_val & 0xF)); p_val >>= 4; } for(n=0;n<16 && temp[n] == '0';n++) {} if (n > 16 - p_width) n = 16 - p_width; char * out = m_buffer; for(;n<16;n++) *(out++) = temp[n]; *out = 0; } format_uint::format_uint(t_uint64 val,unsigned p_width,unsigned p_base) { enum {max_width = tabsize(m_buffer) - 1}; if (p_width > max_width) p_width = max_width; else if (p_width == 0) p_width = 1; char temp[max_width]; unsigned n; for(n=0;n<max_width;n++) { temp[max_width-1-n] = format_hex_char((unsigned)(val % p_base)); val /= p_base; } for(n=0;n<max_width && temp[n] == '0';n++) {} if (n > max_width - p_width) n = max_width - p_width; char * out = m_buffer; for(;n<max_width;n++) *(out++) = temp[n]; *out = 0; } format_fixedpoint::format_fixedpoint(t_int64 p_val,unsigned p_point) { unsigned div = 1; for(unsigned n=0;n<p_point;n++) div *= 10; if (p_val < 0) {m_buffer << "-";p_val = -p_val;} m_buffer << format_int(p_val / div) << "." << format_int(p_val % div, p_point); } format_int::format_int(t_int64 p_val,unsigned p_width,unsigned p_base) { bool neg = false; t_uint64 val; if (p_val < 0) {neg = true; val = (t_uint64)(-p_val);} else val = (t_uint64)p_val; enum {max_width = tabsize(m_buffer) - 1}; if (p_width > max_width) p_width = max_width; else if (p_width == 0) p_width = 1; if (neg && p_width > 1) p_width --; char temp[max_width]; unsigned n; for(n=0;n<max_width;n++) { temp[max_width-1-n] = format_hex_char((unsigned)(val % p_base)); val /= p_base; } for(n=0;n<max_width && temp[n] == '0';n++) {} if (n > max_width - p_width) n = max_width - p_width; char * out = m_buffer; if (neg) *(out++) = '-'; for(;n<max_width;n++) *(out++) = temp[n]; *out = 0; } format_hexdump_lowercase::format_hexdump_lowercase(const void * p_buffer,t_size p_bytes,const char * p_spacing) { t_size n; const t_uint8 * buffer = (const t_uint8*)p_buffer; for(n=0;n<p_bytes;n++) { if (n > 0 && p_spacing != 0) m_formatter << p_spacing; m_formatter << format_hex_lowercase(buffer[n],2); } } format_hexdump::format_hexdump(const void * p_buffer,t_size p_bytes,const char * p_spacing) { t_size n; const t_uint8 * buffer = (const t_uint8*)p_buffer; for(n=0;n<p_bytes;n++) { if (n > 0 && p_spacing != 0) m_formatter << p_spacing; m_formatter << format_hex(buffer[n],2); } } string_replace_extension::string_replace_extension(const char * p_path,const char * p_ext) { m_data = p_path; t_size dot = m_data.find_last('.'); if (dot < m_data.scan_filename()) {//argh m_data += "."; m_data += p_ext; } else { m_data.truncate(dot+1); m_data += p_ext; } } string_directory::string_directory(const char * p_path) { t_size ptr = scan_filename(p_path); if (ptr > 0) m_data.set_string(p_path,ptr-1); } t_size scan_filename(const char * ptr) { t_size n; t_size _used = strlen(ptr); for(n=_used-1;n!=~0;n--) { if (is_path_separator(ptr[n])) return n+1; } return 0; } t_size string_find_first(const char * p_string,char p_tofind,t_size p_start) { return string_find_first_ex(p_string,infinite,&p_tofind,1,p_start); } t_size string_find_last(const char * p_string,char p_tofind,t_size p_start) { return string_find_last_ex(p_string,infinite,&p_tofind,1,p_start); } t_size string_find_first(const char * p_string,const char * p_tofind,t_size p_start) { return string_find_first_ex(p_string,infinite,p_tofind,infinite,p_start); } t_size string_find_last(const char * p_string,const char * p_tofind,t_size p_start) { return string_find_last_ex(p_string,infinite,p_tofind,infinite,p_start); } t_size string_find_first_ex(const char * p_string,t_size p_string_length,char p_tofind,t_size p_start) { return string_find_first_ex(p_string,p_string_length,&p_tofind,1,p_start); } t_size string_find_last_ex(const char * p_string,t_size p_string_length,char p_tofind,t_size p_start) { return string_find_last_ex(p_string,p_string_length,&p_tofind,1,p_start); } t_size string_find_first_ex(const char * p_string,t_size p_string_length,const char * p_tofind,t_size p_tofind_length,t_size p_start) { p_string_length = strlen_max(p_string,p_string_length); p_tofind_length = strlen_max(p_tofind,p_tofind_length); if (p_string_length >= p_tofind_length) { t_size max = p_string_length - p_tofind_length; for(t_size walk = p_start; walk <= max; walk++) { if (__strcmp_partial_ex(p_string+walk,p_string_length-walk,p_tofind,p_tofind_length) == 0) return walk; } } return infinite; } t_size string_find_last_ex(const char * p_string,t_size p_string_length,const char * p_tofind,t_size p_tofind_length,t_size p_start) { p_string_length = strlen_max(p_string,p_string_length); p_tofind_length = strlen_max(p_tofind,p_tofind_length); if (p_string_length >= p_tofind_length) { t_size max = min_t<t_size>(p_string_length - p_tofind_length,p_start); for(t_size walk = max; walk != (t_size)(-1); walk--) { if (__strcmp_partial_ex(p_string+walk,p_string_length-walk,p_tofind,p_tofind_length) == 0) return walk; } } return infinite; } bool string_is_numeric(const char * p_string,t_size p_length) { bool retval = false; for(t_size walk = 0; walk < p_length && p_string[walk] != 0; walk++) { if (!char_is_numeric(p_string[walk])) {retval = false; break;} retval = true; } return retval; } void string_base::fix_dir_separator(char p_char) { t_size length = get_length(); if (length == 0 || get_ptr()[length-1] != p_char) add_byte(p_char); } bool is_multiline(const char * p_string,t_size p_len) { for(t_size n = 0; n < p_len && p_string[n]; n++) { switch(p_string[n]) { case '\r': case '\n': return true; } } return false; } static t_uint64 pow10_helper(unsigned p_extra) { t_uint64 ret = 1; for(unsigned n = 0; n < p_extra; n++ ) ret *= 10; return ret; } format_time_ex::format_time_ex(double p_seconds,unsigned p_extra) { t_uint64 pow10 = pow10_helper(p_extra); t_uint64 ticks = pfc::rint64(pow10 * p_seconds); m_buffer << pfc::format_time(ticks / pow10); if (p_extra>0) { m_buffer << "." << pfc::format_uint(ticks % pow10, p_extra); } } } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/string.h ================================================ #ifndef _PFC_STRING_H_ #define _PFC_STRING_H_ namespace pfc { class NOVTABLE string_receiver { public: virtual void add_string(const char * p_string,t_size p_string_size = infinite) = 0; void add_char(t_uint32 c);//adds unicode char to the string void add_byte(char c) {add_string(&c,1);} void add_chars(t_uint32 p_char,t_size p_count) {for(;p_count;p_count--) add_char(p_char);} protected: string_receiver() {} ~string_receiver() {} }; t_size scan_filename(const char * ptr); bool is_path_separator(unsigned c); bool is_path_bad_char(unsigned c); bool is_valid_utf8(const char * param,t_size max = infinite); bool is_lower_ascii(const char * param); bool is_multiline(const char * p_string,t_size p_len = infinite); bool has_path_bad_chars(const char * param); void recover_invalid_utf8(const char * src,char * out,unsigned replace);//out must be enough to hold strlen(char) + 1, or appropiately bigger if replace needs multiple chars void convert_to_lower_ascii(const char * src,t_size max,char * out,char replace = '?');//out should be at least strlen(src)+1 long inline char ascii_tolower(char c) {if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; return c;} inline char ascii_toupper(char c) {if (c >= 'a' && c <= 'z') c += 'A' - 'a'; return c;} t_size string_find_first(const char * p_string,char p_tofind,t_size p_start = 0); //returns infinite if not found t_size string_find_last(const char * p_string,char p_tofind,t_size p_start = ~0); //returns infinite if not found t_size string_find_first(const char * p_string,const char * p_tofind,t_size p_start = 0); //returns infinite if not found t_size string_find_last(const char * p_string,const char * p_tofind,t_size p_start = ~0); //returns infinite if not found t_size string_find_first_ex(const char * p_string,t_size p_string_length,char p_tofind,t_size p_start = 0); //returns infinite if not found t_size string_find_last_ex(const char * p_string,t_size p_string_length,char p_tofind,t_size p_start = ~0); //returns infinite if not found t_size string_find_first_ex(const char * p_string,t_size p_string_length,const char * p_tofind,t_size p_tofind_length,t_size p_start = 0); //returns infinite if not found t_size string_find_last_ex(const char * p_string,t_size p_string_length,const char * p_tofind,t_size p_tofind_length,t_size p_start = ~0); //returns infinite if not found template<typename t_char> t_size strlen_max_t(const t_char * ptr,t_size max) { if (ptr == NULL) return 0; t_size n = 0; while(n<max && ptr[n] != 0) n++; return n; } inline t_size strlen_max(const char * ptr,t_size max) {return strlen_max_t(ptr,max);} inline t_size wcslen_max(const wchar_t * ptr,t_size max) {return strlen_max_t(ptr,max);} #ifdef _WINDOWS inline t_size tcslen_max(const TCHAR * ptr,t_size max) {return strlen_max_t(ptr,max);} #endif bool string_is_numeric(const char * p_string,t_size p_length = infinite); inline bool char_is_numeric(char p_char) {return p_char >= '0' && p_char <= '9';} inline bool char_is_ascii_alpha_upper(char p_char) {return p_char >= 'A' && p_char <= 'Z';} inline bool char_is_ascii_alpha_lower(char p_char) {return p_char >= 'a' && p_char <= 'z';} inline bool char_is_ascii_alpha(char p_char) {return char_is_ascii_alpha_lower(p_char) || char_is_ascii_alpha_upper(p_char);} inline bool char_is_ascii_alphanumeric(char p_char) {return char_is_ascii_alpha(p_char) || char_is_numeric(p_char);} unsigned atoui_ex(const char * ptr,t_size max); t_int64 atoi64_ex(const char * ptr,t_size max); t_uint64 atoui64_ex(const char * ptr,t_size max); t_size strlen_utf8(const char * s,t_size num = ~0);//returns number of characters in utf8 string; num - no. of bytes (optional) t_size utf8_char_len(const char * s,t_size max = ~0);//returns size of utf8 character pointed by s, in bytes, 0 on error t_size utf8_char_len_from_header(char c); t_size utf8_chars_to_bytes(const char * string,t_size count); t_size strcpy_utf8_truncate(const char * src,char * out,t_size maxbytes); t_size utf8_decode_char(const char * src,unsigned * out,t_size src_bytes = ~0);//returns length in bytes t_size utf8_encode_char(unsigned c,char * out);//returns used length in bytes, max 6 t_size utf16_decode_char(const wchar_t * p_source,unsigned * p_out,t_size p_source_length = ~0); t_size utf16_encode_char(unsigned c,wchar_t * out); t_size strstr_ex(const char * p_string,t_size p_string_len,const char * p_substring,t_size p_substring_len); int strcmp_partial(const char * p_string,const char * p_substring); int strcmp_partial_ex(const char * p_string,t_size p_string_length,const char * p_substring,t_size p_substring_length); t_size skip_utf8_chars(const char * ptr,t_size count); char * strdup_n(const char * src,t_size len); int stricmp_ascii(const char * s1,const char * s2); int stricmp_ascii_ex(const char * s1,t_size len1,const char * s2,t_size len2); int strcmp_ex(const char* p1,t_size n1,const char* p2,t_size n2); unsigned utf8_get_char(const char * src); inline bool utf8_advance(const char * & var) { t_size delta = utf8_char_len(var); var += delta; return delta>0; } inline bool utf8_advance(char * & var) { t_size delta = utf8_char_len(var); var += delta; return delta>0; } inline const char * utf8_char_next(const char * src) {return src + utf8_char_len(src);} inline char * utf8_char_next(char * src) {return src + utf8_char_len(src);} class NOVTABLE string_base : public pfc::string_receiver { public: virtual const char * get_ptr() const = 0; virtual void add_string(const char * p_string,t_size p_length = ~0) = 0;//same as string_receiver method virtual void set_string(const char * p_string,t_size p_length = ~0) {reset();add_string(p_string,p_length);} virtual void truncate(t_size len)=0; virtual t_size get_length() const {return strlen(get_ptr());} virtual char * lock_buffer(t_size p_requested_length) = 0; virtual void unlock_buffer() = 0; //! For compatibility with old conventions. inline t_size length() const {return get_length();} inline void reset() {truncate(0);} inline bool is_empty() const {return *get_ptr()==0;} void skip_trailing_char(unsigned c = ' '); bool is_valid_utf8() {return pfc::is_valid_utf8(get_ptr());} void convert_to_lower_ascii(const char * src,char replace = '?'); inline const string_base & operator= (const char * src) {set_string(src);return *this;} inline const string_base & operator+= (const char * src) {add_string(src);return *this;} inline const string_base & operator= (const string_base & src) {set_string(src);return *this;} inline const string_base & operator+= (const string_base & src) {add_string(src);return *this;} inline operator const char * () const {return get_ptr();} t_size scan_filename() const {return pfc::scan_filename(get_ptr());} t_size find_first(char p_char,t_size p_start = 0) {return pfc::string_find_first(get_ptr(),p_char,p_start);} t_size find_last(char p_char,t_size p_start = ~0) {return pfc::string_find_last(get_ptr(),p_char,p_start);} t_size find_first(const char * p_string,t_size p_start = 0) {return pfc::string_find_first(get_ptr(),p_string,p_start);} t_size find_last(const char * p_string,t_size p_start = ~0) {return pfc::string_find_last(get_ptr(),p_string,p_start);} void fix_dir_separator(char p_char); protected: string_base() {} ~string_base() {} }; template<t_size max_length> class string_fixed_t : public pfc::string_base { public: inline string_fixed_t() {init();} inline string_fixed_t(const string_fixed_t<max_length> & p_source) {init(); *this = p_source;} inline string_fixed_t(const char * p_source) {init(); set_string(p_source);} inline const string_fixed_t<max_length> & operator=(const string_fixed_t<max_length> & p_source) {set_string(p_source);return *this;} inline const string_fixed_t<max_length> & operator=(const char * p_source) {set_string(p_source);return *this;} char * lock_buffer(t_size p_requested_length) { if (p_requested_length >= max_length) return NULL; memset(m_data,0,sizeof(m_data)); return m_data; } void unlock_buffer() { m_length = strlen(m_data); } inline operator const char * () const {return m_data;} const char * get_ptr() const {return m_data;} void add_string(const char * ptr,t_size len) { len = strlen_max(ptr,len); if (m_length + len < m_length || m_length + len > max_length) throw pfc::exception_overflow(); for(t_size n=0;n<len;n++) { m_data[m_length++] = ptr[n]; } m_data[m_length] = 0; } void truncate(t_size len) { if (len > max_length) len = max_length; if (m_length > len) { m_length = len; m_data[len] = 0; } } t_size get_length() const {return m_length;} private: inline void init() { pfc::static_assert<(max_length>1)>(); m_length = 0; m_data[0] = 0; } t_size m_length; char m_data[max_length+1]; }; template<template<typename> class t_alloc> class string8_t : public pfc::string_base { private: typedef string8_t<t_alloc> t_self; protected: pfc::array_t<char,t_alloc> m_data; t_size used; inline void makespace(t_size s) { t_size old_size = m_data.get_size(); if (old_size < s) m_data.set_size(s+16); else if (old_size > s + 32) m_data.set_size(s); } inline const char * __get_ptr() const throw() {return used > 0 ? m_data.get_ptr() : "";} public: inline const t_self & operator= (const char * src) {set_string(src);return *this;} inline const t_self & operator+= (const char * src) {add_string(src);return *this;} inline const t_self & operator= (const string_base & src) {set_string(src);return *this;} inline const t_self & operator+= (const string_base & src) {add_string(src);return *this;} inline const t_self & operator= (const t_self & src) {set_string(src);return *this;} inline const t_self & operator+= (const t_self & src) {add_string(src);return *this;} inline operator const char * () const throw() {return __get_ptr();} string8_t() : used(0) {} string8_t(const char * p_string) : used(0) {set_string(p_string);} string8_t(const char * p_string,t_size p_length) : used(0) {set_string(p_string,p_length);} string8_t(const t_self & p_string) : used(0) {set_string(p_string);} string8_t(const string_base & p_string) : used(0) {set_string(p_string);} void prealloc(t_size p_size) {m_data.prealloc(p_size+1);} const char * get_ptr() const throw() {return __get_ptr();} void add_string(const char * p_string,t_size p_length = ~0); void set_string(const char * p_string,t_size p_length = ~0); void truncate(t_size len) { if (used>len) {used=len;m_data[len]=0;makespace(used+1);} } t_size get_length() const throw() {return used;} void set_char(unsigned offset,char c); t_size replace_nontext_chars(char p_replace = '_'); t_size replace_char(unsigned c1,unsigned c2,t_size start = 0); t_size replace_byte(char c1,char c2,t_size start = 0); void fix_filename_chars(char def = '_',char leave=0);//replace "bad" characters, leave can be used to keep eg. path separators void remove_chars(t_size first,t_size count); //slow void insert_chars(t_size first,const char * src, t_size count);//slow void insert_chars(t_size first,const char * src); bool truncate_eol(t_size start = 0); bool fix_eol(const char * append = " (...)",t_size start = 0); bool limit_length(t_size length_in_chars,const char * append = " (...)"); //for string_buffer class char * lock_buffer(t_size n) { makespace(n+1); pfc::memset_t(m_data,(char)0); return m_data.get_ptr();; } void unlock_buffer() { used=strlen(m_data.get_ptr()); makespace(used+1); } void force_reset() {used=0;m_data.force_reset();} inline static void g_swap(t_self & p_item1,t_self & p_item2) { pfc::swap_t(p_item1.m_data,p_item2.m_data); pfc::swap_t(p_item1.used,p_item2.used); } }; typedef string8_t<pfc::alloc_standard> string8; typedef string8_t<pfc::alloc_fast> string8_fast; typedef string8_t<pfc::alloc_fast_aggressive> string8_fast_aggressive; //for backwards compatibility typedef string8_t<pfc::alloc_fast_aggressive> string8_fastalloc; template<template<typename> class t_alloc> class traits_t<string8_t<t_alloc> > : public pfc::combine_traits<pfc::traits_vtable,pfc::traits_t<pfc::array_t<char,t_alloc> > > { public: enum { needs_constructor = true, }; }; } #include "string8_impl.h" #define PFC_DEPRECATE_PRINTF PFC_DEPRECATE("Use string8/string_fixed_t with operator<< overloads instead.") namespace pfc { class string_buffer { private: string_base & m_owner; char * m_buffer; public: explicit string_buffer(string_base & p_string,t_size p_requeted_length) : m_owner(p_string) {m_buffer = m_owner.lock_buffer(p_requeted_length);} ~string_buffer() {m_owner.unlock_buffer();} operator char* () {return m_buffer;} }; class PFC_DEPRECATE_PRINTF string_printf : public string8_fastalloc { public: static void g_run(string_base & out,const char * fmt,va_list list); void run(const char * fmt,va_list list); explicit string_printf(const char * fmt,...); }; class PFC_DEPRECATE_PRINTF string_printf_va : public string8_fastalloc { public: string_printf_va(const char * fmt,va_list list); }; class format_time { public: format_time(t_uint64 p_seconds); const char * get_ptr() const {return m_buffer;} operator const char * () const {return m_buffer;} protected: string_fixed_t<127> m_buffer; }; class format_time_ex { public: format_time_ex(double p_seconds,unsigned p_extra = 3); const char * get_ptr() const {return m_buffer;} operator const char * () const {return m_buffer;} private: string_fixed_t<127> m_buffer; }; class string_filename : public string8 { public: explicit string_filename(const char * fn); }; class string_filename_ext : public string8 { public: explicit string_filename_ext(const char * fn); }; class string_extension { char buffer[32]; public: inline const char * get_ptr() const {return buffer;} inline t_size length() const {return strlen(buffer);} inline operator const char * () const {return buffer;} explicit string_extension(const char * src); }; class string_replace_extension { public: string_replace_extension(const char * p_path,const char * p_ext); inline operator const char*() const {return m_data;} private: string8 m_data; }; class string_directory { public: string_directory(const char * p_path); inline operator const char*() const {return m_data;} private: string8 m_data; }; void float_to_string(char * out,t_size out_max,double val,unsigned precision,bool force_sign = false);//doesnt add E+X etc, has internal range limits, useful for storing float numbers as strings without having to bother with international coma/dot settings BS double string_to_float(const char * src,t_size len = infinite); template<> inline void swap_t(string8 & p_item1,string8 & p_item2) { string8::g_swap(p_item1,p_item2); } class format_float { public: format_float(double p_val,unsigned p_width,unsigned p_prec); format_float(const format_float & p_source) {*this = p_source;} inline const char * get_ptr() const {return m_buffer.get_ptr();} inline operator const char*() const {return m_buffer.get_ptr();} private: string8 m_buffer; }; class format_int { public: format_int(t_int64 p_val,unsigned p_width = 0,unsigned p_base = 10); format_int(const format_int & p_source) {*this = p_source;} inline const char * get_ptr() const {return m_buffer;} inline operator const char*() const {return m_buffer;} private: char m_buffer[64]; }; class format_uint { public: format_uint(t_uint64 p_val,unsigned p_width = 0,unsigned p_base = 10); format_uint(const format_uint & p_source) {*this = p_source;} inline const char * get_ptr() const {return m_buffer;} inline operator const char*() const {return m_buffer;} private: char m_buffer[64]; }; class format_hex { public: format_hex(t_uint64 p_val,unsigned p_width = 0); format_hex(const format_hex & p_source) {*this = p_source;} inline const char * get_ptr() const {return m_buffer;} inline operator const char*() const {return m_buffer;} private: char m_buffer[17]; }; class format_hex_lowercase { public: format_hex_lowercase(t_uint64 p_val,unsigned p_width = 0); format_hex_lowercase(const format_hex_lowercase & p_source) {*this = p_source;} inline const char * get_ptr() const {return m_buffer;} inline operator const char*() const {return m_buffer;} private: char m_buffer[17]; }; typedef string8_fastalloc string_formatter; class format_hexdump { public: format_hexdump(const void * p_buffer,t_size p_bytes,const char * p_spacing = " "); inline const char * get_ptr() const {return m_formatter;} inline operator const char * () const {return m_formatter;} private: string_formatter m_formatter; }; class format_hexdump_lowercase { public: format_hexdump_lowercase(const void * p_buffer,t_size p_bytes,const char * p_spacing = " "); inline const char * get_ptr() const {return m_formatter;} inline operator const char * () const {return m_formatter;} private: string_formatter m_formatter; }; class format_fixedpoint { public: format_fixedpoint(t_int64 p_val,unsigned p_point); inline const char * get_ptr() const {return m_buffer;} inline operator const char*() const {return m_buffer;} private: string_formatter m_buffer; }; class format_char { public: format_char(char p_char) {m_buffer[0] = p_char; m_buffer[1] = 0;} inline const char * get_ptr() const {return m_buffer;} inline operator const char*() const {return m_buffer;} private: char m_buffer[2]; }; template<typename t_stringbuffer = pfc::string8_fastalloc> class format_pad_left { public: format_pad_left(t_size p_chars,t_uint32 p_padding /* = ' ' */,const char * p_string,t_size p_string_length = infinite) { t_size source_len = 0, source_walk = 0; while(source_walk < p_string_length && source_len < p_chars) { unsigned dummy; t_size delta = pfc::utf8_decode_char(p_string + source_walk, &dummy, p_string_length - source_walk); if (delta == 0) break; source_len++; source_walk += delta; } m_buffer.add_string(p_string,source_walk); m_buffer.add_chars(p_padding,p_chars - source_len); } inline const char * get_ptr() const {return m_buffer;} inline operator const char*() const {return m_buffer;} private: t_stringbuffer m_buffer; }; template<typename t_stringbuffer = pfc::string8_fastalloc> class format_pad_right { public: format_pad_right(t_size p_chars,t_uint32 p_padding /* = ' ' */,const char * p_string,t_size p_string_length = infinite) { t_size source_len = 0, source_walk = 0; while(source_walk < p_string_length && source_len < p_chars) { unsigned dummy; t_size delta = pfc::utf8_decode_char(p_string + source_walk, &dummy, p_string_length - source_walk); if (delta == 0) break; source_len++; source_walk += delta; } m_buffer.add_chars(p_padding,p_chars - source_len); m_buffer.add_string(p_string,source_walk); } inline const char * get_ptr() const {return m_buffer;} inline operator const char*() const {return m_buffer;} private: t_stringbuffer m_buffer; }; } inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const char * p_source) {p_fmt.add_string(p_source); return p_fmt;} inline pfc::string_base & operator<<(pfc::string_base & p_fmt,t_int32 p_val) {return p_fmt << pfc::format_int(p_val);} inline pfc::string_base & operator<<(pfc::string_base & p_fmt,t_uint32 p_val) {return p_fmt << pfc::format_uint(p_val);} inline pfc::string_base & operator<<(pfc::string_base & p_fmt,t_int64 p_val) {return p_fmt << pfc::format_int(p_val);} inline pfc::string_base & operator<<(pfc::string_base & p_fmt,t_uint64 p_val) {return p_fmt << pfc::format_uint(p_val);} inline pfc::string_base & operator<<(pfc::string_base & p_fmt,double p_val) {return p_fmt << pfc::format_float(p_val,0,7);} inline pfc::string_base & operator<<(pfc::string_base & p_fmt,std::exception const & p_exception) {return p_fmt << p_exception.what();} namespace pfc { template<typename t_char> class string_simple_t { private: typedef string_simple_t<t_char> t_self; public: t_size length(t_size p_limit = infinite) const {return pfc::strlen_t(get_ptr(),infinite);} bool is_empty() const {return length(1) == 0;} void set_string(const t_char * p_source,t_size p_length = infinite) { t_size length = pfc::strlen_t(p_source,p_length); m_buffer.set_size(length + 1); pfc::memcpy_t(m_buffer.get_ptr(),p_source,length); m_buffer[length] = 0; } string_simple_t() {} string_simple_t(const t_char * p_source,t_size p_length = infinite) {set_string(p_source,p_length);} const t_self & operator=(const t_char * p_source) {set_string(p_source);return *this;} operator const t_char* () const {return get_ptr();} const t_char * get_ptr() const {return m_buffer.get_size() > 0 ? m_buffer.get_ptr() : pfc::empty_string_t<t_char>();} private: pfc::array_t<t_char> m_buffer; }; typedef string_simple_t<char> string_simple; template<typename t_char> class traits_t<string_simple_t<t_char> > : public traits_t<array_t<t_char> > {}; } namespace pfc { //for tree/map classes class comparator_strcmp { public: inline static int compare(const char * p_item1,const char * p_item2) {return strcmp(p_item1,p_item2);} }; class comparator_stricmp_ascii { public: inline static int compare(const char * p_item1,const char * p_item2) {return pfc::stricmp_ascii(p_item1,p_item2);} }; } #endif //_PFC_STRING_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/string8_impl.h ================================================ namespace pfc { template<template<typename> class t_alloc> void string8_t<t_alloc>::add_string(const char * ptr,t_size len) { if (len > 0 && ptr >= m_data.get_ptr() && ptr <= m_data.get_ptr() + m_data.get_size()) { add_string(string8(ptr,len)); } else { len = strlen_max(ptr,len); makespace(used+len+1); pfc::memcpy_t(m_data.get_ptr() + used,ptr,len); used+=len; m_data[used]=0; true; } } template<template<typename> class t_alloc> void string8_t<t_alloc>::set_string(const char * ptr,t_size len) { if (len > 0 && ptr >= m_data.get_ptr() && ptr < m_data.get_ptr() + m_data.get_size()) { set_string(string8(ptr,len)); } else { len = strlen_max(ptr,len); makespace(len+1); pfc::memcpy_t(m_data.get_ptr(),ptr,len); used=len; m_data[used]=0; } } template<template<typename> class t_alloc> void string8_t<t_alloc>::set_char(unsigned offset,char c) { if (!c) truncate(offset); else if (offset<used) m_data[offset]=c; } template<template<typename> class t_alloc> void string8_t<t_alloc>::fix_filename_chars(char def,char leave)//replace "bad" characters, leave can be used to keep eg. path separators { t_size n; for(n=0;n<used;n++) if (m_data[n]!=leave && pfc::is_path_bad_char(m_data[n])) m_data[n]=def; } template<template<typename> class t_alloc> void string8_t<t_alloc>::remove_chars(t_size first,t_size count) { if (first>used) first = used; if (first+count>used) count = used-first; if (count>0) { t_size n; for(n=first+count;n<=used;n++) m_data[n-count]=m_data[n]; used -= count; makespace(used+1); } } template<template<typename> class t_alloc> void string8_t<t_alloc>::insert_chars(t_size first,const char * src, t_size count) { if (first > used) first = used; makespace(used+count+1); t_size n; for(n=used;(int)n>=(int)first;n--) m_data[n+count] = m_data[n]; for(n=0;n<count;n++) m_data[first+n] = src[n]; used+=count; } template<template<typename> class t_alloc> void string8_t<t_alloc>::insert_chars(t_size first,const char * src) {insert_chars(first,src,strlen(src));} template<template<typename> class t_alloc> bool string8_t<t_alloc>::truncate_eol(t_size start) { t_size n; const char * ptr = m_data.get_ptr() + start; for(n=start;n<used;n++) { if (*ptr==10 || *ptr==13) { truncate(n); return true; } ptr++; } return false; } template<template<typename> class t_alloc> bool string8_t<t_alloc>::fix_eol(const char * append,t_size start) { bool rv = truncate_eol(start); if (rv) add_string(append); return rv; } template<template<typename> class t_alloc> bool string8_t<t_alloc>::limit_length(t_size length_in_chars,const char * append) { bool rv = false; const char * base = get_ptr(), * ptr = base; while(length_in_chars && utf8_advance(ptr)) length_in_chars--; if (length_in_chars==0) { truncate(ptr-base); add_string(append); rv = true; } return rv; } template<template<typename> class t_alloc> t_size string8_t<t_alloc>::replace_nontext_chars(char p_replace) { t_size ret = 0; for(t_size n=0;n<used;n++) { if ((unsigned char)m_data[n] < 32) {m_data[n] = p_replace; ret++; } } return ret; } template<template<typename> class t_alloc> t_size string8_t<t_alloc>::replace_byte(char c1,char c2,t_size start) { PFC_ASSERT(c1 != 0); PFC_ASSERT(c2 != 0); t_size n, ret = 0; for(n=start;n<used;n++) { if (m_data[n] == c1) {m_data[n] = c2; ret++;} } return ret; } template<template<typename> class t_alloc> t_size string8_t<t_alloc>::replace_char(unsigned c1,unsigned c2,t_size start) { if (c1 < 128 && c2 < 128) return replace_byte((char)c1,(char)c2,start); string8 temp(get_ptr()+start); truncate(start); const char * ptr = temp; t_size rv = 0; while(*ptr) { unsigned test; t_size delta = utf8_decode_char(ptr,&test); if (delta==0 || test==0) break; if (test == c1) {test = c2;rv++;} add_char(test); ptr += delta; } return rv; } } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/string_conv.cpp ================================================ #include "pfc.h" #ifdef _WINDOWS namespace { template<typename t_char> class string_writer_t { public: string_writer_t(t_char * p_buffer,t_size p_size) : m_buffer(p_buffer), m_size(p_size), m_writeptr(0) {} void write(t_char p_char) { if (m_writeptr < m_size) { m_buffer[m_writeptr++] = p_char; } } void write_multi(const t_char * p_buffer,t_size p_count) { const t_size delta = pfc::min_t<t_size>(p_count,m_size-m_writeptr); for(t_size n=0;n<delta;n++) { m_buffer[m_writeptr++] = p_buffer[n]; } } void write_as_utf8(unsigned p_char) { char temp[6]; t_size n = pfc::utf8_encode_char(p_char,temp); write_multi(temp,n); } void write_as_wide(unsigned p_char) { wchar_t temp[2]; t_size n = pfc::utf16_encode_char(p_char,temp); write_multi(temp,n); } t_size finalize() { if (m_size == 0) return 0; t_size terminator = pfc::min_t<t_size>(m_writeptr,m_size-1); m_buffer[terminator] = 0; return terminator; } bool is_overrun() const { return m_writeptr >= m_size; } private: t_char * m_buffer; t_size m_size; t_size m_writeptr; }; }; namespace pfc { namespace stringcvt { t_size convert_utf8_to_wide(wchar_t * p_out,t_size p_out_size,const char * p_in,t_size p_in_size) { const t_size insize = p_in_size; t_size inptr = 0; string_writer_t<wchar_t> writer(p_out,p_out_size); while(inptr < insize && !writer.is_overrun()) { unsigned newchar = 0; t_size delta = utf8_decode_char(p_in + inptr,&newchar,insize - inptr); if (delta == 0 || newchar == 0) break; PFC_ASSERT(inptr + delta <= insize); inptr += delta; writer.write_as_wide(newchar); } return writer.finalize(); } t_size convert_wide_to_utf8(char * p_out,t_size p_out_size,const wchar_t * p_in,t_size p_in_size) { const t_size insize = p_in_size; t_size inptr = 0; string_writer_t<char> writer(p_out,p_out_size); while(inptr < insize && !writer.is_overrun()) { unsigned newchar = 0; t_size delta = utf16_decode_char(p_in + inptr,&newchar,insize - inptr); if (delta == 0 || newchar == 0) break; PFC_ASSERT(inptr + delta <= insize); inptr += delta; writer.write_as_utf8(newchar); } return writer.finalize(); } t_size estimate_utf8_to_wide(const char * p_in,t_size p_in_size) { const t_size insize = p_in_size; t_size inptr = 0; t_size retval = 1;//1 for null terminator while(inptr < insize) { unsigned newchar = 0; t_size delta = utf8_decode_char(p_in + inptr,&newchar,insize - inptr); if (delta == 0 || newchar == 0) break; PFC_ASSERT(inptr + delta <= insize); inptr += delta; { wchar_t temp[2]; delta = utf16_encode_char(newchar,temp); if (delta == 0) break; retval += delta; } } return retval; } t_size estimate_wide_to_utf8(const wchar_t * p_in,t_size p_in_size) { const t_size insize = p_in_size; t_size inptr = 0; t_size retval = 1;//1 for null terminator while(inptr < insize) { unsigned newchar = 0; t_size delta = utf16_decode_char(p_in + inptr,&newchar,insize - inptr); if (delta == 0 || newchar == 0) break; PFC_ASSERT(inptr + delta <= insize); inptr += delta; { char temp[6]; delta = utf8_encode_char(newchar,temp); if (delta == 0) break; retval += delta; } } return retval; } t_size convert_codepage_to_wide(unsigned p_codepage,wchar_t * p_out,t_size p_out_size,const char * p_source,t_size p_source_size) { if (p_out_size == 0) return 0; memset(p_out,0,p_out_size * sizeof(*p_out)); MultiByteToWideChar(p_codepage,0,p_source,p_source_size,p_out,p_out_size); p_out[p_out_size-1] = 0; return wcslen(p_out); } t_size convert_wide_to_codepage(unsigned p_codepage,char * p_out,t_size p_out_size,const wchar_t * p_source,t_size p_source_size) { if (p_out_size == 0) return 0; memset(p_out,0,p_out_size * sizeof(*p_out)); WideCharToMultiByte(p_codepage,0,p_source,p_source_size,p_out,p_out_size,0,FALSE); p_out[p_out_size-1] = 0; return strlen(p_out); } t_size estimate_codepage_to_wide(unsigned p_codepage,const char * p_source,t_size p_source_size) { return MultiByteToWideChar(p_codepage,0,p_source,strlen_max(p_source,p_source_size),0,0) + 1; } t_size estimate_wide_to_codepage(unsigned p_codepage,const wchar_t * p_source,t_size p_source_size) { return WideCharToMultiByte(p_codepage,0,p_source,wcslen_max(p_source,p_source_size),0,0,0,FALSE) + 1; } } } #endif //_WINDOWS ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/string_conv.h ================================================ namespace pfc { namespace stringcvt { #ifdef _WINDOWS enum { codepage_system = CP_ACP, codepage_ascii = 20127, codepage_iso_8859_1 = 28591, }; //! Converts UTF-8 characters to wide character. //! @param p_out Output buffer, receives converted string, with null terminator. //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. //! @param p_source String to convert. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @returns Number of characters written, not counting null terminator. t_size convert_utf8_to_wide(wchar_t * p_out,t_size p_out_size,const char * p_source,t_size p_source_size); //! Estimates buffer size required to convert specified UTF-8 string to widechar. //! @param p_source String to be converted. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @returns Number of characters to allocate, including space for null terminator. t_size estimate_utf8_to_wide(const char * p_source,t_size p_source_size); //! Converts wide character string to UTF-8. //! @param p_out Output buffer, receives converted string, with null terminator. //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. //! @param p_source String to convert. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @returns Number of characters written, not counting null terminator. t_size convert_wide_to_utf8(char * p_out,t_size p_out_size,const wchar_t * p_source,t_size p_source_size); //! Estimates buffer size required to convert specified wide character string to UTF-8. //! @param p_source String to be converted. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @returns Number of characters to allocate, including space for null terminator. t_size estimate_wide_to_utf8(const wchar_t * p_source,t_size p_source_size); //! Converts string from specified codepage to wide character. //! @param p_out Output buffer, receives converted string, with null terminator. //! @param p_codepage Codepage ID of source string. //! @param p_source String to convert. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. //! @returns Number of characters written, not counting null terminator. t_size convert_codepage_to_wide(unsigned p_codepage,wchar_t * p_out,t_size p_out_size,const char * p_source,t_size p_source_size); //! Estimates buffer size required to convert specified string from specified codepage to wide character. //! @param p_codepage Codepage ID of source string. //! @param p_source String to be converted. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @returns Number of characters to allocate, including space for null terminator. t_size estimate_codepage_to_wide(unsigned p_codepage,const char * p_source,t_size p_source_size); //! Converts string from wide character to specified codepage. //! @param p_codepage Codepage ID of source string. //! @param p_out Output buffer, receives converted string, with null terminator. //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. //! @param p_source String to convert. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @returns Number of characters written, not counting null terminator. t_size convert_wide_to_codepage(unsigned p_codepage,char * p_out,t_size p_out_size,const wchar_t * p_source,t_size p_source_size); //! Estimates buffer size required to convert specified wide character string to specified codepage. //! @param p_codepage Codepage ID of source string. //! @param p_source String to be converted. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @returns Number of characters to allocate, including space for null terminator. t_size estimate_wide_to_codepage(unsigned p_codepage,const wchar_t * p_source,t_size p_source_size); //! Converts string from system codepage to wide character. //! @param p_out Output buffer, receives converted string, with null terminator. //! @param p_source String to convert. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. //! @returns Number of characters written, not counting null terminator. inline t_size convert_ansi_to_wide(wchar_t * p_out,t_size p_out_size,const char * p_source,t_size p_source_size) { return convert_codepage_to_wide(codepage_system,p_out,p_out_size,p_source,p_source_size); } //! Estimates buffer size required to convert specified system codepage string to wide character. //! @param p_source String to be converted. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @returns Number of characters to allocate, including space for null terminator. inline t_size estimate_ansi_to_wide(const char * p_source,t_size p_source_size) { return estimate_codepage_to_wide(codepage_system,p_source,p_source_size); } //! Converts string from wide character to system codepage. //! @param p_out Output buffer, receives converted string, with null terminator. //! @param p_out_size Size of output buffer, in characters. If converted string is too long, it will be truncated. Null terminator is always written, unless p_out_size is zero. //! @param p_source String to convert. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @returns Number of characters written, not counting null terminator. inline t_size convert_wide_to_ansi(char * p_out,t_size p_out_size,const wchar_t * p_source,t_size p_source_size) { return convert_wide_to_codepage(codepage_system,p_out,p_out_size,p_source,p_source_size); } //! Estimates buffer size required to convert specified wide character string to system codepage. //! @param p_source String to be converted. //! @param p_source_size Number of characters to read from p_source. If reading stops if null terminator is encountered earlier. //! @returns Number of characters to allocate, including space for null terminator. inline t_size estimate_wide_to_ansi(const wchar_t * p_source,t_size p_source_size) { return estimate_wide_to_codepage(codepage_system,p_source,p_source_size); } template<typename t_char> const t_char * null_string_t(); template<> inline const char * null_string_t<char>() {return "";} template<> inline const wchar_t * null_string_t<wchar_t>() {return L"";} template<typename t_char> t_size strlen_t(const t_char * p_string,t_size p_string_size = ~0) { for(t_size n=0;n<p_string_size;n++) { if (p_string[n] == 0) return n; } return p_string_size; } template<typename t_char> bool string_is_empty_t(const t_char * p_string,t_size p_string_size = ~0) { if (p_string_size == 0) return true; return p_string[0] == 0; } template<typename t_char,template<typename t_allocitem> class t_alloc = pfc::alloc_standard> class char_buffer_t { public: char_buffer_t() {} char_buffer_t(const char_buffer_t & p_source) : m_buffer(p_source.m_buffer) {} void set_size(t_size p_count) {m_buffer.set_size(p_count);} t_char * get_ptr_var() {return m_buffer.get_ptr();} const t_char * get_ptr() const { return m_buffer.get_size() > 0 ? m_buffer.get_ptr() : null_string_t<t_char>(); } private: pfc::array_t<t_char,t_alloc> m_buffer; }; template<template<typename t_allocitem> class t_alloc = pfc::alloc_standard> class string_utf8_from_wide_t { public: string_utf8_from_wide_t() {} string_utf8_from_wide_t(const wchar_t * p_source,t_size p_source_size = ~0) {convert(p_source,p_source_size);} void convert(const wchar_t * p_source,t_size p_source_size = ~0) { t_size size = estimate_wide_to_utf8(p_source,p_source_size); m_buffer.set_size(size); convert_wide_to_utf8( m_buffer.get_ptr_var(),size,p_source,p_source_size); } operator const char * () const {return get_ptr();} const char * get_ptr() const {return m_buffer.get_ptr();} bool is_empty() const {return string_is_empty_t(get_ptr());} t_size length() const {return strlen_t(get_ptr());} private: char_buffer_t<char,t_alloc> m_buffer; }; typedef string_utf8_from_wide_t<> string_utf8_from_wide; template<template<typename t_allocitem> class t_alloc = pfc::alloc_standard> class string_wide_from_utf8_t { public: string_wide_from_utf8_t() {} string_wide_from_utf8_t(const string_wide_from_utf8_t<t_alloc> & p_source) : m_buffer(p_source.m_buffer) {} string_wide_from_utf8_t(const char* p_source,t_size p_source_size = ~0) {convert(p_source,p_source_size);} void convert(const char* p_source,t_size p_source_size = ~0) { t_size size = estimate_utf8_to_wide(p_source,p_source_size); m_buffer.set_size(size); convert_utf8_to_wide( m_buffer.get_ptr_var(),size,p_source,p_source_size); } operator const wchar_t * () const {return get_ptr();} const wchar_t * get_ptr() const {return m_buffer.get_ptr();} bool is_empty() const {return string_is_empty_t(get_ptr());} t_size length() const {return strlen_t(get_ptr());} private: char_buffer_t<wchar_t,t_alloc> m_buffer; }; typedef string_wide_from_utf8_t<> string_wide_from_utf8; template<template<typename t_allocitem> class t_alloc = pfc::alloc_standard> class string_wide_from_codepage_t { public: string_wide_from_codepage_t() {} string_wide_from_codepage_t(const string_wide_from_codepage_t<t_alloc> & p_source) : m_buffer(p_source.m_buffer) {} string_wide_from_codepage_t(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) {convert(p_codepage,p_source,p_source_size);} void convert(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) { t_size size = estimate_codepage_to_wide(p_codepage,p_source,p_source_size); m_buffer.set_size(size); convert_codepage_to_wide(p_codepage, m_buffer.get_ptr_var(),size,p_source,p_source_size); } operator const wchar_t * () const {return get_ptr();} const wchar_t * get_ptr() const {return m_buffer.get_ptr();} bool is_empty() const {return string_is_empty_t(get_ptr());} t_size length() const {return strlen_t(get_ptr());} private: char_buffer_t<wchar_t,t_alloc> m_buffer; }; typedef string_wide_from_codepage_t<> string_wide_from_codepage; template<template<typename t_allocitem> class t_alloc = pfc::alloc_standard> class string_codepage_from_wide_t { public: string_codepage_from_wide_t() {} string_codepage_from_wide_t(const string_codepage_from_wide_t<t_alloc> & p_source) : m_buffer(p_source.m_buffer) {} string_codepage_from_wide_t(unsigned p_codepage,const wchar_t * p_source,t_size p_source_size = ~0) {convert(p_codepage,p_source,p_source_size);} void convert(unsigned p_codepage,const wchar_t * p_source,t_size p_source_size = ~0) { t_size size = estimate_wide_to_codepage(p_codepage,p_source,p_source_size); m_buffer.set_size(size); convert_wide_to_codepage(p_codepage, m_buffer.get_ptr_var(),size,p_source,p_source_size); } operator const char * () const {return get_ptr();} const char * get_ptr() const {return m_buffer.get_ptr();} bool is_empty() const {return string_is_empty_t(get_ptr());} t_size length() const {return strlen_t(get_ptr());} private: char_buffer_t<char,t_alloc> m_buffer; }; typedef string_codepage_from_wide_t<> string_codepage_from_wide; class string_codepage_from_utf8 { public: string_codepage_from_utf8() {} string_codepage_from_utf8(const string_codepage_from_utf8 & p_source) : m_buffer(p_source.m_buffer) {} string_codepage_from_utf8(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) {convert(p_codepage,p_source,p_source_size);} void convert(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) { string_wide_from_utf8 temp; temp.convert(p_source,p_source_size); t_size size = estimate_wide_to_codepage(p_codepage,temp,~0); m_buffer.set_size(size); convert_wide_to_codepage(p_codepage,m_buffer.get_ptr_var(),size,temp,~0); } operator const char * () const {return get_ptr();} const char * get_ptr() const {return m_buffer.get_ptr();} bool is_empty() const {return string_is_empty_t(get_ptr());} t_size length() const {return strlen_t(get_ptr());} private: char_buffer_t<char> m_buffer; }; class string_utf8_from_codepage { public: string_utf8_from_codepage() {} string_utf8_from_codepage(const string_utf8_from_codepage & p_source) : m_buffer(p_source.m_buffer) {} string_utf8_from_codepage(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) {convert(p_codepage,p_source,p_source_size);} void convert(unsigned p_codepage,const char * p_source,t_size p_source_size = ~0) { string_wide_from_codepage temp; temp.convert(p_codepage,p_source,p_source_size); t_size size = estimate_wide_to_utf8(temp,~0); m_buffer.set_size(size); convert_wide_to_utf8( m_buffer.get_ptr_var(),size,temp,~0); } operator const char * () const {return get_ptr();} const char * get_ptr() const {return m_buffer.get_ptr();} bool is_empty() const {return string_is_empty_t(get_ptr());} t_size length() const {return strlen_t(get_ptr());} private: char_buffer_t<char> m_buffer; }; class string_utf8_from_ansi { public: string_utf8_from_ansi() {} string_utf8_from_ansi(const string_utf8_from_ansi & p_source) : m_buffer(p_source.m_buffer) {} string_utf8_from_ansi(const char * p_source,t_size p_source_size = ~0) : m_buffer(codepage_system,p_source,p_source_size) {} operator const char * () const {return get_ptr();} const char * get_ptr() const {return m_buffer.get_ptr();} bool is_empty() const {return string_is_empty_t(get_ptr());} t_size length() const {return strlen_t(get_ptr());} void convert(const char * p_source,t_size p_source_size = ~0) {m_buffer.convert(codepage_system,p_source,p_source_size);} private: string_utf8_from_codepage m_buffer; }; class string_ansi_from_utf8 { public: string_ansi_from_utf8() {} string_ansi_from_utf8(const string_ansi_from_utf8 & p_source) : m_buffer(p_source.m_buffer) {} string_ansi_from_utf8(const char * p_source,t_size p_source_size = ~0) : m_buffer(codepage_system,p_source,p_source_size) {} operator const char * () const {return get_ptr();} const char * get_ptr() const {return m_buffer.get_ptr();} bool is_empty() const {return string_is_empty_t(get_ptr());} t_size length() const {return strlen_t(get_ptr());} void convert(const char * p_source,t_size p_source_size = ~0) {m_buffer.convert(codepage_system,p_source,p_source_size);} private: string_codepage_from_utf8 m_buffer; }; class string_wide_from_ansi { public: string_wide_from_ansi() {} string_wide_from_ansi(const string_wide_from_ansi & p_source) : m_buffer(p_source.m_buffer) {} string_wide_from_ansi(const char * p_source,t_size p_source_size = ~0) : m_buffer(codepage_system,p_source,p_source_size) {} operator const wchar_t * () const {return get_ptr();} const wchar_t * get_ptr() const {return m_buffer.get_ptr();} bool is_empty() const {return string_is_empty_t(get_ptr());} t_size length() const {return strlen_t(get_ptr());} void convert(const char * p_source,t_size p_source_size = ~0) {m_buffer.convert(codepage_system,p_source,p_source_size);} private: string_wide_from_codepage m_buffer; }; class string_ansi_from_wide { public: string_ansi_from_wide() {} string_ansi_from_wide(const string_ansi_from_wide & p_source) : m_buffer(p_source.m_buffer) {} string_ansi_from_wide(const wchar_t * p_source,t_size p_source_size = ~0) : m_buffer(codepage_system,p_source,p_source_size) {} operator const char * () const {return get_ptr();} const char * get_ptr() const {return m_buffer.get_ptr();} bool is_empty() const {return string_is_empty_t(get_ptr());} t_size length() const {return strlen_t(get_ptr());} void convert(const wchar_t * p_source,t_size p_source_size = ~0) {m_buffer.convert(codepage_system,p_source,p_source_size);} private: string_codepage_from_wide m_buffer; }; #ifdef UNICODE typedef string_wide_from_utf8 string_os_from_utf8; typedef string_utf8_from_wide string_utf8_from_os; #else typedef string_ansi_from_utf8 string_os_from_utf8; typedef string_utf8_from_ansi string_utf8_from_os; #endif } #else //PORTME #endif }; ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/string_list.h ================================================ #ifndef _PFC_STRING_LIST_H_ #define _PFC_STRING_LIST_H_ namespace pfc { typedef list_base_const_t<const char*> string_list_const; class string_list_impl : public string_list_const { public: t_size get_count() const {return m_data.get_size();} void get_item_ex(const char* & p_out, t_size n) const {p_out = m_data[n];} inline const char * operator[] (t_size n) const {return m_data[n];} void add_item(const char * p_string) { t_size idx = m_data.get_size(); m_data.set_size(idx + 1); m_data[idx] = p_string; } void add_items(const string_list_const & p_source) {_append(p_source);} void remove_all() { m_data.set_size(0); } //unnecessary since pfc::array_t<pfc::string8> is in use for implementation //~string_list_impl() {remove_all();} inline string_list_impl() {} inline string_list_impl(const string_list_impl & p_source) {_copy(p_source);} inline string_list_impl(const string_list_const & p_source) {_copy(p_source);} inline const string_list_impl & operator=(const string_list_impl & p_source) {_copy(p_source);return *this;} inline const string_list_impl & operator=(const string_list_const & p_source) {_copy(p_source);return *this;} inline const string_list_impl & operator+=(const string_list_impl & p_source) {_append(p_source);return *this;} inline const string_list_impl & operator+=(const string_list_const & p_source) {_append(p_source);return *this;} private: void _append(const string_list_const & p_source) { const t_size toadd = p_source.get_count(), base = m_data.get_size(); m_data.set_size(base+toadd); for(t_size n=0;n<toadd;n++) m_data[base+n] = p_source[n]; } void _copy(const string_list_const & p_source) { const t_size newcount = p_source.get_count(); m_data.set_size(newcount); for(t_size n=0;n<newcount;n++) m_data[n] = p_source[n]; } pfc::array_t<pfc::string8,pfc::alloc_fast> m_data; }; } #endif //_PFC_STRING_LIST_H_ ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/traits.h ================================================ namespace pfc { class traits_default { public: enum { realloc_safe = false, needs_destructor = true, needs_constructor = true, constructor_may_fail = true }; }; class traits_default_movable { public: enum { realloc_safe = true, needs_destructor = true, needs_constructor = true, constructor_may_fail = true }; }; class traits_rawobject : public traits_default { public: enum { realloc_safe = true, needs_destructor = false, needs_constructor = false, constructor_may_fail = false }; }; class traits_vtable { public: enum { realloc_safe = true, needs_destructor = true, needs_constructor = true, constructor_may_fail = false }; }; template<typename T> class traits_t : public traits_default {}; template<typename traits1,typename traits2> class combine_traits { public: enum { realloc_safe = (traits1::realloc_safe && traits2::realloc_safe), needs_destructor = (traits1::needs_destructor || traits2::needs_destructor), needs_constructor = (traits1::needs_constructor || traits2::needs_constructor), constructor_may_fail = (traits1::constructor_may_fail || traits2::constructor_may_fail), }; }; template<typename type1, typename type2> class traits_combined : public combine_traits<pfc::traits_t<type1>,pfc::traits_t<type2> > {}; template<typename T> class traits_t<T*> : public traits_rawobject {}; template<> class traits_t<char> : public traits_rawobject {}; template<> class traits_t<unsigned char> : public traits_rawobject {}; template<> class traits_t<wchar_t> : public traits_rawobject {}; template<> class traits_t<short> : public traits_rawobject {}; template<> class traits_t<unsigned short> : public traits_rawobject {}; template<> class traits_t<int> : public traits_rawobject {}; template<> class traits_t<unsigned int> : public traits_rawobject {}; template<> class traits_t<long> : public traits_rawobject {}; template<> class traits_t<unsigned long> : public traits_rawobject {}; template<> class traits_t<long long> : public traits_rawobject {}; template<> class traits_t<unsigned long long> : public traits_rawobject {}; template<> class traits_t<bool> : public traits_rawobject {}; template<> class traits_t<float> : public traits_rawobject {}; template<> class traits_t<double> : public traits_rawobject {}; template<> class traits_t<GUID> : public traits_rawobject {}; template<typename T,t_size p_count> class traits_t<T[p_count]> : public traits_t<T> {}; } ================================================ FILE: plugins/foobar09/foobar_sdk/pfc/utf8.cpp ================================================ #include "pfc.h" namespace pfc { //utf8 stuff static const t_uint8 mask_tab[6]={0x80,0xE0,0xF0,0xF8,0xFC,0xFE}; static const t_uint8 val_tab[6]={0,0xC0,0xE0,0xF0,0xF8,0xFC}; t_size utf8_char_len_from_header(char p_c) { t_uint8 c = (t_uint8)p_c; t_size cnt = 0; for(;;) { if ((p_c & mask_tab[cnt])==val_tab[cnt]) break; if (++cnt>=6) return 0; } return cnt + 1; } t_size utf8_decode_char(const char *p_utf8,unsigned * wide,t_size max) { const t_uint8 * utf8 = (const t_uint8*)p_utf8; if (wide) *wide = 0; if (max==0) return 0; else if (max>6) max = 6; if (utf8[0]<0x80) { if (wide) *wide = utf8[0]; return utf8[0]>0 ? 1 : 0; } unsigned res=0; unsigned n; unsigned cnt=0; for(;;) { if ((*utf8&mask_tab[cnt])==val_tab[cnt]) break; if (++cnt>=max) return 0; } cnt++; if (cnt==2 && !(*utf8&0x1E)) return 0; if (cnt==1) res=*utf8; else res=(0xFF>>(cnt+1))&*utf8; for (n=1;n<cnt;n++) { if ((utf8[n]&0xC0) != 0x80) return 0; if (!res && n==2 && !((utf8[n]&0x7F) >> (7 - cnt))) return 0; res=(res<<6)|(utf8[n]&0x3F); } if (wide) *wide=res; return cnt; } t_size utf8_encode_char(unsigned wide,char * target) { t_size count; if (wide < 0x80) count = 1; else if (wide < 0x800) count = 2; else if (wide < 0x10000) count = 3; else if (wide < 0x200000) count = 4; else if (wide < 0x4000000) count = 5; else if (wide <= 0x7FFFFFFF) count = 6; else return 0; //if (count>max) return 0; if (target == 0) return count; switch (count) { case 6: target[5] = 0x80 | (wide & 0x3F); wide = wide >> 6; wide |= 0x4000000; case 5: target[4] = 0x80 | (wide & 0x3F); wide = wide >> 6; wide |= 0x200000; case 4: target[3] = 0x80 | (wide & 0x3F); wide = wide >> 6; wide |= 0x10000; case 3: target[2] = 0x80 | (wide & 0x3F); wide = wide >> 6; wide |= 0x800; case 2: target[1] = 0x80 | (wide & 0x3F); wide = wide >> 6; wide |= 0xC0; case 1: target[0] = wide; } return count; } t_size utf16_encode_char(unsigned cur_wchar,wchar_t * out) { if (cur_wchar>0 && cur_wchar<(1<<20)) { if (sizeof(wchar_t) == 2 && cur_wchar>=0x10000) { unsigned c = cur_wchar - 0x10000; //MSDN: //The first (high) surrogate is a 16-bit code value in the range U+D800 to U+DBFF. The second (low) surrogate is a 16-bit code value in the range U+DC00 to U+DFFF. Using surrogates, Unicode can support over one million characters. For more details about surrogates, refer to The Unicode Standard, version 2.0. out[0] = (wchar_t)(0xD800 | (0x3FF & (c>>10)) ); out[1] = (wchar_t)(0xDC00 | (0x3FF & c) ) ; return 2; } else { *out = (wchar_t)cur_wchar; return 1; } } return 0; } t_size utf16_decode_char(const wchar_t * p_source,unsigned * p_out,t_size p_source_length) { if (p_source_length == 0) return 0; else if (p_source_length == 1) { *p_out = p_source[0]; return 1; } else { t_size retval = 0; unsigned decoded = p_source[0]; if (decoded != 0) { retval = 1; if ((decoded & 0xFC00) == 0xD800) { unsigned low = p_source[1]; if ((low & 0xFC00) == 0xDC00) { decoded = 0x10000 + ( ((decoded & 0x3FF) << 10) | (low & 0x3FF) ); retval = 2; } } } *p_out = decoded; return retval; } } UINT utf8_get_char(const char * src) { UINT rv = 0; utf8_decode_char(src,&rv); return rv; } t_size utf8_char_len(const char * s,t_size max) { return utf8_decode_char(s,0,max); } t_size skip_utf8_chars(const char * ptr,t_size count) { t_size num = 0; for(;count && ptr[num];count--) { t_size d = utf8_char_len(ptr+num); if (d<=0) break; num+=d; } return num; } bool is_valid_utf8(const char * param,t_size max) { t_size walk = 0; while(walk < max && param[walk] != 0) { t_size d; d = utf8_decode_char(param + walk,NULL,max - walk); if (d==0) return false; walk += d; if (walk > max) { PFC_ASSERT(0);//should not be triggerable return false; } } return true; } bool is_lower_ascii(const char * param) { while(*param) { if (*param<0) return false; param++; } return true; } static bool check_end_of_string(const char * ptr) { __try { return !*ptr; } __except(1) {return true;} } unsigned strcpy_utf8_truncate(const char * src,char * out,unsigned maxbytes) { unsigned rv = 0 , ptr = 0; if (maxbytes>0) { maxbytes--;//for null while(!check_end_of_string(src) && maxbytes>0) { __try { t_size delta = utf8_char_len(src); if (delta>maxbytes || delta==0) break; do { out[ptr++] = *(src++); } while(--delta); } __except(1) { break; } rv = ptr; } out[rv]=0; } return rv; } t_size strlen_utf8(const char * p,t_size num) { unsigned w; t_size d; t_size ret = 0; for(;num;) { d = utf8_decode_char(p,&w); if (w==0 || d<=0) break; ret++; p+=d; num-=d; } return ret; } t_size utf8_chars_to_bytes(const char * string,t_size count) { t_size bytes = 0; while(count) { t_size delta = utf8_decode_char(string+bytes,0); if (delta==0) break; bytes += delta; count--; } return bytes; } } ================================================ FILE: plugins/foobar09/foobar_sdk/sdk-license.txt ================================================ foobar2000 0.9.4 SDK Copyright (c) 2001-2007, Peter Pawlowski All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation or other materials provided with the distribution. Usage restrictions: It is illegal to use this SDK as a part of foobar2000 components that operate outside of legally documented programming interfaces (APIs), such as using window procedure hooks to modify user interface behaviors. We believe components doing so to be harmful to our userbase by introducing compatibility issues and dependencies on undocumented behaviors of our code that may change at any time without any notice or an update to the SDK which would reflect the change. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: plugins/foobar09/foobar_sdk/sdk-readme.css ================================================ /** * Design elements for default Template * * @author Andreas Gohr <andi@splitbrain.org> * @author Anika Henke <henke@cosmocode.de> */ /* -------------- general elements --------------- */ body { font: 80% "Lucida Grande", Verdana, Lucida, Helvetica, Arial, sans-serif; background-color: White; color: Black; margin: 0; padding: 0; } /* the document */ div.page { margin-top: 4px; margin-left: 1em; margin-right: 2em; text-align: justify; } table { font-size: 100%; padding:0; margin:0; } tr,td,th {padding:0; margin:0;} img {border:0} p {padding:0; margin: 0 0 1.0em 0;} hr { border: 0px; border-top: 1px solid #8cacbb; text-align:center; height: 0px; } div.nothing { text-align:center; margin: 2em; } /* ---------------- forms ------------------------ */ form { border: none; margin: 0; display: inline; } label { display: block; text-align: right; font-weight: bold; } label.simple { text-align: left; font-weight: normal; } label input.edit { width: 50%; } fieldset { width: 300px; text-align: center; border: 1px solid #8cacbb; padding: 0.5em; } textarea.edit { font-family:monospace; border: 1px solid #8cacbb; color: Black; background-color: white; font-size:14px; padding: 3px; width:100%; } input.edit,select.edit { font-size: 100%; border: 1px solid #8cacbb; height: 22px !important; max-height: 22px !important; min-height: 22px !important; color: Black; background-color: white; vertical-align: middle; padding: 1px; display: inline; } input.missing { font-size: 100%; border: 1px solid #8cacbb; height: 22px !important; max-height: 22px !important; min-height: 22px !important; color: Black; background-color: #ffcccc; vertical-align: middle; padding: 1px; display: inline; } /* --------- buttons ------------------- */ input.button { border: 1px solid #8cacbb; color: Black; background-color: white; vertical-align: middle; text-decoration:none; font-size: 100%; cursor: pointer; height: 22px !important; max-height: 22px !important; min-height: 22px !important; margin: 1px; display: inline; } div.secedit input.button { border: 1px solid #8cacbb; color: Black; background-color: white; vertical-align: middle; text-decoration:none; margin: 0px; padding: 0px; font-size: 10px; cursor: pointer; height: 15px; max-height: 15px !important; min-height: 15px !important; float:right; display: inline; } /* --------------- Links ------------------ */ a { color:#436976; text-decoration:none; } a:hover { color:#000000; text-decoration:underline; } /* external link */ a.urlextern{ background: transparent url(images/link_icon.gif) 0px 1px no-repeat; padding: 1px 0px 1px 16px; color:#436976; text-decoration:none; } a.urlextern:visited { color:Purple; } a.urlextern:hover { text-decoration:underline; } /* windows share */ a.windows{ background: transparent url(images/windows.gif) 0px 1px no-repeat; padding: 1px 0px 1px 16px; color:#436976; text-decoration:none; } a.windows:visited { color:Purple; } a.windows:hover { text-decoration:underline; } /* interwiki link */ a.interwiki{ background: transparent url(images/interwiki.png) 0px 1px no-repeat; padding-left: 16px; color:#436976; text-decoration:none; } a.interwiki:visited { color:Purple; } a.interwiki:hover { text-decoration:underline; } /* link to some embedded media */ a.media { color:#436976; text-decoration:none; } a.media:hover { color:#436976; text-decoration:underline } /* email link */ a.mail { background: transparent url(images/mail_icon.gif) 0px 1px no-repeat; padding: 1px 0px 1px 16px; color:#436976; text-decoration:none; } a.mail:hover { text-decoration:underline; } /* existing wikipage */ a.wikilink1:link { color:#009900; text-decoration:none } a.wikilink1:visited { color:#009900; text-decoration:none } a.wikilink1:hover { color:#009900; text-decoration:underline } /* not existing wikipage */ a.wikilink2:link { color:#FF3300; text-decoration:none } a.wikilink2:visited { color:#FF3300; text-decoration:none } a.wikilink2:hover { color:#FF3300; text-decoration:underline } /* ------------- Page elements ----------------- */ div.preview{ background:#f7f9fa; margin-left:2em; padding: 4px; border: 1px dashed #000000; } div.breadcrumbs{ background-color: #f5f5f5; font-size:80%; color: #666666; padding-left: 4px; } span.user{ color: #cccccc; font-size: 90%; } /* embedded images */ img.media { margin: 3px; } img.medialeft { border: 0; float: left; margin: 0 1.5em 0 0; } img.mediaright { border: 0; float: right; margin: 0 0 0 1.5em; } img.mediacenter { border: 0; display: block; margin-left: auto; margin-right: auto; } acronym { cursor: help; border-bottom: 1px dotted #000; } /* general headline setup */ h1, h2, h3, h4, h5 { color: Black; background-color: transparent; font-family: "Lucida Grande", Verdana, Lucida, Helvetica, Arial, sans-serif; font-size: 100%; font-weight: normal; margin-left: 0; margin-right: 0; margin-top: 0; margin-bottom: 1em; padding-left: 0; padding-right: 0; padding-top: 0.5em; padding-bottom: 0; border-bottom: 1px solid #8cacbb; clear: left; } /* special headlines */ h1 {font-size: 160%; margin-left: 0px; font-weight: bold;} h2 {font-size: 150%; margin-left: 20px;} h3 {font-size: 140%; margin-left: 40px; border-bottom: none; font-weight: bold;} h4 {font-size: 120%; margin-left: 60px; border-bottom: none; font-weight: bold;} h5 {font-size: 100%; margin-left: 80px; border-bottom: none; font-weight: bold;} /* indent different sections */ div.level1 {margin-left: 3px;} div.level2 {margin-left: 23px;} div.level3 {margin-left: 43px;} div.level4 {margin-left: 63px;} div.level5 {margin-left: 83px;} /* unordered lists */ ul { line-height: 1.5em; list-style-type: square; margin: 0.5em 0 0.5em 1.5em; padding: 0; list-style-image: url(images/bullet.gif); } /* ordered lists */ ol { line-height: 1.5em; margin: 0.5em 0 0.5em 1.5em; padding: 0; color: #638c9c; font-weight: bold; list-style-image: none; } /* the list items overriding the ol definition */ span.li { color: #000000; font-weight: normal; } ol {list-style-type: decimal} ol ol {list-style-type: upper-roman} ol ol ol {list-style-type: lower-alpha} ol ol ol ol {list-style-type: lower-greek} li.open { list-style-image: url(images/open.gif); } li.closed { list-style-image: url(images/closed.gif); } blockquote { border-left: 2px solid #8cacbb; padding-left: 3px; margin-left: 0; } /* code blocks by indention */ pre.pre { font-size: 120%; padding: 0.5em; border: 1px dashed #8cacbb; color: Black; background-color: #f7f9fa; overflow: auto; } /* code blocks by code tag */ pre.code { font-size: 120%; padding: 0.5em; border: 1px dashed #8cacbb; color: Black; background-color: #f7f9fa; overflow: auto; } /* inline code words */ code { font-size: 120%; } /* code blocks by file tag */ pre.file { font-size: 120%; padding: 0.5em; border: 1px dashed #8cacbb; color: Black; background-color: #dee7ec; overflow: auto; } /* inline tables */ table.inline { background-color: #ffffff; border-spacing: 0px; border-collapse: collapse; } table.inline th { padding: 3px; border: 1px solid #8cacbb; background-color: #dee7ec; } table.inline td { padding: 3px; border: 1px solid #8cacbb; } .leftalign{ text-align: left; } .centeralign{ text-align: center; } .rightalign{ text-align: right; } /* ---------- table of contents ------------------- */ div.toc { margin-left: 2em; margin-top: 1.2em; margin-bottom: 0; float:right; width: 200px; font-size: 80%; clear:both; } div.tocheader { padding: 3px; border: 1px solid #8cacbb; background-color: #dee7ec; text-align: left; font-weight:bold; margin-bottom: 2px; } #tocinside { border: 1px solid #8cacbb; background-color: #ffffff; text-align: left; padding-top: 0.5em; padding-bottom: 0.7em; } ul.toc { list-style-type: none; list-style-image: none; line-height: 1.2em; margin: 0; padding: 0; padding-left: 1em; } ul.toc li { background: transparent url(images/tocdot2.gif) 0 0.6em no-repeat; padding-left:0.4em; } ul.toc li.clear { background-image: none; padding-left:0.4em; } a.toc { color: #436976; text-decoration:none; } a.toc:hover { color: #000000; text-decoration:underline; } /* ---------------------------- Diff rendering --------------------------*/ table.diff { background:white; } td.diff-blockheader {font-weight:bold} td.diff-header { border-bottom: 1px solid #8cacbb; font-size:120%; } td.diff-addedline { background:#ddffdd; font-family: monospace; font-size: 100%; } td.diff-deletedline { background:#ffffbb; font-family: monospace; font-size: 100%; } td.diff-context { background:#f7f9fa; font-family: monospace; font-size: 100%; } span.diffchange { color: red; } /* --------------------- footnotes -------------------------------- */ div.footnotes{ clear:both; border-top: 1px solid #8cacbb; padding-left: 1em; margin-top: 1em; } div.fn{ font-size:90%; } a.fn_top{ vertical-align:super; font-size:80%; } a.fn_bot{ vertical-align:super; font-size:80%; font-weight:bold; } /* --------------- search result formating --------------- */ .search_result{ margin-bottom: 6px; padding-left: 30px; padding-right: 10px; } .search_snippet{ color: #999999; font-size: 12px; margin-left: 20px; } .search_sep{ color: #000000; } .search_hit{ color: #000000; background: #FFFF99; } div.search_quickresult{ margin-bottom: 15px; padding-bottom: 5px; border-bottom: 1px dashed #8cacbb; margin-left: 30px; padding-right: 10px; } div.search_quickhits { margin-left: 1em; float:left; background: transparent url(images/bullet.gif) 0px 1px no-repeat; padding: 1px 0px 1px 8px; width: 30%; } /* ------------------ Additional ---------------------- */ .footerinc a img { opacity: 0.5; } .footerinc a:hover img { opacity: 1; } /* ---- Admin --- */ div.acladmin label { text-align: left; font-weight: normal; display: inline; } div.acladmin table{ margin-left: 10%; width: 80%; } ================================================ FILE: plugins/foobar09/foobar_sdk/sdk-readme.html ================================================ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr"> <head> <title>foobar2000 0.9.4 SDK readme

    foobar2000 0.9.4 SDK readme

    Compatibility

    This SDK is compatible with foobar2000 0.9.4. It is not compatible with any earlier versions, and not guaranteed to be compatible with any future versions (though upcoming 0.9.X releases will aim to maintain compatibility as far as possible without crippling newly added functionality).

    Basic usage

    Each component must link against:

    • foobar2000_SDK project (contains declarations of services and various service-specific helper code)
    • foobar2000_component_client project (contains DLL entrypoint)
    • shared.dll (various helper code, mainly win32 function wrappers taking UTF-8 strings)
    • PFC (non-OS-specific helper class library)

    Foobar2000_SDK, foobar2000_component_client and PFC are included in sourcecode form; you can link against them by adding them to your workspace and using dependencies. To link against shared.dll, you must add “shared.lib” to linker input manually.

    Structure of a component

    A component is a DLL that implements one or more entrypoint services and interacts with services provided by other components.

    Services

    A service type is an interface class, deriving directly or indirectly from service_base class. A service type class must not have any data members; it can only provide virtual methods (to be overridden by service implementation), helper non-virtual methods built around virtual methods, static helper methods, and constants / enums. Each service interface class must have a static class_guid member, used for identification when enumerating services or querying for supported functionality. A service type declaration should declare a class with public virtual/helper/static methods, and use FB2K_MAKE_SERVICE_INTERFACE() / FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT() macro to implement standard service behaviors for the class; additionally, class_guid needs to be defined outside class declaration (e.g. const GUID someclass::class_guid = {....}; ). Note that most of components will not declare their own service types, they will only implement existing ones declared in the SDK.

    A service implementation is a class derived from relevant service type class, implementing virtual methods declared by service type class. Note that service implementation class does not implement virtual methods declared by service_base; those are implemented by service type declaration framework (service_query) or by instantiation framework (service_add_ref / service_release). Service implementation classes are instantiated using service_factory templates in case of entrypoint services (see below), or using service_impl_t template and operator new: “myserviceptr = new service_impl_t<myservice_impl>(params);“.

    Each service object provides reference counter features and (service_add_ref() and service_release() methods) as well as a method to query for extended functionality (service_query() method). Those methods are implemented by service framework and should be never overridden by service implementations. These methods should also never be called directly - reference counter methods are managed by relevant autopointer templates, service_query_t function template should be used instead of calling service_query directly, to ensure type safety and correct type conversions.

    Entrypoint services

    An entrypoint service type is a special case of a service type that can be registered using service_factory templates, and then accessed from any point of service system (excluding DLL startup/shutdown code, such as code inside static object constructors/destructors). An entrypoint service type class must directly derive from service_base.

    Registered entrypoint services can be accessed using:

    • For services types with variable number of implementations registered: service_enum_t<T> template, service_class_helper_t<T> template, etc, e.g.
      service_enum_t<someclass> e; service_ptr_t<someclass> ptr; while(e.next(ptr)) ptr->dosomething();

    • For services types with single always-present implementation registered - such as core services like playlist_manager - using static_api_ptr_t<T> template, e.g.:
      static_api_ptr_t<someclass> api; api->dosomething(); api->dosomethingelse();

    • Using per-service-type defined static helper functions, e.g. someclass::g_dosomething() - those use relevant service enumeration methods internally.

    An entrypoint service type must use FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT() macro to implement standard entrypoint service behaviors, as opposed to all other service types that use FB2K_MAKE_SERVICE_INTERFACE() macro instead.

    You can register your own entrypoint service implementations using either service_factory_t or service_factory_single_t template - the difference between the two is that the former instantiates the class on demand, while the latter keeps a single static instance and returns references to it when requested; the latter is faster but usable only for things that have no per-instance member data to maintain. Each service_factory_t / service_factory_single_t instance should be a static variable, such as: “static service_factory_t<myclass> g_myclass_factory;“.

    Certain service types require custom service_factory helper templates to be used instead of standard service_factory_t / service_factory_single_t templates; see documentation of specific service type for exact info about registering.

    A typical entrypoint service implementation looks like this:

    class myservice_impl : public myservice {
    public:
    	void dosomething() {....};
    	void dosomethingelse(int meh) {...};
    };
    static service_factory_single_t<myservice_impl> g_myservice_impl_factory;

    Service extensions

    Additional methods can be added to any service type, by declaring a new service type class deriving from service type class you want to extend. For example:

    class myservice : public service_base { public: virtual void dosomething() = 0; FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(myservice); };
    class myservice_v2 : public myservice { public: virtual void dosomethingelse() = 0; FB2K_MAKE_SERVICE_INTERFACE(myservice_v2, myservice); };

    In such scenario, to query whether a myservice instance is a myservice_v2 and to retrieve myservice_v2 pointer, use service_query functionality:

    service_ptr_t<myservice> ptr;
    (...)
    service_ptr_t<myservice_v2> ptr_ex;
    if (ptr->service_query_t(ptr_ex)) { /* ptr_ex is a valid pointer to myservice_v2 interface of our myservice instance */ (...) }
    else {/* this myservice implementation does not implement myservice_v2 */ (...) }

    Autopointer template use

    When performing most kinds of service operations, service_ptr_t<T> template should be used rather than working with service pointers directly; it automatically manages reference counter calls, ensuring that the service object is deleted when it is no longer referenced. Additionally, static_api_ptr_t<T> can be used to automatically acquire/release a pointer to single-implementation entrypoint service, such as one of standard APIs like playlist_manager.

    Exception use

    Most of API functions use C++ exceptions to signal failure conditions. All used exception classes must derive from std::exception (which pfc::exception is typedef’d to); this design allows various instances of code to use single catch() line to get human-readable description of the problem to display.

    Additionally, special subclasses of exceptions are defined for use in specific conditions, such as exception_io for I/O failures. As a result, you must provide an exception handler whenever you invoke any kind of I/O code that may fail, unless in specific case calling context already handles exceptions (e.g. input implementation code - any exceptions should be forwarded to calling context, since exceptions are a part of input API).

    Implementations of global callback services such as playlist_callback, playback_callback or library_callback must not throw unhandled exceptions; behaviors in case they do are undefined (app termination is to be expected).

    Storing configuration

    In order to create your entries in the configuration file, you must instantiate some objects that derive from cfg_var class. Those can be either predefined classes (cfg_int, cfg_string, etc) or your own classes implementing relevant methods.

    Each cfg_var instance has a GUID assigned, to identify its configuration file entry. The GUID is passed to its constructor (which implementations must take care of, typically by providing a constructor that takes a GUID and forwards it to cfg_var constructor).

    Note that cfg_var objects can only be instantiated statically (either directly as static objects, or as members of other static objects). Additionally, you can create configuration data objects that can be accessed by other components, by implementing config_object service. Some standard configuration variables can be also accessed using config_object interface.

    Use of global callback services

    Multiple service classes presented by the SDK allow your component to receive notifications about various events:

    • file_operation_callback - tracking file move/copy/delete operations.
    • library_callback - tracking Media Library content changes.
    • metadb_io_callback - tracking tag read / write operations altering cached/displayed media information.
    • play_callback - tracking playback related events.
    • playback_statistics_collector - collecting information about played tracks.
    • playlist_callback, playlist_callback_single - tracking playlist changes (the latter tracks only active playlist changes).
    • playback_queue_callback - tracking playback queue changes.
    • titleformat_config_callback - tracking changes of title formatting configuration.
    • ui_drop_item_callback - filtering items dropped into the UI.

    All of global callbacks operate only within main app thread, allowing easy cooperation with windows GUI - for an example, you perform playlist view window repainting directly from your playlist_callback implementation.

    There are restrictions on things that are legal to call from within global callbacks. For an example, you can’t modify playlist from inside a playlist callback, because there are other registered callbacks tracking playlist changes that haven’t been notified about the change being currently processed yet.

    IMPORTANT: You must not enter modal message loops from inside global callbacks, as those allow any unrelated code (queued messages, user input, etc.) to be executed, without being aware that a global callback is being processed. Certain global API methods such as metadb_io::load_info_multi or threaded_process::run_modal enter modal loops when called. In other words, calling tag read/write operations from within one of global callbacks is illegal and will lead to issues. Use main_thread_callback service to avoid this problem and delay execution of problematic code.

    Service class design guidelines (advanced)

    This chapter describes things you should keep on your mind when designing your own service type classes. Since 99% of components will only implement existing service types rather than adding their own cross-DLL-communication protocols, you can probably skip reading this chapter.

    Cross-DLL safety

    It is important that all function parameters used by virtual methods of services are cross-DLL safe (do not depend on compiler-specific or runtime-specific behaviors, so no unexpected behaviors occur when calling code is built with different compiler/runtime than callee). To achieve this, any classes passed around must be either simple objects with no structure that could possibly vary with different compilers/runtimes (i.e. make sure that any memory blocks are freed on the side that allocated them); easiest way to achieve this is to reduce all complex data objects or classes passed around to interfaces with virtual methods, with implementation details hidden from callee. For an example, use pfc::string_base& as parameter to a function that is meant to return variable-length strings.

    Entrypoint service efficiency

    When designing an entrypoint service interface meant to have multiple different implementations, you should consider making it possible for all its implementations to use service_factory_single_t (i.e. no per-instance member data); by e.g. moving functionality that needs multi-instance operation to a separate service type class that is created on-demand by one of entrypoint service methods. For example:

    class myservice : public service_base {
    public:
    	//this method accesses per-instance member data of the implementation class
    	virtual void workerfunction(const void * p_databuffer,t_size p_buffersize) = 0;
    	//this method is used to determine which implementation can be used to process specific data stream.
    	virtual bool queryfunction(const char * p_dataformat) = 0;
     
    	FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(myservice);
    };
    (...)
    service_ptr_t<myservice> findservice(const char * p_dataformat) {
    	service_enum_t<myservice> e; service_ptr_t<myservice> ptr;
    	//BOTTLENECK, this dynamically instantiates the service for each query.
    	while(e.next(ptr)) {
    		if (ptr->queryfunction(p_dataformat)) return ptr;
    	}
    	throw exception_io_data();
    }

    .. should be changed to:

    //no longer an entrypoint service - use myservice::instantiate to get an instance instead.
    class myservice_instance : public service_base {
    public:
    	virtual void workerfunction(const void * p_databuffer,t_size p_buffersize) = 0;
    	FB2K_MAKE_SERVICE_INTERFACE(myservice_instance,service_base);
    };
     
    class myservice : public service_base {
    public:
    	//this method is used to determine which implementation can be used to process specific data stream.
    	virtual bool queryfunction(const char * p_dataformat) = 0;
    	virtual service_ptr_t<myservice_instance> instantiate() = 0;
    	FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(myservice);
    };
     
    template<typename t_myservice_instance_impl>
    class myservice_impl_t : public myservice {
    public:
    	//implementation of myservice_instance must provide static bool g_queryformatfunction(const char*);
    	bool queryfunction(const char * p_dataformat) {return t_myservice_instance_impl::g_queryfunction(p_dataformat);}
    	service_ptr_t<myservice_instance> instantiate() {return new service_impl_t<t_myservice_instance_impl>();}
    };
     
    template<typename t_myservice_instance_impl> class myservice_factory_t :
    	public service_factory_single_t<myservice_impl_t<t_myservice_instance_impl> > {};
    //usage: static myservice_factory_t<myclass> g_myclass_factory;
     
    (...)
     
    service_ptr_t<myservice_instance> findservice(const char * p_dataformat) {
    	service_enum_t<myservice> e; service_ptr_t<myservice> ptr;
    	//no more bottleneck, enumerated service does not perform inefficient operations when requesting an instance.
    	while(e.next(ptr)) {
    		//"inefficient" part is used only once, with implementation that actually supports what we request.
    		if (ptr->queryfunction(p_dataformat)) return ptr->instantiate();
    	}
    	throw exception_io_data();
    }
    ================================================ FILE: plugins/foobar09/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by foo_audioscrobbler.rc // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: plugins/foobar09/tools/append_once.bat ================================================ REM %1 - string to append REM %2 - file to append it to find /C /I %1 %2 if not errorlevel 1 goto end echo start /WAIT "" %1 /SILENT >> %2 :end ================================================ FILE: plugins/iTunes/ChangeLog.txt ================================================ 5.0.5.1 (19/11/12) ------------------------------------------------------------------------------- * Now syncs tracks more often for less spurious double scrobbles (only happened if you skipped to the end of a track with 20s) 5.0.4.1 (20/8/12) ------------------------------------------------------------------------------- * Now sends album artist as parameter d in scrob sub protocol. 3.0.1.0 (16/5/08) ------------------------------------------------------------------------------- * Better error handling. * Mac: fixed iTunes slowdown (beach balling) 3.0.0.13 (13/5/08) ------------------------------------------------------------------------------- * Fixed double scrobbling of tracks when using iPod classic. * Updates broken Updater.exe in client if installed. 3.0.0.12 (8/5/08) ------------------------------------------------------------------------------- * Win: fixed random crashes on track changes. * Win: fixed crash on track start in iTunes 6. 3.0.0.10 (6/5/08) ------------------------------------------------------------------------------- * Mac: fixed crash when upgrading iPod firmware. 3.0.0.9 (1/5/08) ------------------------------------------------------------------------------- * Retrying when we get a playcount diff of 0 from iTunes. 3.0.0.8 (29/4/08) ------------------------------------------------------------------------------- * Fixed crash on track start with iPod scrobbling disabled. 3.0.0.5 (19/3/08) ------------------------------------------------------------------------------- * Fixed crash on CD track change. * Fixed crash on unmounting Firewire device. * Don't start iPod scrobbling when a non-iPod USB device is unmounted. 3.0.0.4 (17/3/08) ------------------------------------------------------------------------------- * Repeated tracks now scrobble correctly (Win) * Fixed bug where a restarted iTunes track would not lead to the scrobble timer in the client getting reset (Win) 3.0.0.0 (19/2/08) ------------------------------------------------------------------------------- * Now with iPod scrobbling 2.0.13.0 (6/8/07) ------------------------------------------------------------------------------- * Recompiled with new ScrobSub for Vista compatibility. * Fixed path retrieval for iTunes 7+ 2.0.11.0 (3/4/07) ------------------------------------------------------------------------------- * Fixed crash on shutdown on Vista. * Fixed GDI memory leak making iTunes GUI sluggish after prolonged use. * Fixed version reporting. * Ripped out unnecessary WTL dependency. 2.0.9.0 (13/9/06) ------------------------------------------------------------------------------- * Changed structure around to work with new ScrobSub. 2.0.8.0 (22/6/06) ------------------------------------------------------------------------------- * Changed output name to itw_scrobbler.dll and recompiled with new ScrobSub for the beta release of the new Last.fm client. 2.0.6.0 (16/5/06) ------------------------------------------------------------------------------- * Submitting songs from shared playlists now works 2.0.4.0 (11/4/06) ------------------------------------------------------------------------------- * Searching for "movie" as well as "video" to filter out video files ================================================ FILE: plugins/iTunes/IPod.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "IPod.h" #include #include #include #ifndef WIN32 #include #endif #ifndef WIN32 std::string escape(std::string const &s) #else std::wstring escape(std::wstring const &s) #endif { std::size_t n = s.length(); #ifndef WIN32 std::string escaped; #else std::wstring escaped; #endif escaped.reserve(n * 2); // pessimistic preallocation for (std::size_t i = 0; i < n; ++i) { if (s[i] == '\\' || s[i] == '\'') escaped += '\\'; escaped += s[i]; } return escaped; } const COMMON_STD_STRING IPod::twiddlyFlags() const { LFM_STRINGSTREAM ss; ss << "--device "; ss << device(); ss << " --connection "; switch( m_connectionType ) { case usb: ss << "usb"; break; case fireWire: ss << "fireWire"; break; } ss << " --pid " << m_pid; ss << " --vid " << m_vid; ss << " --serial " << m_serial; ss << " --name " << escape( m_displayName ); if( m_manualMode ) ss << " --manual"; return ss.str(); } COMMON_STD_STRING IPod::device() const { #ifdef WIN32 #define _(x) L##x #else #define _(x) x #endif switch( m_type ) { case iPod: return _( "ipod" ); case iTouch: return _( "itouch" ); case iPhone: return _( "iphone" ); case iPad: return _( "ipad" ); default: return _( "unknown" ); } #undef _ } ================================================ FILE: plugins/iTunes/IPod.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef IPOD_H #define IPOD_H #include #include #include "common/c++/logger.h" #ifdef WIN32 #define LFM_STRINGSTREAM std::wstringstream #else #include #include #define LFM_STRINGSTREAM std::stringstream #endif /** @author Jono Cole * @brief stores iPod / iPhone device information */ class IPod { public: const COMMON_STD_STRING twiddlyFlags() const; const COMMON_STD_STRING& serial() const { return m_serial; } COMMON_STD_STRING device() const; const COMMON_STD_STRING& displayName() const { return m_displayName; } enum deviceType { unknown = 0, iPod, iTouch, iPhone, iPad }; bool isManualMode() const{ return m_manualMode; } bool isMobileScrobblerInstalled() const { return m_mobileScrobblerInstalled; } /** populates the m_manualMode bool * NOTE possibly shouldn't be here */ void populateIPodManual(); #ifdef WIN32 //Note: It's not ideal that the IPodDetector has to pass the IWbemServices pointer // to the IPod class but it is necessary in order to make further WMI queries // in this class. It should be safe however as no IPod instances should exist // beyond the scope of the IPodDetector instance. static IPod* newFromUsbDevice( struct IWbemClassObject* device, struct IWbemServices* wmiServices ); /** get the serial, pid and vid of a CIM_PnPEntity device * ( pass NULL to any of the output parameters if not needed ) */ static void getSerialVidPid( struct IWbemClassObject* device, std::wstring *const serialOut = NULL, int *const vidOut = NULL, int *const pidOut = NULL ); /** get the name of the CIM_PnPEntity device */ std::wstring getDeviceName( struct IWbemClassObject* const object ) const; /** Check if the IPod is older than the timeout value (milliseconds) ) */ bool hasTimeoutExpired( DWORD timeoutValue ); #else //MAC: /** Utility method to get the usb serial number of a io_object_t device * without having to create an object. */ static bool getUsbSerial( io_object_t device, std::string* serialOut ); static bool getFireWireSerial( io_object_t device, std::string* serialOut ); static IPod* newFromUsbDevice( io_object_t device, deviceType type = unknown ); static IPod* newFromFireWireDevice( io_object_t device ); static io_object_t getBaseDevice( io_object_t device ); void setMountPoint( const std::string& mountPoint ){ m_mountPoint = mountPoint; } void setDiskID( const std::string& diskID ){ m_diskID = diskID; } const std::string& diskID() const{ return m_diskID; } void getDisplayName(); #endif private: IPod(): m_manualMode( false ), m_mobileScrobblerInstalled( false ) {} COMMON_STD_STRING m_serial; int m_pid; int m_vid; bool m_manualMode; bool m_mobileScrobblerInstalled; enum { fireWire = 0, usb } m_connectionType; deviceType m_type; std::string m_mountPoint; COMMON_STD_STRING m_displayName; #ifdef WIN32 static void tokenize( const std::wstring& str, std::vector< std::wstring >& tokens, const std::wstring& delimiters = L" " ); DWORD m_connectionTickCount; struct IWbemServices* m_wmiServices; /** populates the m_mountPoint string querying using WMI */ void populateMountPoint( IWbemClassObject *device ); char getDriveLetterFromPnPEntity( IWbemClassObject* device ) const; char getDriveLetterFromDiskDrive( IWbemClassObject* diskDrive ) const; char getDriveLetterFromPartition( IWbemClassObject* partition ) const; /** populates the m_manualMode and m_mobileScrobblerInstalled bools */ void populateIPhoneMobileScrobblerAndManualMode(); /** Waits for iPod backup to complete (checking mod time of Info.plist * or times out after 5 mins. */ void waitForIpodBackup( const std::wstring& backupPath ) const; /** Query if mobile scrobbler is installed by iterating through each * file in the backupPath directory checking for the if it's a * mobilescrobbler plist file. */ bool queryMobileScrobblerInstalled( const std::wstring& backupPath ) const; /** Determines if a file is a backup of a mobilescrobbler plist file by * searching for the string "org.c99.MobileScrobbler.plist". */ bool isMobileScrobblerPlist( const std::wstring& path ) const; /** Determines if the iPhone is in manual or automatic mode by * reading the info.plist file and checking the iTunesPrefs section. */ bool queryIPhoneManual( const std::wstring& infoPlistPath ) const; #else //MAC: bool isOldIpod( io_object_t device ); bool getDeviceId( io_object_t device, int* vid, int* pid ); bool getDeviceFireWireId( io_object_t device, int* vid, int* pid ); std::string m_diskID; bool isMobileScrobblerPlist( const char* path ) const; bool queryMobileScrobblerInstalled() const; bool queryIPhoneManual() const; bool waitForIpodBackup() const; CFDictionaryRef createDictionaryFromXML( CFStringRef filePath ) const; #endif }; #endif //IPOD_H ================================================ FILE: plugins/iTunes/IPodDetector.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef IPOD_DETECTOR_H #define IPOD_DETECTOR_H #include "common/c++/logger.h" #include "Moose.h" #include "IPod.h" #include #include #include #ifdef WIN32 #include #else #include #include "pthread.h" #include #include #endif /** @author Jono Cole * @brief launches twiddly for diffing when an iPod / iPhone is unplugged */ class IPodDetector { public: IPodDetector(); ~IPodDetector(); private: // NOTE inline because there is no IPodDetector.cpp void startTwiddlyWithFlag( const COMMON_STD_STRING& args ) { if ( !Moose::isTwiddlyRunning() ) { Moose::exec( Moose::twiddlyPath(), args ); } } // NOTE inline because there is no IPodDetector.cpp void startTwiddlyWithIpodSerial( const COMMON_STD_STRING& serial, char* trigger = "unknown" ) { std::map< COMMON_STD_STRING, IPod*>::iterator it = m_ipodMap.find( serial ); if( it != m_ipodMap.end() ) { LOG( 3, "Ipod scrobbling triggered by " << trigger << " method." ); IPod* iPod = it->second; startTwiddlyWithFlag( iPod->twiddlyFlags() ); m_ipodMap.erase( serial ); delete iPod; } } std::map< COMMON_STD_STRING, IPod*> m_ipodMap; void notifyIfUnknownIPod( IPod* ipod ); #ifdef WIN32 void threadMain(); static unsigned CALLBACK threadEntry( LPVOID lpParam ); static LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); static std::map< HWND, IPodDetector* > s_hwndMap; HWND m_deviceDetectionWnd; bool initializeWMI(); void shutdownWMI(); /** Check if the device still exists (determined using the serial number and WMI) */ bool doesIpodExist( IPod* ipod ); void checkConnectedDevices(); bool queryCurrentlyConnectedDevices( struct IEnumWbemClassObject** enumerator ); bool queryByDeviceID( const std::string& deviceId, IEnumWbemClassObject** enumerator ); void runDetectionEventLoop(); void onDeviceConnected( struct IWbemClassObject* const device ); void onDeviceRemoved(); std::string getDBTDeviceInfo( const LPARAM lparam ) const; bool m_shutdown; HANDLE m_thread; struct IWbemLocator *m_wmiLocator; struct IWbemServices *m_wmiServices; #else bool setupDetection(); /// callbacks static void* threadEntry( void* param ); static void onIPadDetected( void*, io_iterator_t newIterator ); static void onIPhoneDetected( void*, io_iterator_t newIterator ); static void onUsbIPodDetected( void*, io_iterator_t newIterator ); static void onDeviceRemoved( void*, io_iterator_t newIterator ); static void onFireWireDetected( void*, io_iterator_t newIterator ); static void onFireWireRemoved( void*, io_iterator_t newIterator ); static void onDeviceNodeAdded( void*, io_iterator_t newIterator ); static void onDeviceNodeRemoved( void*, io_iterator_t newIterator ); static OSStatus onMountStateChanged( EventHandlerCallRef handlerCallRef, EventRef event, void* userData ); static void onSyncTimerFire( CFRunLoopTimerRef timer, void *info ); static void onSyncTimerRelease( const void *info ); static void onQuit( void* param ); void startSyncTimer( class IPod* const ipod ); std::string getMountPoint( const std::string& deviceName ) const; CFRunLoopSourceRef m_quitSource; pthread_t m_threadId; bool m_detectorStarted; bool m_threadError; #endif }; #endif //IPOD_DETECTOR_H ================================================ FILE: plugins/iTunes/IPodDetector_mac.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "iPodDetector.h" #include "Ipod.h" #include "common/c++/logger.h" #include "Moose.h" #include #include #include #include #include #include #include #include #include #include #include using namespace std; static const double kTimeOutInterval = 5.0 * 60.0; // 5 minute timeout before sync is forced /** @author Jono Cole * @brief Used in the callback when the sync timer timesout */ struct SyncTimeoutInfo { IPodDetector* iPodDetector; std::string ipodSerial; }; IPodDetector::IPodDetector() :m_detectorStarted( false ), m_threadError( false ) { pthread_attr_t attr; pthread_attr_init( &attr ); pthread_create( &m_threadId, &attr, threadEntry, this ); } IPodDetector::~IPodDetector() { if( !m_threadError ) { CFRunLoopSourceSignal( m_quitSource ); CFRunLoopWakeUp( CFRunLoopGetCurrent() ); } } void* //static IPodDetector::threadEntry( void* param ) { IPodDetector* ipd = static_cast(param); /* setup native events to give mount / unmount detection * IOKit does not give this information as it is not at * the Hardware level. */ EventHandlerUPP handlerUPP; handlerUPP = NewEventHandlerUPP( onMountStateChanged ); EventTypeSpec eventTypes[] = { { kEventClassVolume, kEventVolumeMounted }, { kEventClassVolume, kEventVolumeUnmounted } }; OSStatus err = InstallApplicationEventHandler( handlerUPP, 2, eventTypes, param, NULL ); //TODO error handling /* Set up IOKit to give notifications for hardware events * (USB and Firewire Device plugin / remove ) * This is mainly needed to support non-mountable devices * such as the iPhone and iPod Touch. But is also used to * retrieve the USB serial number for all devices. */ if( !ipd->setupDetection() ) { LOG( 2, "Error - could not initialize iPod / iPhone detection.\n" "Devices will not be detected and wont scrobble." ); ipd->m_threadError = true; return 0; } CFRunLoopSourceContext context = { 0, //version; ipd, //void *info; NULL, //CFAllocatorRetainCallBack retain; NULL, //CFAllocatorReleaseCallBack release; NULL, //CFAllocatorCopyDescriptionCallBack copyDescription; NULL, //CFRunLoopEqualCallBack equal; NULL, //CFRunLoopHashCallBack hash; NULL, //CFRunLoopScheduleCallBack schedule; NULL, //CFRunLoopCancelCallBack cancel; onQuit //CFRunLoopPerformCallBack perform; }; ipd->m_quitSource = CFRunLoopSourceCreate( kCFAllocatorDefault, 0, &context ); CFRunLoopAddSource( CFRunLoopGetCurrent(), ipd->m_quitSource, kCFRunLoopDefaultMode ); ipd->m_detectorStarted = true; CFRunLoopRun(); LOG( 3, "Clearing up iPodMap" ); //clear up memory map< std::string, IPod* >::iterator i; for( i = ipd->m_ipodMap.begin(); i != ipd->m_ipodMap.end(); ipd++ ) { delete i->second; } ipd->m_ipodMap.clear(); return 0; } bool IPodDetector::setupDetection() { kern_return_t ioResult; IONotificationPortRef notificationObject = IONotificationPortCreate( kIOMasterPortDefault ); CFRunLoopSourceRef notificationRunLoopSource; //Use the notification object received from IONotificationPortCreate notificationRunLoopSource = IONotificationPortGetRunLoopSource(notificationObject); //Add the notification object to the event loop CFRunLoopAddSource(CFRunLoopGetCurrent(), notificationRunLoopSource, kCFRunLoopDefaultMode); //Dictionary for matching iPhone / iPod connections CFMutableDictionaryRef iPadConnectMatch = IOServiceNameMatching( "iPad" ); CFMutableDictionaryRef iPhoneConnectMatch = IOServiceNameMatching( "iPhone" ); CFMutableDictionaryRef iPodConnectMatch = IOServiceNameMatching( "iPod" ); CFMutableDictionaryRef iPodMiniConnectMatch = IOServiceNameMatching( "iPod mini" ); //Dictionaries for matching iPhone / iPod disconnections CFMutableDictionaryRef iPadDisconnectMatch = IOServiceNameMatching( "iPad" ); CFMutableDictionaryRef iPhoneDisconnectMatch = IOServiceNameMatching( "iPhone" ); CFMutableDictionaryRef iPodDisconnectMatch = IOServiceNameMatching( "iPod" ); CFMutableDictionaryRef iPodMiniDisconnectMatch = IOServiceNameMatching( "iPod mini" ); //Dictionaries for matching Firewire devices CFMutableDictionaryRef fireWireConnectMatch = IOServiceMatching( "IOFireWireSBP2LUN" ); CFMutableDictionaryRef fireWireDisconnectMatch = IOServiceMatching( "IOFireWireSBP2LUN" ); //Dictionaries for mount and unmount CFMutableDictionaryRef deviceNodeAddedMatch = IOServiceMatching( "IOMedia" ); CFMutableDictionaryRef deviceNodeRemovedMatch = IOServiceMatching( "IOMedia" ); //These iterators will be used to check the current state of already //plugged in devices before the notifications start coming in. io_iterator_t iPadAddedIter; io_iterator_t iPadRemovedIter; io_iterator_t iPhoneAddedIter; io_iterator_t iPhoneRemovedIter; io_iterator_t iPodAddedIter; io_iterator_t iPodRemovedIter; io_iterator_t iPodMiniAddedIter; io_iterator_t iPodMiniRemovedIter; io_iterator_t fireWireAddedIter; io_iterator_t fireWireRemovedIter; io_iterator_t deviceNodeAddedIter; io_iterator_t deviceNodeRemovedIter; //Add the notifications for connected / disconnected ioResult = IOServiceAddMatchingNotification( notificationObject, kIOMatchedNotification, iPadConnectMatch, onIPadDetected, this, &iPadAddedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add iPad connected notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOMatchedNotification, iPhoneConnectMatch, onIPhoneDetected, this, &iPhoneAddedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add iPhone connected notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOMatchedNotification, iPodConnectMatch, onUsbIPodDetected, this, &iPodAddedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add iPod connected notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOMatchedNotification, iPodMiniConnectMatch, onUsbIPodDetected, this, &iPodMiniAddedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add iPod Mini connected notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOMatchedNotification, fireWireConnectMatch, onFireWireDetected, this, &fireWireAddedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add firewire connected notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOTerminatedNotification, iPadDisconnectMatch, onDeviceRemoved, this, &iPadRemovedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add iPhone disconnected notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOTerminatedNotification, iPhoneDisconnectMatch, onDeviceRemoved, this, &iPhoneRemovedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add iPhone disconnected notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOTerminatedNotification, iPodDisconnectMatch, onDeviceRemoved, this, &iPodRemovedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add iPod disconnected notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOTerminatedNotification, iPodMiniDisconnectMatch, onDeviceRemoved, this, &iPodMiniRemovedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add iPod Mini disconnected notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOTerminatedNotification, fireWireDisconnectMatch, onFireWireRemoved, this, &fireWireRemovedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add iPod Mini disconnected notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOMatchedNotification, deviceNodeAddedMatch, onDeviceNodeAdded, this, &deviceNodeAddedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add device mount notification: " << mach_error_string( ioResult ) ); return false; } ioResult = IOServiceAddMatchingNotification( notificationObject, kIOTerminatedNotification, deviceNodeRemovedMatch, onDeviceNodeRemoved, this, &deviceNodeRemovedIter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not add device unmount notification: " << mach_error_string( ioResult ) ); return false; } // Check if there are any devices already plugged in / disconnected // and 'arm' the notification mechanism onIPhoneDetected( this, iPhoneAddedIter ); onIPadDetected( this, iPadAddedIter ); onUsbIPodDetected( this, iPodAddedIter ); onUsbIPodDetected( this, iPodMiniAddedIter ); onFireWireDetected( this, fireWireAddedIter ); onDeviceRemoved( this, iPhoneRemovedIter ); onDeviceRemoved( this, iPadRemovedIter ); onDeviceRemoved( this, iPodRemovedIter ); onDeviceRemoved( this, iPodMiniRemovedIter ); onFireWireRemoved( this, fireWireRemovedIter ); onDeviceNodeAdded( this, deviceNodeAddedIter ); onDeviceNodeRemoved( this, deviceNodeRemovedIter ); return true; } void //static IPodDetector::onUsbIPodDetected( void* param, io_iterator_t newIterator ) { IPodDetector* ipd = static_cast( param ); io_object_t ioUsbDeviceNub; while( ioUsbDeviceNub = IOIteratorNext( newIterator ) ) { IPod* iPod = IPod::newFromUsbDevice( ioUsbDeviceNub ); if( iPod ) { ipd->notifyIfUnknownIPod( iPod ); if( iPod->isMobileScrobblerInstalled() ) { LOG( 3, "Mobile Scrobbler detected on iPod Touch - client scrobbling not needed." ); delete iPod; IOObjectRelease( ioUsbDeviceNub ); continue; } ipd->m_ipodMap[ iPod->serial() ] = iPod; if( iPod->isManualMode() ) { LOG( 3, "iPod detected in manual mode." ); ipd->startTwiddlyWithIpodSerial( iPod->serial() ); } else { //Either the iPod is in automatic mode OR it's an old iPod LOG( 3, "iPod detected" ); ipd->startSyncTimer( iPod ); } } IOObjectRelease( ioUsbDeviceNub ); } } void //static IPodDetector::onIPadDetected( void* param, io_iterator_t newIterator ) { IPodDetector* ipd = static_cast( param ); io_object_t ioUsbDeviceNub; while( ioUsbDeviceNub = IOIteratorNext( newIterator ) ) { IPod* iPod = IPod::newFromUsbDevice( ioUsbDeviceNub, IPod::iPad ); if( iPod ) { ipd->notifyIfUnknownIPod( iPod ); if( iPod->isMobileScrobblerInstalled() ) { LOG( 3, "Mobile Scrobbler detected on iPad - client scrobbling not needed." ); delete iPod; IOObjectRelease( ioUsbDeviceNub ); continue; } ipd->m_ipodMap[ iPod->serial() ] = iPod; if( iPod->isManualMode() ) { LOG( 3, "iPad detected in manual mode" ); ipd->startTwiddlyWithIpodSerial( iPod->serial(), "manualMode" ); } else { LOG( 3, "iPad detected in automatic mode" ); ipd->startSyncTimer( iPod ); } } IOObjectRelease( ioUsbDeviceNub ); } } void //static IPodDetector::onIPhoneDetected( void* param, io_iterator_t newIterator ) { IPodDetector* ipd = static_cast( param ); io_object_t ioUsbDeviceNub; while( ioUsbDeviceNub = IOIteratorNext( newIterator ) ) { IPod* iPod = IPod::newFromUsbDevice( ioUsbDeviceNub, IPod::iPhone ); if( iPod ) { ipd->notifyIfUnknownIPod( iPod ); if( iPod->isMobileScrobblerInstalled() ) { LOG( 3, "Mobile Scrobbler detected on iPhone - client scrobbling not needed." ); delete iPod; IOObjectRelease( ioUsbDeviceNub ); continue; } ipd->m_ipodMap[ iPod->serial() ] = iPod; if( iPod->isManualMode() ) { LOG( 3, "iPhone detected in manual mode" ); ipd->startTwiddlyWithIpodSerial( iPod->serial(), "manualMode" ); } else { LOG( 3, "iPhone detected in automatic mode" ); ipd->startSyncTimer( iPod ); } } IOObjectRelease( ioUsbDeviceNub ); } } void //static IPodDetector::onDeviceRemoved( void* param, io_iterator_t newIterator ) { IPodDetector* ipd = static_cast( param ); io_object_t deviceInfo; while( deviceInfo = IOIteratorNext( newIterator ) ) { std::string serial; IPod::getUsbSerial( deviceInfo, &serial ); ipd->startTwiddlyWithIpodSerial( serial, "unplug" ); IOObjectRelease( deviceInfo ); } } void //static IPodDetector::onFireWireDetected( void* param, io_iterator_t newIterator ) { IPodDetector* ipd = static_cast( param ); io_object_t ioFireWireDeviceNub; kern_return_t ioResult; while( ioFireWireDeviceNub = IOIteratorNext( newIterator ) ) { CFMutableDictionaryRef propertyDictionary; ioResult = IORegistryEntryCreateCFProperties( (io_registry_entry_t)ioFireWireDeviceNub, &propertyDictionary, kCFAllocatorDefault, 0 ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not get firewire device properties: " << mach_error_string( ioResult ) ); IOObjectRelease( ioFireWireDeviceNub ); continue; } CFStringRef cfProductName; cfProductName = (CFStringRef)CFDictionaryGetValue( propertyDictionary, CFSTR( "FireWire Product Name" ) ); if( CFStringCompare( cfProductName, CFSTR( "iPod" ), 0 ) == kCFCompareEqualTo ) { //This firewire device is an iPod! IPod* iPod = IPod::newFromFireWireDevice( ioFireWireDeviceNub ); if( iPod ) { ipd->notifyIfUnknownIPod( iPod ); LOG( 3, "FireWire iPod plugged in" ); ipd->m_ipodMap[ iPod->serial() ] = iPod; ipd->startSyncTimer( iPod ); } } IOObjectRelease( ioFireWireDeviceNub ); } } void //static IPodDetector::onFireWireRemoved( void* param, io_iterator_t newIterator ) { IPodDetector* ipd = static_cast< IPodDetector* >( param ); io_object_t ioFireWireDeviceNub; while( ioFireWireDeviceNub = IOIteratorNext( newIterator ) ) { std::string serial; IPod::getFireWireSerial( ioFireWireDeviceNub, &serial ); ipd->startTwiddlyWithIpodSerial( serial, "unplug" ); IOObjectRelease( ioFireWireDeviceNub ); } } void //static IPodDetector::onDeviceNodeAdded( void* param, io_iterator_t newIterator ) { IPodDetector* ipd = static_cast< IPodDetector* >( param ); io_object_t device; while( device = IOIteratorNext( newIterator ) ) { CFBooleanRef isWhole = (CFBooleanRef)IORegistryEntryCreateCFProperty( device, CFSTR( kIOMediaWholeKey ), kCFAllocatorDefault, 0 ); if( isWhole ) { if( CFBooleanGetValue( isWhole ) ) { IOObjectRelease( device ); continue; } } /* //TODO: try and use the following to determine the device name CFStringRef cfBsdName = (CFStringRef)IORegistryEntryCreateCFProperty( device, CFSTR( kIOBSDNameKey ), kCFAllocatorDefault, 0 ); */ CFNumberRef cfBsdUnit = (CFNumberRef)IORegistryEntryCreateCFProperty( device, CFSTR( kIOBSDUnitKey ), kCFAllocatorDefault, 0 ); CFNumberRef cfPartId = (CFNumberRef)IORegistryEntryCreateCFProperty( device, CFSTR( kIOMediaPartitionIDKey ), kCFAllocatorDefault, 0 ); int bsdUnit, partitionId; CFNumberGetValue( cfBsdUnit, kCFNumberIntType, &bsdUnit ); CFNumberGetValue( cfPartId, kCFNumberIntType, &partitionId ); CFRelease( cfBsdUnit ); CFRelease( cfPartId ); std::stringstream deviceDev; deviceDev << "disk" << bsdUnit << "s" << partitionId; std::string serialNo; if( !IPod::getUsbSerial( device, &serialNo ) ) { io_name_t className; IOObjectGetClass( device, className ); LOG( 3, "Could not get serial - className: " << className ); } if( ipd->m_ipodMap.find( serialNo ) == ipd->m_ipodMap.end() ) { LOG( 3, "Error: something's gone wrong - cannot find iPod in the map" ); IOObjectRelease( device ); continue; } IPod* ipod = ipd->m_ipodMap[ serialNo ]; if( ipd->m_detectorStarted ) { /* Store the deviceID in the iPod object so that we can * match it up with a mount point when it is mounted. */ ipod->setDiskID( deviceDev.str() ); } else { /* The media can only be guarunteed to be mounted by the operating system * if the device was connected before iTunes started. * (ie the detector has NOT started yet.) */ std::string mountPoint = ipd->getMountPoint( deviceDev.str() ); ipod->setMountPoint( mountPoint ); ipod->populateIPodManual(); if( ipod->isManualMode() ) { ipd->startTwiddlyWithIpodSerial( ipod->serial(), "manual" ); } } IOObjectRelease( device ); } } std::string IPodDetector::getMountPoint( const std::string& deviceName ) const { LOG( 3, deviceName << " scanned." ); std::string devPath = "/dev/"; devPath += deviceName; int mountCount = getfsstat( NULL, 0, MNT_WAIT ); struct statfs * mountInfo = new struct statfs[ mountCount ]; sleep( 1 ); int result = getfsstat( mountInfo, sizeof( struct statfs ) * mountCount, MNT_WAIT ); if ( result < 0 ) { LOG( 3, "getfsstat error: " << result ); return ""; } std::string mountPoint = ""; for( int i = 0; i < mountCount; i++ ) { if( strncmp( devPath.c_str(), mountInfo[i].f_mntfromname, devPath.length() ) == 0 ) { mountPoint = mountInfo[i].f_mntonname; break; } } delete[] mountInfo; return mountPoint; } void //static IPodDetector::onDeviceNodeRemoved( void* param, io_iterator_t newIterator ) { IPodDetector* ipd = static_cast< IPodDetector* >( param ); io_object_t device; while( device = IOIteratorNext( newIterator ) ) { kern_return_t ioResult; CFBooleanRef isLeaf = (CFBooleanRef)IORegistryEntryCreateCFProperty( device, CFSTR( kIOMediaLeafKey ), kCFAllocatorDefault, 0 ); if( isLeaf ) { if( CFBooleanGetValue( isLeaf ) ) { LOG( 3, "Leaf node removal ignored" ); IOObjectRelease( device ); continue; } } io_iterator_t iter; ioResult = IORegistryEntryCreateIterator( device, kIOServicePlane, kIORegistryIterateParents | kIORegistryIterateRecursively, &iter); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not create iterator to iterate over unmounted device" << mach_error_string( ioResult ) ); } LOG( 3, "determining ipod unmount details" ); io_object_t curParent; while( curParent = IOIteratorNext( iter ) ) { io_name_t deviceName; ioResult = IORegistryEntryGetName( curParent, deviceName ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not get unmount parent's name/class: " << mach_error_string( ioResult ) ); IOObjectRelease( curParent ); continue; } if( strncmp( deviceName, "iPod", 4 ) == 0 || strncmp( deviceName, "iPod mini", 9 ) == 0 ) { LOG( 3, "iPod has been unmounted" ); std::string serial; if( !IPod::getUsbSerial( curParent, &serial ) ) { LOG( 3, "Error: could not determine serial number of iPod from unmount event" ); IOObjectRelease( curParent ); continue; } ipd->startTwiddlyWithIpodSerial( serial, "unmount" ); } if( strncmp( deviceName, "IOFireWireSBP2LUN", 17 ) == 0 ) { LOG( 3, "iPod has been unmounted" ); std::string serial; if( !IPod::getFireWireSerial( curParent, &serial ) ) { LOG( 3, "Error: could not determine serial number of iPod from unmount event" ); IOObjectRelease( curParent ); continue; } ipd->startTwiddlyWithIpodSerial( serial, "unmount" ); } IOObjectRelease( curParent ); } IOObjectRelease( device ); } } OSStatus //Static IPodDetector::onMountStateChanged( EventHandlerCallRef handlerCallRef, EventRef event, void* userData ) { IPodDetector* ipd = static_cast( userData ); unsigned int eventKind = GetEventKind( event ); unsigned int eventClass = GetEventClass( event ); switch( eventClass ) { case kEventClassVolume: { switch( eventKind ) { case kEventVolumeMounted: { FSVolumeRefNum volNum = 0; OSStatus result = GetEventParameter( event, kEventParamDirectObject, typeFSVolumeRefNum, NULL, sizeof( volNum ), NULL, &volNum ); if( result != noErr ) { //This has been known to be caused by iPod firmware update / restore LOG( 3, "Could not get event parameters. This mount will be ignored" ); break; } CFStringRef cfDiskID = NULL; result = FSCopyDiskIDForVolume( volNum, &cfDiskID ); if( result != noErr ) { LOG( 3, "Could not get diskID from mounted volume. This mount will be ignored" ); break; } char diskID[255]; CFStringGetCString( cfDiskID, diskID, sizeof( diskID ), kCFStringEncodingASCII ); CFRelease( cfDiskID ); LOG( 3, "mounted Volume location = /dev/" << diskID ); std::string volumePath = ipd->getMountPoint( diskID ); LOG( 3, "Volume mounted at location: " << volumePath ); std::map< std::string, IPod* >::iterator mapIter; for( mapIter = ipd->m_ipodMap.begin(); mapIter != ipd->m_ipodMap.end(); ) { IPod* curIpod = mapIter->second; const std::string& curDiskID = curIpod->diskID(); mapIter++; if( strncmp( curDiskID.c_str(), diskID, curDiskID.length() ) != 0 ) continue; curIpod->setMountPoint( volumePath ); curIpod->populateIPodManual(); if( curIpod->isManualMode() ) { LOG( 3, "iPod detected in manual sync mode." ); ipd->startTwiddlyWithIpodSerial( curIpod->serial(), "manual" ); } else { LOG( 3, "iPod detected in automatic sync mode." ); } } } break; case kEventVolumeUnmounted: { LOG( 3, "kEventVolumeUnmounted event received." ); } break; } } break; default: assert( !"Unexpected event received from the carbon event system." ); } return noErr; } void IPodDetector::startSyncTimer( IPod* const ipod ) { CFRunLoopTimerRef timer; SyncTimeoutInfo* info = new SyncTimeoutInfo(); info->iPodDetector = this; info->ipodSerial = ipod->serial(); CFRunLoopTimerContext context = { 0, //version (must be 0) info, //parameter to pass to callback NULL, //retain callback onSyncTimerRelease, //release callback NULL //copy description callback }; timer = CFRunLoopTimerCreate ( kCFAllocatorDefault, //Allocator CFAbsoluteTimeGetCurrent () + kTimeOutInterval, //fireDate 0, //interval 0 == one-shot timer 0, //flags (for future compatibility) 0, //order (currently ignored by runloops?!) onSyncTimerFire, //callback method &context //context information ); CFRunLoopAddTimer( CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode ); CFRunLoopWakeUp( CFRunLoopGetCurrent() ); } void //static IPodDetector::onSyncTimerFire( CFRunLoopTimerRef timer, void *info ) { SyncTimeoutInfo* timeoutInfo = static_cast( info ); timeoutInfo->iPodDetector->startTwiddlyWithIpodSerial( timeoutInfo->ipodSerial, "timeout" ); } void //static IPodDetector::onSyncTimerRelease( const void *info ) { const SyncTimeoutInfo* timeoutInfo = static_cast( info ); delete timeoutInfo; } void //static IPodDetector::onQuit( void* param ) { IPodDetector* ipd = static_cast< IPodDetector* >( param ); CFRunLoopStop( CFRunLoopGetCurrent() ); pthread_join( ipd->m_threadId, NULL ); } void IPodDetector::notifyIfUnknownIPod( IPod* ipod ) { // Qt unexpectedly separates with '.' std::string tmp = "device." + ipod->device() + '.' + ipod->serial() + ".user"; CFStringRef key = CFStringCreateWithBytes( NULL, (const unsigned char*) tmp.c_str(), tmp.length(), kCFStringEncodingUTF8, false ); if (!key) return; CFStringRef appId = CFSTR( "fm.last" ); CFStringRef user = (CFStringRef) CFPreferencesCopyAppValue( key, appId ); std::vector args; args.push_back( "--tray" ); if (user == NULL) { CFPreferencesSetAppValue( key, CFSTR( "" ), appId ); CFPreferencesAppSynchronize( appId ); args.push_back( "--new-ipod-detected" ); } else { CFRelease( user ); args.push_back( "--ipod-detected" ); } args.push_back( ipod->displayName() ); Moose::launchAudioscrobbler( args ); CFRelease( key ); } ================================================ FILE: plugins/iTunes/IPodDetector_win.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "iPodDetector.h" #include "common/c++/Logger.h" #include "Moose.h" #include "IPod.h" #include #include // for beginthreadex #include #include #include #include #include #include #include using namespace std; std::map< HWND, IPodDetector* > IPodDetector::s_hwndMap; static const DWORD kTimeOutInterval = 5 * 60 * 1000; // 5 minute timeout before sync is forced //The following strings are used in the notification query #define IPHONE_ITOUCH_NAME "Apple Mobile Device USB Driver" #define IPOD_NAME "USB Mass Storage Device" #define IPOD_NANO_NAME "Apple iPod USB Driver" #define IPOD_FIREWIRE_NAME "Apple Computer_ Inc. iPod IEEE 1394 SBP2 Device" #define VID_STRING "%VID_05AC%" #define LONG_VID_STRING "%APPLE_COMPUTER__INC%" IPodDetector::IPodDetector() :m_shutdown( false ), m_wmiLocator( NULL ), m_wmiServices( NULL ) { unsigned int threadId; m_thread = (HANDLE)_beginthreadex( NULL, 0, IPodDetector::threadEntry, this, 0, &threadId ); LOG( 3, "Starting iPod detection" ); } IPodDetector::~IPodDetector() { m_shutdown = true; //Wake up the event loop ::SendMessage( m_deviceDetectionWnd, WM_NULL, 0, 0 ); DWORD threadResult = WaitForSingleObject( m_thread, 5000 ); if( threadResult == WAIT_TIMEOUT ) LOGL( 3, "Error: the IPodDetector shutdown code failed to complete after 5 seconds." ); } unsigned CALLBACK //static IPodDetector::threadEntry( LPVOID lpParam ) { IPodDetector* ipd = static_cast(lpParam); ipd->threadMain(); return 0; } void IPodDetector::threadMain() { if( !initializeWMI() ) { return; } checkConnectedDevices(); runDetectionEventLoop(); LOGL( 3, "Cleaning up iPod Detector" ); shutdownWMI(); return; } void IPodDetector::runDetectionEventLoop() { WNDCLASSEX wndClass; wndClass.cbSize = sizeof( wndClass ); wndClass.style = NULL; wndClass.lpfnWndProc = IPodDetector::WindowProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = GetModuleHandle(NULL); wndClass.hIcon = NULL; wndClass.hCursor = NULL; wndClass.hbrBackground = NULL; wndClass.lpszMenuName = ""; wndClass.lpszClassName = "DeviceDetectionClass"; wndClass.hIconSm = NULL; ATOM regClassResult = RegisterClassEx( &wndClass ); m_deviceDetectionWnd = CreateWindow( "DeviceDetectionClass", "DeviceDetectionWindow", NULL, 0, 0, 0, 0, NULL, NULL, NULL, NULL); s_hwndMap[ m_deviceDetectionWnd ] = this; DEV_BROADCAST_DEVICEINTERFACE filter; ZeroMemory( &filter, sizeof( filter ) ); filter.dbcc_size = sizeof( filter ); filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; HDEVNOTIFY deviceNotifierHandle = RegisterDeviceNotification( m_deviceDetectionWnd, reinterpret_cast( &filter ), DEVICE_NOTIFY_WINDOW_HANDLE | // This next parameter causes notifications to be sent for all devices, // regardless of the dbcc_classguid member of filter. However, it's not // supported by Win 98/2000 so better use a sensible class anyway. 0x00000004 /* == DEVICE_NOTIFY_ALL_INTERFACE_CLASSES argh! */ ); if( !deviceNotifierHandle ) LOG( 3, "RegisterDeviceNotification Error: " << GetLastError() ); BOOL messageVal = 1; //initialize to non-zero value MSG message; while( messageVal != 0 && !m_shutdown ) { if( PeekMessage( &message, m_deviceDetectionWnd, 0, 0, PM_REMOVE ) > 0 ) { if( messageVal == -1 ) { LOGL( 3, "GetMessage error: " << GetLastError() ); } else { TranslateMessage( &message ); DispatchMessage( &message ); } } Sleep( 100 ); //Think of the poor CPU cycles if( m_ipodMap.empty() ) continue; //Check to see if any of the connected iPods have timedout std::map< std::wstring, IPod* >::iterator iter; for( iter = m_ipodMap.begin(); iter != m_ipodMap.end(); ) { IPod* ipod = iter->second; /* This looks wierd but we must increment the pointer before the * startTwiddlyWithIpodSerial method erases the ipod and invalidates * the previous pointer (Norman will tell you all about this) */ ++iter; if( ipod->hasTimeoutExpired( kTimeOutInterval ) ) { startTwiddlyWithIpodSerial( ipod->serial(), "timeout" ); } } } } LRESULT CALLBACK //static IPodDetector::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { IPodDetector* ipd = s_hwndMap[ hwnd ]; switch( uMsg ) { case WM_DEVICECHANGE: { HRESULT result = PostMessage( hwnd, WM_USER, wParam, lParam ); if( FAILED( result ) ) LOGL( 3, "error: PostMessage WM_USER failed: " << ::GetLastError() ) } break; case WM_USER: switch( wParam ) { case DBT_DEVICEARRIVAL: { std::string deviceId = ipd->getDBTDeviceInfo( lParam ); LOGL( 3, "Device arrived - id: " << deviceId ) IEnumWbemClassObject* enumerator; ipd->queryByDeviceID( deviceId, &enumerator ); IWbemClassObject* object; ULONG retCount; int count = 0; while( enumerator ) { enumerator->Next( WBEM_INFINITE, 1, &object, &retCount ); if( 0 == retCount ) break; ipd->onDeviceConnected( object ); object->Release(); count++; } if( count > 0 ) { LOGL( 3, count << " ipods found with this deviceId" ); } else { LOGL( 3, "no ipods found with this deviceid - presumeably a non-ipod has been plugged in!" ) } enumerator->Release(); } break; case DBT_DEVICEREMOVECOMPLETE: { //std::string deviceId = ipd->getDBTDeviceInfo( lParam ); LOGL( 3, "DBT device removal detected - checking for disconnected iPod.." ); ipd->onDeviceRemoved(); } break; default: { // LOGL( 3, "Unknown WM_DEVICECHANGE wParam: " << wParam ); } break; } break; default: return DefWindowProc( hwnd, uMsg, wParam, lParam ); break; } return 0; } std::string IPodDetector::getDBTDeviceInfo( const LPARAM lParam ) const { std::string retVal = "unknown"; PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { PDEV_BROADCAST_DEVICEINTERFACE lpDevIf = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; /* The dbcc_name needs to be copied into another memory location as * the SetupDiCreateDeviceInfoList function seems to clear the lpdb memory * (this seems to only happen with firewire devices *sigh* ) */ char* dbcc_name = new char[ strlen( lpDevIf->dbcc_name ) + 1 ]; strncpy( dbcc_name, lpDevIf->dbcc_name, strlen( lpDevIf->dbcc_name ) + 1 ); HDEVINFO devInfoList = SetupDiCreateDeviceInfoList( NULL, NULL ); SP_DEVICE_INTERFACE_DATA devInterfaceData; ZeroMemory( &devInterfaceData, sizeof( devInterfaceData ) ); devInterfaceData.cbSize = sizeof( devInterfaceData ); BOOL b = SetupDiOpenDeviceInterface( devInfoList, dbcc_name, 0, &devInterfaceData ); delete[] dbcc_name; DWORD required; SP_DEVICE_INTERFACE_DETAIL_DATA devDetailData; ZeroMemory( &devDetailData, sizeof( devDetailData ) ); devDetailData.cbSize = sizeof( devDetailData ); SP_DEVINFO_DATA devInfoData; ZeroMemory( &devInfoData, sizeof( devInfoData ) ); devInfoData.cbSize = sizeof( devInfoData ); b = SetupDiGetDeviceInterfaceDetail( devInfoList, &devInterfaceData, &devDetailData, sizeof( devDetailData ), &required, &devInfoData ); TCHAR deviceId[1000]; b = SetupDiGetDeviceInstanceId( devInfoList, &devInfoData, deviceId, 1000, NULL ); retVal = deviceId; } return retVal; } void IPodDetector::onDeviceConnected( IWbemClassObject* const device ) { IPod* iPod = IPod::newFromUsbDevice( device, m_wmiServices ); if( !iPod ) return; notifyIfUnknownIPod( iPod ); if( iPod->isMobileScrobblerInstalled() ) { LOGL( 3, "Ignoring this iPhone / iPod touch as mobile scrobbler has been detected!" ); return; } m_ipodMap[ iPod->serial() ] = iPod; if( iPod->isManualMode() ) { LOGWL( 3, L"Manual iPod detected launching twiddly..:" << iPod->serial() ); startTwiddlyWithIpodSerial( iPod->serial(), "manual" ); } else { LOGWL( 3, L"Device connected in automatic sync mode: " << iPod->serial() ); } } void IPodDetector::notifyIfUnknownIPod( IPod* ipod ) { std::wstring const key = UNICORN_HKEY L"\\device\\" + ipod->device() + L"\\" + ipod->serial(); HKEY h = NULL; LONG r = RegOpenKeyExW( HKEY_CURRENT_USER, key.c_str(), 0, // reserved KEY_ALL_ACCESS, // access mask &h ); if ( r != ERROR_SUCCESS ) { Moose::exec( Moose::applicationPath(), L"--ipod-detected " + ipod->serial() ); r = RegCreateKeyExW( HKEY_CURRENT_USER, key.c_str(), 0, // reserved, must be 0 NULL, // class, ignore REG_OPTION_NON_VOLATILE, // saves key on exit KEY_ALL_ACCESS, // access mask. TODO: test this as non-admin NULL, // security attrs, NULL = default, inherit from parent &h, NULL ); } // Internet says this is safe, even if NULL, or after failed function calls RegCloseKey( h ); } void IPodDetector::onDeviceRemoved() { bool iPodFound = false; std::map< std::wstring, IPod* >::iterator iter; for( iter = m_ipodMap.begin(); iter != m_ipodMap.end();) { IPod* curIpod = iter->second; iter++; //Stop the iterator from being invalidated if( !doesIpodExist( curIpod ) ) { iPodFound = true; LOGWL( 3, L"Ipod: " << curIpod->serial() << L" removed." ); startTwiddlyWithIpodSerial( curIpod->serial(), "unplug" ); } } if( !iPodFound ) LOGL( 3, "Could not find any disconnected iPods - a non-ipod device must have been removed" ); } bool IPodDetector::initializeWMI() { HRESULT result; // Initialize COM. result = CoInitializeEx(0, COINIT_APARTMENTTHREADED); if (FAILED(result)) { LOGL( 3, "Failed to initialize COM library. Error code = 0x" << hex << result ); return false; } // Obtain the initial locator to WMI result = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &m_wmiLocator); if (FAILED(result)) { LOGL( 3, "Failed to create IWbemLocator object." << " Err code = 0x" << hex << result ); CoUninitialize(); return false; } // Connect to WMI through the IWbemLocator::ConnectServer method // Connect to the root\cimv2 namespace with // the current user and obtain pointer m_services // to make IWbemServices calls. result = m_wmiLocator->ConnectServer( _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace NULL, // User name. NULL = current user NULL, // User password. NULL = current 0, // Locale. NULL indicates current NULL, // Security flags. 0, // Authority (e.g. Kerberos) 0, // Context object &m_wmiServices // pointer to IWbemServices proxy ); if (FAILED(result)) { LOGL( 3, "Could not connect to WMI. Error code = 0x" << hex << result ); m_wmiLocator->Release(); CoUninitialize(); return false; } LOGL( 3, "Connected to ROOT\\CIMV2 WMI namespace" ); // Set security levels on the proxy result = CoSetProxyBlanket( m_wmiServices, // Indicates the proxy to set RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx NULL, // Server principal name RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx NULL, // client identity EOAC_NONE // proxy capabilities ); if (FAILED(result)) { LOGL( 3, "Could not set proxy blanket. Error code = 0x" << hex << result ); m_wmiServices->Release(); m_wmiLocator->Release(); CoUninitialize(); return false; } return true; } bool IPodDetector::queryByDeviceID( const std::string& deviceId, IEnumWbemClassObject** enumerator ) { HRESULT result; std::string query = "SELECT * FROM Win32_PnPEntity " "WHERE DeviceID LIKE '"; query += deviceId; query += "' AND " " (Name = '" IPHONE_ITOUCH_NAME "' OR " " Name = '" IPOD_NAME "' OR " " Name = '" IPOD_FIREWIRE_NAME "' OR " " Name = '" IPOD_NANO_NAME "') AND " " (DeviceID LIKE '" VID_STRING "' OR " " DeviceID LIKE '" LONG_VID_STRING "')"; size_t pos = 0; for( pos = query.find("\\", pos); pos != query.npos ; pos = query.find("\\", pos) ) { query.replace(pos, 1, "\\\\"); pos += 2; } result = m_wmiServices->ExecQuery( bstr_t("WQL"), bstr_t(query.c_str()), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, enumerator ); if (FAILED(result)) { LOGL( 3, "ExecQuery Error code = 0x" << hex << result ); return false; } return true; } bool IPodDetector::queryCurrentlyConnectedDevices( IEnumWbemClassObject** enumerator ) { HRESULT result; result = m_wmiServices->ExecQuery( bstr_t("WQL"), bstr_t("SELECT * FROM Win32_PnPEntity " "WHERE (Name = '" IPHONE_ITOUCH_NAME "' OR " " Name = '" IPOD_NAME "' OR " " Name = '" IPOD_FIREWIRE_NAME "' OR " " Name = '" IPOD_NANO_NAME "') AND " " (DeviceID LIKE '" VID_STRING "' OR " " DeviceID LIKE '" LONG_VID_STRING "')"), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, enumerator ); if (FAILED(result)) { LOGL( 3, "ExecQuery Error code = 0x" << hex << result ); return false; } return true; } void IPodDetector::checkConnectedDevices() { IEnumWbemClassObject* currentDevicesEnumerator = NULL; queryCurrentlyConnectedDevices( ¤tDevicesEnumerator ); IWbemClassObject* device; ULONG returnCount = 0; while( currentDevicesEnumerator ) { currentDevicesEnumerator->Next( WBEM_INFINITE, 1, &device, &returnCount ); if( 0 == returnCount ) { break; } if( returnCount > 0 ) { LOGL( 3, "Detected pre-connected iPod" ); onDeviceConnected( device ); } device->Release(); } currentDevicesEnumerator->Release(); } void IPodDetector::shutdownWMI() { m_wmiServices->Release(); m_wmiLocator->Release(); CoUninitialize(); } bool IPodDetector::doesIpodExist(IPod *ipod) { IEnumWbemClassObject* enumerator = NULL; std::stringstream output; for( size_t i = 0; i < ipod->serial().length(); ++i ) { output << (char)ipod->serial().c_str()[i]; } queryByDeviceID( output.str(), &enumerator ); IWbemClassObject* object; ULONG count; enumerator->Next( WBEM_INFINITE, 1, &object, &count ); enumerator->Release(); if( count > 0 ) { object->Release(); return true; } return false; } ================================================ FILE: plugins/iTunes/IPod_mac.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "IPod.h" #include #include #include #include #include IPod* //static IPod::newFromUsbDevice( io_object_t device, deviceType type /* = unknown */ ) { IPod* ipod = new IPod(); io_object_t usbDevice = device; io_name_t className; IOObjectGetClass( usbDevice, className ); if( strncmp( className, kIOUSBDeviceClassName, sizeof( kIOUSBDeviceClassName ) ) != 0 ) { usbDevice = getBaseDevice( device ); if( NULL == usbDevice ) { LOG( 3, "Error could not find base USBDevice for device class: " << className ); return NULL; } } ipod->m_connectionType = usb; if ( !getUsbSerial( device, &ipod->m_serial ) ) { delete ipod; return NULL; } try { if( type != unknown ) { //IPhone ipod->m_type = type; } else if( ipod->isOldIpod( device ) ) { //IPod classic ipod->m_type = iPod; } else { //IPod Touch ipod->m_type = iTouch; } } catch ( const kern_return_t e ) { // the kern_return_t error number is thrown if isOldIpod fails // (the success can't be determined by the return value) delete ipod; return NULL; } ipod->getDisplayName(); if( ipod->m_type == iTouch || ipod->m_type == iPhone || ipod->m_type == iPad) { if( ipod->waitForIpodBackup() ) { ipod->m_mobileScrobblerInstalled = false; //ipod->queryMobileScrobblerInstalled(); ipod->m_manualMode = ipod->queryIPhoneManual(); } else { LOG( 3, "Warning: could not determine information from iPhone / iPod touch backup information\n" "The presumption is that this device is in automatic sync mode without mobile scrobbler installed" ); } } if( !ipod->getDeviceId( device, &ipod->m_vid, &ipod->m_pid ) ) { delete ipod; return NULL; } if( usbDevice != device ) { IOObjectRelease( usbDevice ); } return ipod; } IPod* //static IPod::newFromFireWireDevice( io_object_t device ) { IPod* ipod = new IPod(); ipod->m_connectionType = fireWire; if ( !getFireWireSerial( device, &ipod->m_serial ) ) { delete ipod; return NULL; } //We can presume that all firewire IPods are classic iPods //( Apple deprecated firewire connections with iPods after 4G ) ipod->m_type = iPod; if( !ipod->getDeviceFireWireId( device, &ipod->m_vid, &ipod->m_pid ) ) { delete ipod; return NULL; } return ipod; } bool IPod::getDeviceId( io_object_t device, int* vid, int* pid ) { CFMutableDictionaryRef propertyDictionary; kern_return_t ioResult; ioResult = IORegistryEntryCreateCFProperties( (io_registry_entry_t)device, &propertyDictionary, kCFAllocatorDefault, 0 ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not get usb device properties: " << mach_error_string( ioResult ) ); return false; } CFNumberRef cfVendor; cfVendor = (CFNumberRef)CFDictionaryGetValue( propertyDictionary, CFSTR( "idVendor" ) ); CFNumberRef cfProduct; cfProduct = (CFNumberRef)CFDictionaryGetValue( propertyDictionary, CFSTR( "idProduct" ) ); CFNumberGetValue ( cfVendor, kCFNumberIntType, vid ); CFNumberGetValue ( cfProduct, kCFNumberIntType, pid ); CFRelease( cfVendor ); CFRelease( cfProduct ); return true; } bool IPod::getDeviceFireWireId( io_object_t device, int* vid, int* pid ) { CFMutableDictionaryRef propertyDictionary; kern_return_t ioResult; ioResult = IORegistryEntryCreateCFProperties( (io_registry_entry_t)device, &propertyDictionary, kCFAllocatorDefault, 0 ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not get firewire device properties: " << mach_error_string( ioResult ) ); return false; } CFNumberRef cfVendor; cfVendor = (CFNumberRef)CFDictionaryGetValue( propertyDictionary, CFSTR( "Vendor_ID" ) ); CFNumberRef cfProduct; cfProduct = (CFNumberRef)CFDictionaryGetValue( propertyDictionary, CFSTR( "Model_ID" ) ); CFNumberGetValue ( cfVendor, kCFNumberIntType, vid ); CFNumberGetValue ( cfProduct, kCFNumberIntType, pid ); CFRelease( cfVendor ); CFRelease( cfProduct ); return true; } bool //static IPod::getUsbSerial( io_object_t device, std::string* serialOut ) { io_object_t usbDevice = device; io_name_t className; IOObjectGetClass( usbDevice, className ); if( strncmp( className, kIOUSBDeviceClassName, sizeof( kIOUSBDeviceClassName ) ) != 0 ) { usbDevice = getBaseDevice( device ); if( usbDevice == NULL ) { LOG( 3, "Error could not find base USBDevice for device class: " << className ); return NULL; } } CFMutableDictionaryRef propertyDictionary; kern_return_t ioResult; ioResult = IORegistryEntryCreateCFProperties( (io_registry_entry_t)usbDevice, &propertyDictionary, kCFAllocatorDefault, 0 ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not get serial properties: " << mach_error_string( ioResult ) ); return false; } CFStringRef cfSerial; cfSerial = (CFStringRef)CFDictionaryGetValue( propertyDictionary, CFSTR( "USB Serial Number" ) ); if( cfSerial == NULL ) { LOG( 3, "Error - Could not get USB Serial Number" ); return false; } char serial[50]; CFStringGetCString( cfSerial, serial, 50, kCFStringEncodingASCII ); CFRelease( cfSerial ); *serialOut = serial; if( usbDevice != device ) { IOObjectRelease( usbDevice ); } return true; } bool //static IPod::getFireWireSerial( io_object_t device, std::string* serialOut ) { CFMutableDictionaryRef propertyDictionary; kern_return_t ioResult; ioResult = IORegistryEntryCreateCFProperties( (io_registry_entry_t)device, &propertyDictionary, kCFAllocatorDefault, 0 ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not get USB device properties: " << mach_error_string( ioResult ) ); return false; } CFNumberRef cfSerial; cfSerial = (CFNumberRef)CFDictionaryGetValue( propertyDictionary, CFSTR( "GUID" ) ); if( cfSerial == NULL ) { LOG( 3, "GUID property does not exist for this device" ); return false; } int serial; CFNumberGetValue( cfSerial, kCFNumberIntType, &serial ); std::stringstream serialStream; serialStream << "0x" << std::hex << serial; *serialOut = serialStream.str(); CFRelease( cfSerial ); return true; } bool IPod::isOldIpod( io_object_t device ) { io_iterator_t iter; kern_return_t ioResult; ioResult = IORegistryEntryCreateIterator( (io_registry_entry_t)device, kIOServicePlane, kIORegistryIterateRecursively, &iter ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not create isOldIpod recursive iterator: " << mach_error_string( ioResult ) ); throw ioResult; } io_object_t curObject; while( curObject = IOIteratorNext( iter ) ) { io_name_t className; ioResult = IOObjectGetClass( curObject, className ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not get class name from current object: " << mach_error_string( ioResult ) ); throw ioResult; } IOObjectRelease( curObject ); if( strncmp( (char*)className, "IOUSBMassStorageClass", sizeof( className ) ) == 0 ) { return true; } } return false; } CFDictionaryRef IPod::createDictionaryFromXML( CFStringRef filePath ) const { CFDictionaryRef dictionary = NULL; CFDataRef resourceData = NULL; CFStringRef errorString = NULL; SInt32 errorCode; CFURLRef fileURL; fileURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, filePath, // file path name kCFURLPOSIXPathStyle, // interpret as POSIX path false ); // is it a directory? Boolean result = CFURLCreateDataAndPropertiesFromResource( kCFAllocatorDefault, // allocator fileURL, // file location &resourceData, // place to put file data NULL, // properties NULL, // properties which are desired &errorCode); // error code if( !result ) { LOG( 3, "Error: could not get data from backup file:" << CFStringGetCStringPtr ( filePath, kCFStringEncodingASCII ) ); CFRelease( fileURL ); return dictionary; } dictionary = (CFDictionaryRef)CFPropertyListCreateFromXMLData( kCFAllocatorDefault, resourceData, kCFPropertyListImmutable, &errorString); CFRelease( resourceData ); CFRelease( fileURL ); return dictionary; } bool IPod::queryIPhoneManual() const { CFDictionaryRef iTunesFiles; UInt8 manualMode; CFDataRef iTunesPrefsData; CFMutableStringRef infoPlistPath = CFStringCreateMutable( kCFAllocatorDefault, 0 ); CFStringAppendCString( infoPlistPath, ::getenv( "HOME" ), kCFStringEncodingASCII ); CFStringAppendCString( infoPlistPath, "/Library/Application Support/MobileSync/Backup/", kCFStringEncodingASCII ); CFStringAppendCString( infoPlistPath, m_serial.c_str(), kCFStringEncodingASCII ); CFStringAppendCString( infoPlistPath, "/Info.plist", kCFStringEncodingASCII ); CFDictionaryRef propertyList = createDictionaryFromXML( infoPlistPath ); CFRelease( infoPlistPath ); if( propertyList == NULL ) { LOG( 3, "Error: Could not read Info.plist file - presuming automatic sync enabled" ); return false; } iTunesFiles = (CFDictionaryRef)CFDictionaryGetValue( propertyList, CFSTR( "iTunes Files" ) ); if( iTunesFiles == NULL ) { LOG( 3, "Error: Could not extract the iTunes Files property from Info.plist file. " "Presuming automatic sync enabled." ); return false; } iTunesPrefsData = (CFDataRef)CFDictionaryGetValue( iTunesFiles, CFSTR( "iTunesPrefs" ) ); if( iTunesPrefsData == NULL ) { LOG( 3, "Error: Could not read the iTunesPrefs data from Info.plist - iTunesFiles section. " "Presuming automatic sync enabled." ); return false; } if( CFDataGetLength( iTunesPrefsData ) < 11 ) { LOG( 3, "Error: Data length of iTunesPrefs data is too small. " "Presuming automatic sync enabled." ); return false; } /* The 10th byte of the iTunesPrefs file determines whether the iPod is * in manual or automatic mode. * Automatic == 1, Manual == 0 */ CFDataGetBytes( iTunesPrefsData, CFRangeMake( 10, 1 ), &manualMode ); CFRelease( iTunesPrefsData ); return (manualMode == 0); } void IPod::getDisplayName() { // fall back on the serial number if we can't find the display name m_displayName = m_serial; CFStringRef displayName; CFMutableStringRef infoPlistPath = CFStringCreateMutable( kCFAllocatorDefault, 0 ); CFStringAppendCString( infoPlistPath, ::getenv( "HOME" ), kCFStringEncodingASCII ); CFStringAppendCString( infoPlistPath, "/Library/Application Support/MobileSync/Backup/", kCFStringEncodingASCII ); CFStringAppendCString( infoPlistPath, m_serial.c_str(), kCFStringEncodingASCII ); CFStringAppendCString( infoPlistPath, "/Info.plist", kCFStringEncodingASCII ); CFDictionaryRef propertyList = createDictionaryFromXML( infoPlistPath ); CFRelease( infoPlistPath ); if( propertyList == NULL ) { LOG( 3, "Error: Could not read Info.plist file - presuming automatic sync enabled" ); return; } displayName = (CFStringRef)CFDictionaryGetValue( propertyList, CFSTR( "Display Name" ) ); if( displayName == NULL ) { LOG( 3, "Error: Could not read Display Name data from Info.plist." ); return; } CFIndex length = CFStringGetLength( displayName ); length = CFStringGetMaximumSizeForEncoding( length, kCFStringEncodingUTF8 ); char* buffer = new char[length]; CFStringGetCString( displayName, buffer, length, kCFStringEncodingUTF8 ); m_displayName = buffer; delete[] buffer; CFRelease( displayName ); } void IPod::populateIPodManual() { CFMutableStringRef iTunesPrefsPath = CFStringCreateMutable( kCFAllocatorDefault, 0 ); CFStringAppendCString( iTunesPrefsPath, m_mountPoint.c_str(), kCFStringEncodingASCII ); CFStringAppendCString( iTunesPrefsPath, "/iPod_Control/iTunes/iTunesPrefs", kCFStringEncodingASCII ); CFURLRef fileURL; fileURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, iTunesPrefsPath, // file path name kCFURLPOSIXPathStyle, // interpret as POSIX path false ); // is it a directory? CFRelease( iTunesPrefsPath ); CFDataRef iTunesPrefsData; SInt32 errorCode; Boolean result = CFURLCreateDataAndPropertiesFromResource( kCFAllocatorDefault, // allocator fileURL, // file location &iTunesPrefsData, // place to put file data NULL, // properties NULL, // properties which are desired &errorCode); // error code if( !result || CFDataGetLength( iTunesPrefsData ) < 11 ) { LOG( 3, "Error: Data length of iTunesPrefs data is too small. " "Presuming automatic sync enabled. " << std::endl << "MountPoint: " << m_mountPoint ); m_manualMode = false; return; } UInt8 manualMode; /* The 10th byte of the iTunesPrefs file determines whether the iPod is * in manual or automatic mode. * Automatic == 1, Manual == 0 */ CFDataGetBytes( iTunesPrefsData, CFRangeMake( 10, 1 ), &manualMode ); CFRelease( iTunesPrefsData ); m_manualMode = (manualMode == 0); } io_object_t //static IPod::getBaseDevice( io_object_t device ) { io_iterator_t parentIter; IOReturn ioResult = IORegistryEntryCreateIterator( device, kIOServicePlane, kIORegistryIterateParents | kIORegistryIterateRecursively, &parentIter); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not create iterator to iterate over unmounted device" << mach_error_string( ioResult ) ); } io_object_t curParent; while( curParent = IOIteratorNext( parentIter ) ) { io_name_t deviceName; ioResult = IORegistryEntryGetName( curParent, deviceName ); if( ioResult != kIOReturnSuccess ) { LOG( 3, "Could not get parent's name/class: " << mach_error_string( ioResult ) ); IOObjectRelease( curParent ); continue; } if( strncmp( deviceName, "iPod", 4 ) == 0 || strncmp( deviceName, "iPad", 4 ) == 0 || strncmp( deviceName, "iPod mini", 9 ) == 0 || strncmp( deviceName, "IOFireWireSBP2LUN", 17 ) == 0 ) { return curParent; } IOObjectRelease( curParent ); } return NULL; } bool IPod::queryMobileScrobblerInstalled() const { DIR* plistFolder; struct dirent *dirEntry; std::string backupPath = ::getenv( "HOME" ); backupPath += "/Library/Application Support/MobileSync/Backup/"; backupPath += m_serial; backupPath += "/"; plistFolder = opendir( backupPath.c_str() ); if( plistFolder == NULL ) { LOG( 3, "Could not query itouch / iphone backup folder - presuming MobileScrobbler is NOT installed.." ); return false; } while( dirEntry = readdir( plistFolder ) ) { if( dirEntry->d_type != DT_REG ) continue; std::string curPath = backupPath + dirEntry->d_name; if( isMobileScrobblerPlist( curPath.c_str() ) ) return true; } return false; } bool IPod::isMobileScrobblerPlist( const char* path ) const { std::ifstream fin( path ); fin.seekg( 0, std::ios_base::end ); int length = fin.tellg(); if( length < 29 ) return false; fin.seekg( 0, std::ios_base::beg ); char* buffer = new char[ length ]; fin.read( buffer, length ); bool retval = false; if(strstr( buffer, "org.c99.MobileScrobbler.plist" )) retval = true; delete[] buffer; return retval; } bool IPod::waitForIpodBackup() const { std::string backupPath = ::getenv( "HOME" ); backupPath += "/Library/Application Support/MobileSync/Backup/"; backupPath += m_serial; backupPath += "/Info.plist"; struct stat infoPlistStat; long int curTime = time( NULL ); long int infoPlistModTime = 0; int count; const int timeout = 60 * 5; //5 minute timeout for( count = 0; count < timeout && infoPlistModTime < curTime; count++ ) { sleep( 1 ); //sleep 1 second if( stat( backupPath.c_str(), &infoPlistStat ) ) { //TODO: error handling pls } infoPlistModTime = infoPlistStat.st_mtimespec.tv_sec; } return count != timeout; } ================================================ FILE: plugins/iTunes/IPod_win.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "IPod.h" #include #include #include #include #include #include #include #include "Plist.h" IPod* //static IPod::newFromUsbDevice( IWbemClassObject *device, IWbemServices* wmiServices ) { IPod* ipod = new IPod; ipod->m_wmiServices = wmiServices; std::wstring name = ipod->getDeviceName( device ); getSerialVidPid( device, &ipod->m_serial, &ipod->m_vid, &ipod->m_pid ); if( L"Apple Mobile Device USB Driver" == name ) { //iPhone / iPod Touch Detected ipod->m_type = iPhone; ipod->populateIPhoneMobileScrobblerAndManualMode(); } else if( L"unknown" == name ) { ipod->m_type = unknown; } else { ipod->m_type = iPod; ipod->populateMountPoint( device ); if( ipod->m_mountPoint.length() < 1 ) { LOGL( 3, "No mount point found - presuming automatic sync mode" ); } else { ipod->populateIPodManual(); } } if( L"Apple Computer_ Inc. iPod IEEE 1394 SBP2 Device" == name ) ipod->m_connectionType = fireWire; else ipod->m_connectionType = usb; ipod->m_connectionTickCount = ::GetTickCount(); return ipod; } void //static IPod::getSerialVidPid( IWbemClassObject* const object, std::wstring *const serialOut /* = NULL */, int *const vidOut /* = NULL */, int *const pidOut /* = NULL */ ) { HRESULT result; VARIANT vtDeviceId; std::wstring deviceId; //get full pnpDevice Id String result = object->Get( L"DeviceID", 0, &vtDeviceId, 0, 0 ); if( SUCCEEDED(result) ) { deviceId = std::wstring( _bstr_t( vtDeviceId.bstrVal ) ); } else { LOGL( 3, "Error getting DeviceID from WMI: 0x" << std::hex << result ); VariantClear( &vtDeviceId ); if( vidOut ) *vidOut = 0; if( pidOut ) *pidOut = 0; if( serialOut ) *serialOut = L"unknown"; return; } //seperate deviceId into tokens delimited by a '\' std::vector tokens; tokenize( deviceId, tokens, L"\\" ); if( tokens.size() < 3 ) { LOGWL( 3, L"Could not determine serial / pid / vid from deviceID string: " << deviceId ); VariantClear( &vtDeviceId ); if( vidOut ) *vidOut = 0; if( pidOut ) *pidOut = 0; if( serialOut ) *serialOut = L"unknown"; return; } //serial number ( + optional &APPL0 if iPod classic ) is always 3rd element of vector std::vector serialTokens; tokenize( tokens.at(2), serialTokens, L"&" ); if( serialOut ) *serialOut = serialTokens.at( 0 ); // vid / pid information is in 2nd element of vector // split on & std::vector vidPidTokens; tokenize( tokens.at(1), vidPidTokens, L"&" ); if( vidPidTokens.size() < 2 ) { LOGWL( 3, L"Could not determine pid / vid from vidpid string: " << tokens.at(1) ); VariantClear( &vtDeviceId ); if( vidOut ) *vidOut = 0; if( pidOut ) *pidOut = 0; return; } std::wstring vid = vidPidTokens.at( 0 ); std::wstring pid = vidPidTokens.at( 1 ); std::wstringstream vidStream( vid.substr( 4, vid.length() - 4 ) ); std::wstringstream pidStream( pid.substr( 4, pid.length() - 4 ) ); //convert hex string to int if( pidOut ) pidStream >> std::hex >> *pidOut; if( vidOut ) vidStream >> std::hex >> *vidOut; return; } std::wstring IPod::getDeviceName( IWbemClassObject* const object ) const { HRESULT result; VARIANT vtName; std::wstring retVal; result = object->Get( L"Name", 0, &vtName, 0, 0 ); if( FAILED( result ) ) { LOGL( 3, "Error getting Name from WMI: 0x" << std::hex << result ); retVal = L"unknown"; } else { retVal = std::wstring( _bstr_t( vtName.bstrVal ) ); VariantClear( &vtName ); } return retVal; } bool IPod::hasTimeoutExpired( DWORD timeoutValue ) { if( ( ::GetTickCount() - m_connectionTickCount ) > timeoutValue ) { return true; } return false; } void //static IPod::tokenize( const std::wstring & str, std::vector & tokens, const std::wstring & delimiters /* = L" " */ ) { // Skip delimiters at beginning. std::wstring::size_type last_pos = str.find_first_not_of(delimiters, 0); // Find first "non-delimiter". std::wstring::size_type pos = str.find_first_of(delimiters, last_pos); while (std::wstring::npos != pos || std::wstring::npos != last_pos) { // Found a token, add it to the vector. tokens.push_back(str.substr(last_pos, pos - last_pos)); // Skip delimiters. Note the "not_of" last_pos = str.find_first_not_of(delimiters, pos); // Find next "non-delimiter" pos = str.find_first_of(delimiters, last_pos); } } void IPod::populateMountPoint( IWbemClassObject *device ) { char driveLetter = NULL; int i; LOGL( 3, "Searching for iPod mountpoint.." ); for( i = 0; i < 100 && driveLetter == NULL; i++ ) { driveLetter = getDriveLetterFromPnPEntity( device ); Sleep( 100 ); } if( driveLetter == NULL ) { LOGL( 3, "Error: could not determine mount point of connected iPod after 10 seconds of waiting." ); } else { m_mountPoint = driveLetter; m_mountPoint += ":"; LOGL( 3, "Mount point " << m_mountPoint << " found after" << ( (float)i / 10.0f) << "seconds" ); } } char IPod::getDriveLetterFromPnPEntity( IWbemClassObject* device ) const { char driveLetter = NULL; std::wstring query = L"select * from Win32_DiskDrive where PNPDeviceID LIKE '%"; query += m_serial; query += L"%'"; IEnumWbemClassObject* enumerator; HRESULT result = m_wmiServices->ExecQuery( bstr_t("WQL"), bstr_t(query.c_str()), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &enumerator ); if (FAILED(result)) { LOGL( 3, "ExecQuery Error code = 0x" << std::hex << result ); return NULL; } ULONG returnCount; IWbemClassObject* diskDrive; enumerator->Next( WBEM_INFINITE, 1, &diskDrive, &returnCount ); if( returnCount > 0 ) { driveLetter = getDriveLetterFromDiskDrive( diskDrive ); diskDrive->Release(); } enumerator->Release(); return driveLetter; } char IPod::getDriveLetterFromDiskDrive( IWbemClassObject* diskDrive ) const { char driveLetter = NULL; VARIANT vtName; diskDrive->Get( L"Name", 0, &vtName, 0, 0 ); std::wstring query = L"Associators Of {Win32_DiskDrive.DeviceID=\""; query += vtName.bstrVal; query += L"\"} where AssocClass = Win32_DiskDriveToDiskPartition"; size_t pos = 0; for( pos = query.find(L"\\", pos); pos != query.npos ; pos = query.find(L"\\", pos) ) { query.replace(pos, 1, L"\\\\"); pos += 2; } IEnumWbemClassObject* diskPartitionEnum; HRESULT result = m_wmiServices->ExecQuery( bstr_t("WQL"), bstr_t(query.c_str()), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &diskPartitionEnum ); if (FAILED(result)) { LOGL( 3, "ExecQuery Error code = 0x" << std::hex << result ); return NULL; } ULONG diskEnumReturnCount; IWbemClassObject* Partition; diskPartitionEnum->Next( WBEM_INFINITE, 1, &Partition, &diskEnumReturnCount ); if( diskEnumReturnCount > 0 ) { driveLetter = getDriveLetterFromPartition( Partition ); Partition->Release(); } diskPartitionEnum->Release(); return driveLetter; } char IPod::getDriveLetterFromPartition( IWbemClassObject* partition ) const { char driveLetter = NULL; VARIANT vtPartitionID; partition->Get( L"DeviceID", 0, &vtPartitionID, 0, 0 ); std::wstring query = L"associators of {Win32_DiskPartition.DeviceID=\""; query += vtPartitionID.bstrVal; query += L"\"} where AssocClass = Win32_LogicalDiskToPartition"; IEnumWbemClassObject* logicalDiskEnum; HRESULT result = m_wmiServices->ExecQuery( bstr_t("WQL"), bstr_t(query.c_str()), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &logicalDiskEnum ); if (FAILED(result)) { LOGL( 3, "ExecQuery Error code = 0x" << std::hex << result ); return NULL; } ULONG logicalDiskEnumReturnCount; IWbemClassObject* logicalDisk; logicalDiskEnum->Next( WBEM_INFINITE, 1, &logicalDisk, &logicalDiskEnumReturnCount ); if( logicalDiskEnumReturnCount > 0 ) { VARIANT vtDriveLetter; logicalDisk->Get( L"DeviceID", 0, &vtDriveLetter, 0, 0); //This returns the first byte of the first wchar_t in the string //which will be the drive letter of the drive unless microsoft go //and do something stupid like enable unicode drive letters! driveLetter = (reinterpret_cast( vtDriveLetter.pbstrVal ))[0]; logicalDisk->Release(); } logicalDiskEnum->Release(); return driveLetter; } void IPod::populateIPodManual() { std::string iTunesPrefsPath = m_mountPoint + "\\iPod_Control\\iTunes\\iTunesPrefs"; FILE* fp = fopen( iTunesPrefsPath.c_str(), "r" ); try { if (!fp) throw "Couldn't open"; int r = fseek( fp, 10, SEEK_SET ); if (r != 0) throw "Couldn't seek to byte 10"; int c = fgetc( fp ); m_manualMode = (c == 0); } catch (const char* message) { LOGL( 2, message << " `" << iTunesPrefsPath << "'" ); } if( fp ) fclose( fp ); LOGL( 3, "Sync mode detected: " << ( m_manualMode ? "Manual" : "Automatic" ) ); } void IPod::populateIPhoneMobileScrobblerAndManualMode() { WCHAR appData[ MAX_PATH ]; HRESULT result = ::SHGetFolderPathW( NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, appData ); if( result != S_OK ) { LOGL( 3, "Could not get CSIDL_APPDATA location - presuming iPhone is in automatic sync mode and mobile scrobbler is NOT installed." ); return; } std::wstring backupPath = appData; backupPath += L"\\Apple Computer\\MobileSync\\Backup\\"; backupPath += m_serial; backupPath += L"\\"; std::wstring infoPlistPath = backupPath + L"Info.plist"; waitForIpodBackup( infoPlistPath ); m_mobileScrobblerInstalled = queryMobileScrobblerInstalled( backupPath ); m_manualMode = queryIPhoneManual( infoPlistPath ); } void IPod::waitForIpodBackup( const std::wstring& backupPath ) const { struct _stat infoPlistStat; time_t curTime = time( NULL ); time_t infoPlistModTime = 0; int i = 0; for( i = 0; infoPlistModTime < curTime && i < (5 * 60); i++ ) { Sleep( 1000 ); if( _wstat( backupPath.c_str(), &infoPlistStat ) ) { //TODO: error handling pls } infoPlistModTime = infoPlistStat.st_mtime; } if( infoPlistModTime < curTime ) LOGL( 3, "Waiting for ipod backup has timed out - " "Presuming that settings haven't changed since last backup." ); } bool IPod::queryIPhoneManual( const std::wstring& infoPlistPath ) const { char manualMode; try { std::ifstream plistStream( infoPlistPath.c_str() ); if( plistStream.fail() ) throw std::string( "Could not open info.plist backup file." ); Plist plist( plistStream ); Element iTunesPrefs = plist[0][ "iTunes Files" ][ "iTunesPrefs" ]; if( iTunesPrefs.getDataLength() < 10 ) throw std::string( "iTunesPrefs data looks too short" ); manualMode = iTunesPrefs.getData()[10]; } catch( const std::string& e ) { LOGL( 3, "Could not determine whether iPod is in manual / automatic mode error: " << e << ". Presuming automatic mode."); manualMode = 1; } return (manualMode == 0); } bool IPod::queryMobileScrobblerInstalled( const std::wstring& backupPath ) const { std::wstring searchPath = backupPath + L"*.mdbackup"; WIN32_FIND_DATAW fileInfo; HANDLE dirHandle = ::FindFirstFileW( searchPath.c_str(), &fileInfo ); if( dirHandle == INVALID_HANDLE_VALUE ) { LOGL( 3, "Could not search the backup folder for mdbackup files - presuming Mobile scrobbler is NOT installed." ); return false; } do{ std::wstring curPath = backupPath + fileInfo.cFileName; if( isMobileScrobblerPlist( curPath ) ) return true; }while( ::FindNextFileW( dirHandle, &fileInfo ) != 0 ); FindClose( dirHandle ); return false; } bool IPod::isMobileScrobblerPlist( const std::wstring& path ) const { std::ifstream fin( path.c_str() ); fin.seekg( 0, std::ios_base::end ); int length = fin.tellg(); fin.seekg( 0, std::ios_base::beg ); char* buffer = new char[ length ]; fin.read( buffer, length ); bool retval = false; if(strstr (buffer, "org.c99.MobileScrobbler.plist" )) retval = true; delete[] buffer; return retval; } ================================================ FILE: plugins/iTunes/ITunesComThread.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifdef WIN32 #include "ITunesComThread.h" #include "ITunesPlaysDatabase.h" #include "ITunesComWrapper.h" #include "common/c++/Logger.h" #include "Moose.h" #include "EncodingUtils.h" #include #include #include #include using namespace std; // We use a custom message to tell our thread message loop to sync enum { WM_SYNC_PLAYS_DB = WM_USER, WM_STOP, WM_IS_PLAYING }; /****************************************************************************** ITunesComThread ******************************************************************************/ ITunesComThread::ITunesComThread() : m_com( 0 ), m_db( 0 ), m_comEnabled( true ), m_threadHandle( 0 ), m_hWnd( 0 ), m_timeOfLastTrackChange( 0 ), m_retrying( false ), m_retryAttempts( 0 ) { InitializeCriticalSection(&m_critSect); startComThread(); } ITunesComThread::~ITunesComThread() { LOGL( 3, "~ITunesComThread()" ); // Thread shutdown has been initialised already via the OnAboutToQuit event DWORD waitResult = WaitForSingleObject( m_threadHandle, 5000 ); if ( waitResult == WAIT_TIMEOUT || waitResult == WAIT_FAILED ) { LOGL( 2, "Wait for ITunesComThread thread termination " << ( ( waitResult == WAIT_TIMEOUT ) ? "timed out. " : "failed. " ) << "GetLastError(): " << GetLastError() ); } else { LOGL( 3, "iTunes COM thread terminated" ); } CloseHandle( m_threadHandle ); DeleteCriticalSection( &m_critSect ); } void ITunesComThread::startComThread() { unsigned threadId; m_threadHandle = reinterpret_cast( _beginthreadex( NULL, // security crap 0, // stack size threadMain, // start function reinterpret_cast(this), // argument to thread 0, // run straight away &threadId ) ); // thread id out if ( m_threadHandle == 0 ) // Error { // FIXME LOGL( 1, "Failed to start iTunes COM thread: " << string( strerror(errno) ) ); } } void ITunesComThread::syncTrack( const VisualPluginTrack& vpt ) { // If the com thread was closed due to a close signal // but iTunes didn't actaully close we should start up our thread again if ( !m_com ) startComThread(); // Needs guarding because it will be read by the worker thread after the // PostMessage. If we come into this function twice before the worker thread // has had a chance to wake up, it shouldn't matter that we miss we the first // track. The reason being that if two or more tracks come in in such quick // succession, they have most likely been skipped and their playcounts // won't have increased. EnterCriticalSection( &m_critSect ); m_visTrackToSync = vpt; LeaveCriticalSection( &m_critSect ); PostMessage( m_hWnd, WM_SYNC_PLAYS_DB, reinterpret_cast( this ), 0 ); } void ITunesComThread::stop() { // If the com thread was closed due to a close signal // but iTunes didn't actaully close we should start up our thread again if ( !m_com ) startComThread(); PostMessage( m_hWnd, WM_STOP, reinterpret_cast( this ), 0 ); } void ITunesComThread::callbackIfStopped( void (*callback)() ) { if ( !m_com ) startComThread(); SendMessageCallback( m_hWnd, WM_IS_PLAYING, reinterpret_cast( this ), 0, ITunesComThread::isPlayingCallback, reinterpret_cast( callback ) ); } bool ITunesComThread::isPlaying() { bool isPlaying = true; try { m_com->currentTrack(); } catch (...) { isPlaying = false; } return isPlaying; } VOID ITunesComThread::isPlayingCallback( HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT isPlaying ) { void (*callback)() = reinterpret_cast( dwData ); if ( !isPlaying ) callback(); } void ITunesComThread::onDatabaseChanged( std::vector deletedObjects, std::vector changedObjects ) { /* for ( int i = 0; i < changedObjects.size(); ++i ) { ITunesEventInterface::ITunesIdSet ids = changedObjects.at( i ); ITunesTrack t = m_com->track( ids ); if ( !t.isNull() ) { LOGL( 3, "Changed track: " << t.toString() ); } } */ } void ITunesComThread::onPlay( ITunesTrack track ) { /* LOGWL( 3, "onPlay track: " << track.toString() << " " << track.playCount() ); LOGWL( 3, "onPlay m_lastTrack: " << m_lastTrack.toString() << " " << m_lastTrack.initialPlayCount() ); if ( !track.isSameAs( m_lastTrack ) ) { if ( m_loopMode ) { // We sync here because otherwise we will miss looped tracks // (the cunts don't emit a stopped when they exit the loop). // // They do however emit a stop if they are skipped. In that case // it will have been synced in onStop and the m_lastTrack has been // set to null. So only sync here if it's non-null. // // The remaining flaw in this system is that if the user presses // stop on a looped track, we will not get a stop event. Therefore // the playcounts for that track will not be synced until the user // starts another track. if ( !m_lastTrack.isNull() ) { LOGL( 3, "Leaving loop mode, syncing" ); syncComTrackInThread( m_lastTrack ); } } // This stores the current playcount internally at this point m_lastTrack = ExtendedITunesTrack::from( track ); m_loopMode = false; } else { // Don't sync when we're looping one track as iTunes event emissions are fucked. LOGL( 3, "Same track detected, loopmode = true" ); m_loopMode = true; } */ } void ITunesComThread::onStop( ITunesTrack ) { //LOGWL( 3, "onStop m_lastTrack: " << m_lastTrack.toString() << " " << m_lastTrack.initialPlayCount() ); //LOGWL( 3, "onStop current track: " << m_com->currentTrack().toString() << " " << m_com->currentTrack().playCount() ); // We compare the current track to last track here so as to prevent syncing when // we're in 1x loop mode. Once iTunes changes to a different track we will sync. // This conditional will also not hit when the user pressed stop on a track as iTunes // considers the stopped track still current (it's still displayed in the iTunes // current track window thingie). However, this is not a problem as the playcount // hasn't increased in this case, so a sync isn't necessary. /* if ( !m_lastTrack.isSameAs( m_com->currentTrack() ) ) { // We sync here because otherwise we will miss the sync at end of playlist. // We blank the m_lastTrack to prevent a double sync from happening when // we come into onPlay after leaving loop mode by skipping. syncComTrackInThread( m_lastTrack ); m_lastTrack = ExtendedITunesTrack(); } else LOGL( 3, "Same track, not syncing" ); */ // Why do we clear this here? // If we don't, we know that we always have an m_lastTrack in onPlay and can compare // to see if we're looping the same track. //m_lastTrack = ExtendedITunesTrack(); } void ITunesComThread::doStop() { //LOGWL( 3, "onStop m_lastTrack: " << m_lastTrack.toString() << " " << m_lastTrack.initialPlayCount() ); //LOGWL( 3, "onStop current track: " << m_com->currentTrack().toString() << " " << m_com->currentTrack().playCount() ); // We compare the current track to last track here so as to prevent syncing when // we're in 1x loop mode. Once iTunes changes to a different track we will sync. // This conditional will also not hit when the user pressed stop on a track as iTunes // considers the stopped track still current (it's still displayed in the iTunes // current track window thingie). However, this is not a problem as the playcount // hasn't increased in this case, so a sync isn't necessary. ITunesTrack currentTrack; try { currentTrack = m_com->currentTrack(); } catch (...) { } if ( !m_lastTrack.isSameAs( currentTrack ) ) { // We sync here because otherwise we will miss the sync at end of playlist. // We blank the m_lastTrack to prevent a double sync from happening when // we come into onPlay after leaving loop mode by skipping. syncComTrackInThread( m_lastTrack ); m_lastTrack = ExtendedITunesTrack(); } else LOGL( 3, "Same track, not syncing" ); // Why do we clear this here? // If we don't, we know that we always have an m_lastTrack in onPlay and can compare // to see if we're looping the same track. //m_lastTrack = ExtendedITunesTrack(); } void ITunesComThread::onTrackChanged( ITunesTrack track ) { // This is fired for "joined CD tracks" (see Advanced menu). // However, iTunes doesn't maintain playcounts for CD tracks so we // don't need to do any syncing in here. // The other thing that will cause this event to fire is if the user // changed the metadata of a track. This should never cause a playcount // change either so no need to sync. We should probably update our // database with the new path here though. TODO! //LOGWL( 3, "onTrackChanged track: " << track.toString() << " " << track.playCount() ); //LOGWL( 3, "onTrackChanged m_lastTrack: " << m_lastTrack.toString() << " " << m_lastTrack.initialPlayCount() ); } void ITunesComThread::onAboutToPromptUserToQuit() { // Terminate the thread's message loop PostQuitMessage( 0 ); } void ITunesComThread::onComCallsDisabled() { LOGL( 3, "" ); m_comEnabled = false; } void ITunesComThread::onComCallsEnabled() { LOGL( 3, "" ); m_comEnabled = true; syncQueue(); } unsigned ITunesComThread::eventLoop() { setupWndProc(); UINT returnCode = 0; try { // This call will cause the bootstrap to happen first time it's called m_db = new ITunesPlaysDatabase(); // This might take a long time if iTunes isn't responding to the CoCreateInstance m_com = new ITunesComWrapper( this ); try { LOGWL( 3, "iTunes version: " << m_com->iTunesVersion() ); } catch ( ITunesException& ) { } BOOL messageVal; MSG message; while ( ( messageVal = GetMessage( &message, NULL, 0, 0 ) ) != 0 ) { if ( messageVal == -1 ) { LOGL( 3, "GetMessage error: " << GetLastError() ); continue; } else { TranslateMessage( &message ); DispatchMessage( &message ); } } // This is the wParam of the WM_QUIT message returnCode = static_cast( message.wParam ); } catch ( ITunesException& e ) { LOGL( 1, "FATAL ERROR: " << e.what() ); } delete m_com; m_com = 0; delete m_db; m_db = 0; LOGL( 3, "Thread shutting down" ); return returnCode; } bool ITunesComThread::setupWndProc() { WNDCLASSEX wndClass; wndClass.cbSize = sizeof( wndClass ); wndClass.style = NULL; wndClass.lpfnWndProc = ITunesComThread::wndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = GetModuleHandle( NULL ); wndClass.hIcon = NULL; wndClass.hCursor = NULL; wndClass.hbrBackground = NULL; wndClass.lpszMenuName = ""; wndClass.lpszClassName = "ITunesComThreadClass"; wndClass.hIconSm = NULL; ATOM regClassResult = RegisterClassEx( &wndClass ); if ( regClassResult == 0 ) { LOGL( 1, "RegisterClassEx failed: " << regClassResult ); LOGL( 1, "Last Error Code: " << GetLastError() ); return false; } m_hWnd = CreateWindow( "ITunesComThreadClass", "ITunesComThreadWindow", NULL, 0, 0, 0, 0, NULL, NULL, NULL, NULL ); if ( m_hWnd == NULL ) { LOGL( 1, "CreateWindow failed: " << m_hWnd ); LOGL( 1, "Last Error Code: " << GetLastError() ); return false; } return true; } // static LRESULT CALLBACK ITunesComThread::wndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { if ( uMsg == WM_SYNC_PLAYS_DB ) { ITunesComThread* instance = reinterpret_cast( wParam ); instance->syncVisTrackInThread(); return 0; } else if ( uMsg == WM_STOP ) { ITunesComThread* instance = reinterpret_cast( wParam ); instance->doStop(); return 0; } else if ( uMsg == WM_IS_PLAYING ) { ITunesComThread* instance = reinterpret_cast( wParam ); return instance->isPlaying(); } else if ( uMsg == WM_TIMER ) { ITunesComThread* instance = reinterpret_cast( wParam ); instance->syncVisTrackInThread(); return 0; } else { return DefWindowProc( hwnd, uMsg, wParam, lParam ); } } void ITunesComThread::syncComTrackInThread( const ExtendedITunesTrack& changedTrack ) { try { if ( !changedTrack.isNull() ) { // This is used if we sync from the COM events (which we currently don't) if ( !m_db->sync( changedTrack ) ) { LOGWL( 2, "ITunesComThread failed to update the local database for: " << changedTrack.toString() ); } if ( !m_db->syncOld( changedTrack ) ) { LOGWL( 2, "ITunesComThread failed to update the old local database for: " << changedTrack.toString() ); } } } catch ( ITunesException& ) { // Ignore this track } } void ITunesComThread::syncVisTrackInThread() { if ( m_comEnabled ) { ExtendedITunesTrack currentComTrack; try { // TODO: potential sanity checking of this track against the m_visTrackToSync currentComTrack = ExtendedITunesTrack::from( m_com->currentTrack() ); if ( !m_lastTrack.isNull() ) { // HACK WARNING: all this crap was introduced last minute when we found that iTunes // had stopped always returning the new playcount on track change. This logic has // got a bit too complicated, refactor. DWORD elapsed = GetTickCount() - m_timeOfLastTrackChange; if ( !m_retrying ) { // To avoid lots of superfluous sync attempts on repeated skipping, we // don't bother trying to sync if less than 1s has passed. m_timeOfLastTrackChange = GetTickCount(); if ( elapsed < ( 1 * 1000 ) ) { LOGL( 3, "Less than 1 second since last change, won't sync" ); goto refreshCurrentTrack; } } else { // Check if we've been retrying for too long and should give up. if ( elapsed > ( 15 * 1000 ) ) { LOGL( 3, "Retried for 15 seconds and still no diff. Give up." ); KillTimer( m_hWnd, (UINT_PTR)this ); m_retrying = false; goto refreshCurrentTrack; } } // Check if the diff is 0 and if so, go into retry mode. We have to do this // because iTunes doesn't seem to update its internal playcount immediately // after the track finished int diff = 0; try { diff = m_lastTrack.playCountDifference(); } catch( PlayCountException& ) { LOGL( 2, "Playcount Exception when reading initial diff, can't sync" ); diff = 0; } if ( diff == 0 ) { if ( !m_retrying ) { LOGL( 3, "Got a diff of 0, starting retry timer" ); SetTimer( m_hWnd, (UINT_PTR)this, 1000, 0 ); // 1-sec timer m_retrying = true; m_retryAttempts = 0; } else { m_retryAttempts++; } return; } else { if ( m_retrying ) LOGL( 3, "Got a diff of " << diff << " after " << m_retryAttempts << " secs" ); KillTimer( m_hWnd, (UINT_PTR)this ); m_retrying = false; m_retryAttempts = 0; } // Sync previous track if ( !m_db->sync( m_lastTrack ) ) { LOGWL( 3, "ITunesComThread failed to update the local database for: " << m_lastTrack.toString() ); } if ( !m_db->syncOld( m_lastTrack ) ) { LOGWL( 3, "ITunesComThread failed to update the old local database for: " << m_lastTrack.toString() ); } } } catch ( ITunesException& e ) { LOGL( 2, e.what() ); } refreshCurrentTrack: LOGWL( 3, "Current track: " << currentComTrack.toString() ); m_lastTrack = currentComTrack; } else // COM is disabled { if ( m_retrying ) { KillTimer( m_hWnd, (UINT_PTR)this ); m_retrying = false; return; } // This happens when COM is unavailable due to a modal dialog displaying // in iTunes. We store the track on a queue for later processing. VisualPluginTrack lastVisTrack; EnterCriticalSection( &m_critSect ); lastVisTrack = m_visTrackToSync; LeaveCriticalSection( &m_critSect ); LOGWL( 3, "COM is disabled, putting " << lastVisTrack.track << " in queue" ); m_visTrackQueue.push_back( lastVisTrack ); } } void ITunesComThread::syncQueue() { LOGL( 3, "Size of vis track queue before weeding: " << m_visTrackQueue.size() ); // Weed out dupes in m_visTrackQueue and only keep the one with the lowest playcount. // m_lastTrack also needs to be taken into account. VisualPluginTrack current; current.path = m_lastTrack.path(); size_t i = -1; // m_lastTrack is index -1 while ( true ) { // Search for dupes of current track in the remainder (hence i+1) of list for ( size_t j = i + 1; j < m_visTrackQueue.size(); /* nowt */ ) { if ( current.path == m_visTrackQueue.at( j ).path ) m_visTrackQueue.erase( m_visTrackQueue.begin() + j ); else j++; } i++; if ( i < m_visTrackQueue.size() ) current = m_visTrackQueue.at( i ); else break; } LOGL( 3, "Size of vis track queue after weeding: " << m_visTrackQueue.size() ); // Sync tracks that were put on queue while COM was off for ( size_t n = 0; n < m_visTrackQueue.size(); ++n ) { try { VisualPluginTrack vpt = m_visTrackQueue.at( n ); // Now, we need to try and locate this track in the iTunes library // to find its current playcount. vector results = m_com->searchForTrack( ITPlaylistSearchFieldSongNames, vpt.track ); ITunesTrack ourGuy; if ( results.size() == 0 ) { // Eh? LOGWL( 2, "Couldn't find matching track in db for " << vpt.track << ". Result size 0." ); continue; } else if ( results.size() == 1 ) { // We've found our guy ourGuy = results.at( 0 ); } else { // Need to narrow it down, let's look at the path too for ( size_t i = 0; i < results.size(); ++i ) { ITunesTrack currentResult = results.at( i ); if ( currentResult.path() == vpt.path ) { // Found him ourGuy = currentResult; break; } } if ( ourGuy.isNull() ) { // Not much we can do, we'll have to drop the diff for this track LOGWL( 2, "Couldn't find matching track in db for " << vpt.track << ". Path not found." ); continue; } } ExtendedITunesTrack diffTrack = ExtendedITunesTrack::from( ourGuy ); // throws diffTrack.setInitialPlaycount( vpt.playCount ); if ( !m_db->sync( diffTrack ) ) { LOGWL( 2, "ITunesComThread failed to update the local database for: " << diffTrack.toString() ); } if ( !m_db->syncOld( diffTrack ) ) { LOGWL( 2, "ITunesComThread failed to update the old local database for: " << diffTrack.toString() ); } } catch ( ITunesException& ) { LOGL( 2, "ITunesException when trying to sync one of the tracks in the queue, ignoring track." ); } } m_visTrackQueue.clear(); // This will cause the old m_lastTrack which we still have kicking about // to get synced and the current track stored in m_lastTrack. syncVisTrackInThread(); } #endif // WIN32 ================================================ FILE: plugins/iTunes/ITunesComThread.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef ITUNESCOMTHREAD_H #define ITUNESCOMTHREAD_H #ifdef WIN32 #include "ITunesTrack.h" #include "ITunesEventInterface.h" #include #include #include /// Used to pass the track info we get from the visual plugin in one chunk struct VisualPluginTrack { std::wstring artist; std::wstring albumArtist; std::wstring track; std::wstring album; std::wstring path; unsigned long playCount; }; /** @author Erik Jalevik * @brief Sets up a thread which internally uses the ITunesComWrapper. * This is because we can't make COM calls on the iTunes main thread * whilst inside an iTunes plugin. It also acts as the event sink * for any events iTunes emits. */ class ITunesComThread : public ITunesEventInterface { public: ITunesComThread(); ~ITunesComThread(); void callbackIfStopped( void (*callback)() ); // But because the COM interface has a bug which delivers wonky events // when looping a track, we use this function to sync instead, which is // driven by the visualizer plugin stop/start events. void syncTrack( const VisualPluginTrack& vpt ); void stop(); private: // These are the COM event handlers virtual void onDatabaseChanged( std::vector deletedObjects, std::vector changedObjects ); virtual void onPlay( ITunesTrack ); virtual void onStop( ITunesTrack ); virtual void onTrackChanged( ITunesTrack ); virtual void onAboutToPromptUserToQuit(); virtual void onComCallsDisabled(); virtual void onComCallsEnabled(); void startComThread(); static unsigned __stdcall threadMain( LPVOID p ) { return reinterpret_cast(p)->eventLoop(); } /// Eternal event loop for handling action messages from other threads unsigned eventLoop(); /// We need a window with a WndProc for the iTunes event sink to work bool setupWndProc(); static LRESULT CALLBACK wndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); void syncComTrackInThread( const ExtendedITunesTrack& track ); void syncVisTrackInThread(); bool isPlaying(); static VOID CALLBACK isPlayingCallback( HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult ); /// Syncs tracks that were put on the queue while COM was disabled void syncQueue(); void doStop(); class ITunesComWrapper* m_com; class ITunesPlaysDatabase* m_db; ExtendedITunesTrack m_lastTrack; bool m_comEnabled; DWORD m_timeOfLastTrackChange; bool m_retrying; int m_retryAttempts; CRITICAL_SECTION m_critSect; VisualPluginTrack m_visTrackToSync; std::vector m_visTrackQueue; HANDLE m_threadHandle; HWND m_hWnd; }; #endif // WIN32 #endif // ITUNESCOMTHREAD_H ================================================ FILE: plugins/iTunes/ITunesComWrapper.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifdef WIN32 #include "ITunesComWrapper.h" #include "plugins/scrobsub/EncodingUtils.h" #include "common/c++/Logger.h" #include #include #include #include #include using namespace std; TheAtlModule* g_atlModule = NULL; /****************************************************************************** ITunesComWrapper ******************************************************************************/ ITunesComWrapper::ITunesComWrapper( ITunesEventInterface* handler ) : m_iTunesApp( 0 ), m_allTracks( 0 ), m_trackCount( -1 ), m_sink( 0 ), m_sinkAsUnknown( 0 ), m_sinkId( 0 ) { initialiseCom(); // throws if ( handler ) { try { connectSink( handler ); } catch ( ITunesException& ) { // Just carry on if this fails, we'll get no events but the other stuff will work } } libraryTrackCount(); // throws } ITunesComWrapper::~ITunesComWrapper() { uninitialiseCom(); } long ITunesComWrapper::libraryTrackCount() { // Can't use isValid here as it also checks the m_trackCount and m_allTracks // which this method initialises when called from the ctor. Bad class design. if ( m_iTunesApp == 0 ) throw ITunesException( "COM not initialised" ); IITLibraryPlaylist* playlist; HRESULT res = m_iTunesApp->get_LibraryPlaylist( &playlist ); handleComResult( res, L"Failed to get iTunes library playlist" ); if ( !m_allTracks ) { res = playlist->get_Tracks( &m_allTracks ); try { handleComResult( res, L"Failed to get iTunes library track collection" ); } catch ( ITunesException& ) { m_allTracks = 0; playlist->Release(); throw; } } res = m_allTracks->get_Count( &m_trackCount ); try { handleComResult( res, L"Failed to get iTunes library track count" ); } catch ( ITunesException& ) { m_trackCount = -1; playlist->Release(); throw; } playlist->Release(); return m_trackCount; } void ITunesComWrapper::setPersistentIDForTrack( ITunesTrack& t, IITTrack* track ) { VARIANT variant; VariantInit( &variant ); variant.pdispVal = reinterpret_cast( track ); variant.vt = VT_DISPATCH; long high = 0; long low = 0; HRESULT res = m_iTunesApp->get_ITObjectPersistentIDHigh( &variant, &high ); if ( res == S_OK ) res = m_iTunesApp->get_ITObjectPersistentIDLow( &variant, &low ); if ( res == S_OK ) { wstringstream ss; ss.fill( '0' ); ss.width( 8 ); ss << hex << high << low; t.setPersistentId( ss.str() ); } else { LOGW( 2, "Failed to get persistent ID" ); } } ITunesTrack ITunesComWrapper::track( long idx ) { if ( !isValid() ) throw ITunesException( "COM not initialised" ); IITTrack* track; HRESULT res = m_allTracks->get_Item( idx + 1, &track ); // COM arrays are 1-based handleComResult( res, L"Failed to get track" ); // IITTrack pointer is Released by the ITunesTrack. // Won't throw if the track has no path. ITunesTrack t( track ); setPersistentIDForTrack( t, track ); return t; } IITPlaylist* ITunesComWrapper::iPodLibraryPlaylist() { HRESULT h; #define HANDLE_COM_FAIL( x ) h = ( x ); handleComResult( h, L#x ) IITSourceCollection* sources; HANDLE_COM_FAIL( m_iTunesApp->get_Sources( &sources ) ); long n; try { HANDLE_COM_FAIL( sources->get_Count( &n ) ); } catch ( ITunesException& ) { sources->Release(); throw; } IITPlaylist* playlist = NULL; for( int i = 1; i < n + 1 && !playlist; ++i ) { IITSource* source = NULL; IITPlaylistCollection* playlists = NULL; try { HANDLE_COM_FAIL( sources->get_Item( i, &source ) ); ITSourceKind kind; HANDLE_COM_FAIL( source->get_Kind( &kind ) ); if ( kind == ITSourceKindIPod ) { HANDLE_COM_FAIL( source->get_Playlists( &playlists ) ); HANDLE_COM_FAIL( playlists->get_Item( 1, &playlist ) ); BSTR name; HANDLE_COM_FAIL( playlist->get_Name( &name ) ); LOGW( 3, "Playlist name: " << bstrToWString( name ) ); } } catch ( ITunesException& ) { // We'll just continue to the next source } if ( source != NULL ) source->Release(); if ( playlists != NULL ) playlists->Release(); } sources->Release(); if ( playlist == NULL ) throw ITunesException( "No iPod library found" ); return playlist; } long ITunesComWrapper::iPodLibraryTrackCount() { IITPlaylist* playlist = iPodLibraryPlaylist(); HRESULT res = playlist->get_Tracks( &m_allTracks ); try { handleComResult( res, L"Failed to get iPod library track collection" ); } catch ( ITunesException& ) { m_allTracks = 0; playlist->Release(); throw; } m_trackCount = -1; res = m_allTracks->get_Count( &m_trackCount ); try { handleComResult( res, L"Failed to get iPod library track collection" ); } catch ( ITunesException& ) { playlist->Release(); throw; } playlist->Release(); return m_trackCount; } ITunesTrack ITunesComWrapper::track( const ITunesEventInterface::ITunesIdSet& ids ) { if ( !isValid() ) throw ITunesException( "COM not initialised" ); IITObject* obj; HRESULT res = m_iTunesApp->GetITObjectByID( ids.sourceId, ids.playlistId, ids.trackId, ids.dbId, &obj ); handleComResult( res, L"Failed to get object from ID set" ); IITTrack* track = 0; res = obj->QueryInterface( IID_IITTrack, (void**)&track ); if ( res != S_OK || track == 0 ) { // Not a track object, ignore obj->Release(); handleComResult( res, L"ID set didn't represent a track" ); } // IITTrack pointer is Released by the ITunesTrack. // This ctor can't throw. ITunesTrack t( track ); setPersistentIDForTrack( t, track ); return t; } ITunesTrack ITunesComWrapper::currentTrack() { if ( !isValid() ) throw ITunesException( "COM not initialised" ); IITTrack* track = NULL; HRESULT res = m_iTunesApp->get_CurrentTrack( &track ); handleComResult( res, L"Failed to get current track" ); // IITTrack pointer is Released by the ITunesTrack // This ctor can't throw. ITunesTrack t( track ); setPersistentIDForTrack( t, track ); return t; } ITPlayerState ITunesComWrapper::playerState() { if ( !isValid() ) throw ITunesException( "COM not initialised" ); ITPlayerState state; HRESULT res = m_iTunesApp->get_PlayerState( &state ); handleComResult( res, L"Failed to get player state" ); return state; } long ITunesComWrapper::playerPosition() { if ( !isValid() ) throw ITunesException( "COM not initialised" ); long pos; HRESULT res = m_iTunesApp->get_PlayerPosition( &pos ); handleComResult( res, L"Failed to get player pos" ); return pos; } wstring ITunesComWrapper::iTunesVersion() { if ( !isValid() ) return L""; BSTR v; HRESULT res = m_iTunesApp->get_Version( &v ); if ( !handleComResult( res, L"Failed to get iTunes version" ) ) return L""; return bstrToWString( v ); } vector ITunesComWrapper::searchForTrack( ITPlaylistSearchField searchField, wstring searchTerm ) { vector results; if ( !isValid() ) throw ITunesException( "COM not initialised" ); IITLibraryPlaylist* playlist; HRESULT res = m_iTunesApp->get_LibraryPlaylist( &playlist ); handleComResult( res, L"Failed to get iTunes library playlist" ); _bstr_t bTerm( searchTerm.c_str() ); IITTrackCollection* tc; res = playlist->Search( bTerm, searchField, &tc ); try { handleComResult( res, L"Failed to search iTunes library" ); } catch ( ITunesException& ) { playlist->Release(); throw; } if ( tc == 0 ) { playlist->Release(); return results; } long count; tc->get_Count( &count ); try { handleComResult( res, L"Failed to get count of search results" ); } catch ( ITunesException& ) { playlist->Release(); throw; } for ( long i = 1; i < ( count + 1 ); ++i ) { try { IITTrack* track; tc->get_Item( i, &track ); handleComResult( res, L"Failed to get track out of search results" ); ITunesTrack t( track ); setPersistentIDForTrack( t, track ); results.push_back( t ); } catch ( ITunesException& ) { // Just move on to the next } } playlist->Release(); return results; } void ITunesComWrapper::initialiseCom() { LOG( 3, "Calling CoInitializeEx..." ); // Init COM HRESULT res = CoInitializeEx( NULL, COINIT_APARTMENTTHREADED ); // S_FALSE means COM has already been initialised on this thread. if ( res != S_OK && res != S_FALSE ) { LOG( 2, "Failed to initialise COM. Return: " << res ); } else { bool retry = false; do { LOG( 3, "Calling CoCreateInstance..." ); HRESULT res = 0; res = CoCreateInstance( CLSID_iTunesApp, // CLSID NULL, // not part of aggregate CLSCTX_LOCAL_SERVER, // server is on same machine IID_IiTunes, // specifies what interface we want the pointer to adhere to (a COM object can implement more than one) (PVOID*)&m_iTunesApp ); // the pointer to fill if ( res == S_OK ) { // We're all set retry = false; } else if ( res == CO_E_SERVER_EXEC_FAILURE ) { // This seems to happen if there is a modal dialog being shown in iTunes // or some other delay has occurred. Retrying should do the trick. LOG( 2, "Got CO_E_SERVER_EXEC_FAILURE, retrying..." ); retry = true; } else { logComError( res, L"Failed to get hold of iTunes instance via COM" ); m_iTunesApp = 0; retry = false; } } while ( retry ); if ( m_iTunesApp == 0 ) throw ITunesException( "Failed to get hold of iTunes instance via COM" ); LOG( 3, "Value of iTunes ptr: " << m_iTunesApp ); } } void ITunesComWrapper::connectSink( ITunesEventInterface* handler ) { LOG( 3, "Connecting sink" ); // Init COM the ATL way, needed for the CComObject to work g_atlModule = new TheAtlModule(); // Instantiate our sink object. By wrapping it in a CComObject, we get all // the IUnknown plumbing for free. HRESULT res = CComObject::CreateInstance( &m_sink ); handleComResult( res, L"Failed to create event sink object" ); m_sink->setHandler( handler ); // Query our own sink object to see if it supports the IUnknown interface (which it // obviously does) and return a reference to it as an IUnknown*. res = m_sink->QueryInterface( IID_IUnknown, reinterpret_cast( &m_sinkAsUnknown ) ); handleComResult( res, L"Getting IUnknown from ITunesComEventSink failed" ); // Hook up the outgoing events interface from iTunes with our own sink, and get hold // of the ID so we can unadvise at shutdown. res = AtlAdvise( m_iTunesApp, m_sinkAsUnknown, DIID__IiTunesEvents, &m_sinkId ); handleComResult( res, L"AtlAdvise failed" ); } void ITunesComWrapper::uninitialiseCom() { LOG( 3, "Uninitialising COM..." ); // Don't do anything unless we managed to initialise the iTunes pointer correctly if ( m_iTunesApp != 0 ) { // Release everything in reverse order to what we initialised it in if ( m_sinkId != 0 ) { HRESULT res = AtlUnadvise( m_iTunesApp, DIID__IiTunesEvents, m_sinkId ); try { handleComResult( res, L"AtlUnadvise failed but what can we do except sigh, shrug our shoulders and move on." ); } catch ( ITunesException& ) {} } if ( m_sinkAsUnknown != 0 ) m_sinkAsUnknown->Release(); if ( g_atlModule != 0 ) { delete g_atlModule; g_atlModule = 0; } if ( m_allTracks != 0 ) m_allTracks->Release(); m_iTunesApp->Release(); } // Close COM, returns no error CoUninitialize(); LOG( 3, "CoUninitialize done" ); } wstring ITunesComWrapper::bstrToWString( BSTR bstr ) { if ( bstr == 0 ) return wstring(); else { // This wraps the BSTR and the _b_str_t class will take care of // deallocating its memory so we don't have to do it. _bstr_t bstrCls( bstr, false ); return wstring( bstrCls ); } /* We used to convert it to Utf8 unsigned int len = bstrCls.length(); char* utf8buf = new char[len * 4]; memset( utf8buf, 0, len * 4 ); // _bstr_t has an operator wchar_t*() which will be used here EncodingUtils::UnicodeToUtf8( bstrCls, len, utf8buf, len * 4 ); string s( utf8buf ); delete[] utf8buf; return s; */ } // HACK: This shouldn't be here but there isn't really anywhere else to put // it right now unless we want to create a utility class inside common. string wstring2string( wstring ws ) { string s; for ( size_t i = 0; i < ws.size(); ++i ) { unsigned short us = ws.at( i ); char c = (char)us; s += c; } return s; } bool ITunesComWrapper::handleComResult( HRESULT res, wstring err ) { if ( res == S_OK ) { return false; } else { if ( err.empty() ) err = L"Generic COM error"; logComError( res, err ); throw ITunesException( wstring2string( err ).c_str() ); return true; } } void ITunesComWrapper::logComError( HRESULT res, wstring err ) { wstring sRes; switch ( res ) { case S_OK: sRes = L"S_OK"; break; case S_FALSE: sRes = L"S_FALSE"; break; case E_POINTER: sRes = L"E_POINTER"; break; case ITUNES_E_OBJECTDELETED: sRes = L"ITUNES_E_OBJECTDELETED"; break; case ITUNES_E_OBJECTLOCKED: sRes = L"ITUNES_E_OBJECTLOCKED"; break; case E_FAIL: sRes = L"E_FAIL"; break; case REGDB_E_CLASSNOTREG: sRes = L"REGDB_E_CLASSNOTREG"; break; case CLASS_E_NOAGGREGATION: sRes = L"CLASS_E_NOAGGREGATION"; break; case E_NOINTERFACE: sRes = L"E_NOINTERFACE"; break; // Happens when CoCreateInstance times out for whatever reason case CO_E_SERVER_EXEC_FAILURE: sRes = L"CO_E_SERVER_EXEC_FAILURE"; break; // We've lost the connection to iTunes case CO_E_OBJNOTCONNECTED: sRes = L"CO_E_OBJNOTCONNECTED"; break; // Happens when iTunes displays dialog case RPC_E_CALL_REJECTED: sRes = L"RPC_E_CALL_REJECTED"; break; // Happens when iTunes is shut down while we're accessing it // (couldn't find the enum value in winerror.h) case (HRESULT)0x800706BA: sRes = L"RPC_S_SERVER_UNAVAILABLE"; break; default: wchar_t buf[50]; swprintf_s( buf, 50, L"%x", static_cast( res ) ); sRes = wstring( buf ); } wostringstream os; os << L"COM error: " << err << L"\n Result code: " << sRes; LOGW( 2, os.str() ); } /****************************************************************************** ITunesComEventSink ******************************************************************************/ HRESULT ITunesComEventSink::Invoke( DISPID dispidMember, // the event in question REFIID /*riid*/, // reserved - NULL LCID /*lcid*/, // locale WORD /*wFlags*/, // context of call DISPPARAMS* pdispparams, // arguments VARIANT* /*pvarResult*/, // return value - NULL EXCEPINFO* /*pexcepinfo*/, // out param for exception info UINT* /*puArgErr*/ ) // out param for which param was wrong { // TODO: figure out if I need to free the DISPPARAMS objects #define EVENT_ERROR( name ) \ { \ if ( pdispparams == 0 ) \ { \ LOG( 2, #name " event pdispparams NULL" ); \ hr = DISP_E_PARAMNOTFOUND; \ } \ else \ { \ LOG( 2, #name " event returned wrong number of args: " << pdispparams->cArgs ); \ hr = DISP_E_BADPARAMCOUNT; \ } \ } HRESULT hr = S_OK; ITEvent evt = (ITEvent)( dispidMember ); switch ( evt ) { case ITEventDatabaseChanged: { //LOG( 3, "ondbchanged" ); // OnDatabaseChanged should have two parameters if ( pdispparams != 0 && pdispparams->cArgs == 2 ) { // IDispatch arguments are passed in reverse order onDatabaseChangedEvent( pdispparams->rgvarg[1], pdispparams->rgvarg[0] ); } else EVENT_ERROR( OnDatabaseChanged ); } break; case ITEventPlayerPlay: { //LOG( 3, "onplay" ); // Should have one parameter if ( pdispparams != 0 && pdispparams->cArgs == 1 ) { IDispatch* d = pdispparams->rgvarg[0].pdispVal; if ( d == 0 ) { LOG( 2, "d null in onPlay, sending empty track" ); // iTunes can return 0 here, so we use a null track m_handler->onPlay( ITunesTrack() ); } else { IITTrack* track = 0; HRESULT res = d->QueryInterface( IID_IITTrack, (void**)&track ); if ( res == S_OK ) { m_handler->onPlay( ITunesTrack( track ) ); } } } else EVENT_ERROR( OnPlayerPlay ); } break; case ITEventPlayerStop: { //LOG( 3, "onstop" ); // Should have one parameter if ( pdispparams != 0 && pdispparams->cArgs == 1 ) { IDispatch* d = pdispparams->rgvarg[0].pdispVal; if ( d == 0 ) { LOG( 2, "d null in onStop, sending empty track" ); // iTunes can return 0 here, so we use a null track m_handler->onStop( ITunesTrack() ); } else { IITTrack* track = 0; HRESULT res = d->QueryInterface( IID_IITTrack, (void**)&track ); if ( res == S_OK ) { m_handler->onStop( ITunesTrack( track ) ); } } } else EVENT_ERROR( OnPlayerStop ); } break; case ITEventPlayerPlayingTrackChanged: { //LOG( 3, "ontrackchanged" ); // Should have one parameter if ( pdispparams != 0 && pdispparams->cArgs == 1 ) { IDispatch* d = pdispparams->rgvarg[0].pdispVal; if ( d == 0 ) { LOG( 2, "d null in onTrackChanged, sending empty track" ); // iTunes can return 0 here, so we use a null track m_handler->onTrackChanged( ITunesTrack() ); } else { IITTrack* track = 0; HRESULT res = d->QueryInterface( IID_IITTrack, (void**)&track ); if ( res == S_OK ) { m_handler->onTrackChanged( ITunesTrack( track ) ); } } } else EVENT_ERROR( OnPlayingTrackChanged ); } break; case ITEventCOMCallsDisabled: m_handler->onComCallsDisabled(); break; case ITEventCOMCallsEnabled: m_handler->onComCallsEnabled(); break; case ITEventQuitting: break; case ITEventAboutToPromptUserToQuit: m_handler->onAboutToPromptUserToQuit(); break; case ITEventSoundVolumeChanged: break; default: hr = DISP_E_MEMBERNOTFOUND; break; } return hr; } void ITunesComEventSink::onDatabaseChangedEvent( VARIANT deletedObjectIDs, VARIANT changedObjectIDs ) { // TODO: can this really not go wrong? No docs about what happens when it fails. // Tried forcing an exception in a release build but nothing happened. CComSafeArray deleted( deletedObjectIDs.parray ); CComSafeArray changed( changedObjectIDs.parray ); std::vector vecDeleted; std::vector vecChanged; LOG( 3, "Deleted: " << deleted.GetCount() << ", Changed: " << changed.GetCount() ); for ( ULONG i = 0; i < deleted.GetCount(); ++i ) { ITunesEventInterface::ITunesIdSet ids = getIdsFromSafeArray( i, deleted ); vecDeleted.push_back( ids ); } for ( ULONG i = 0; i < changed.GetCount(); ++i ) { ITunesEventInterface::ITunesIdSet ids = getIdsFromSafeArray( i, changed ); vecChanged.push_back( ids ); } m_handler->onDatabaseChanged( vecDeleted, vecChanged ); } ITunesEventInterface::ITunesIdSet ITunesComEventSink::getIdsFromSafeArray( long index, CComSafeArray& array ) { VARIANT elem; LONG idx2d[2]; idx2d[0] = index; idx2d[1] = 0; array.MultiDimGetAt( idx2d, elem ); long sourceId = elem.lVal; idx2d[1] = 1; array.MultiDimGetAt( idx2d, elem ); long playlistId = elem.lVal; idx2d[1] = 2; array.MultiDimGetAt( idx2d, elem ); long trackId = elem.lVal; idx2d[1] = 3; array.MultiDimGetAt( idx2d, elem ); long dbId = elem.lVal; ITunesEventInterface::ITunesIdSet ids = { sourceId, playlistId, trackId, dbId }; return ids; } // Old crap, but leaving the code here for now for reference purposes // ------------------------------------------------------------------ //void //ITunesComWrapper::buildMapping() //{ // //stdext::hash_map mapping; // // cout << "Starting building map" << endl; // // DWORD startTime = GetTickCount(); // // // iterate over itunes collection // IITLibraryPlaylist* playlist; // m_iTunesApp->get_LibraryPlaylist( &playlist ); // // IITTrackCollection* coll; // playlist->get_Tracks( &coll ); // // long cnt; // coll->get_Count( &cnt ); // // for ( long i = 1; i <= cnt; ++i ) // arrays are 1-based in COM // { // IITTrack* track; // coll->get_Item( i, &track ); // // long dbId; // track->get_TrackDatabaseID( &dbId ); // // string path = pathForTrack( track ); // // //ostringstream os; // //os << "ID: " << dbId << "\nPath: " << path; // //Log( os.str() ); // // //mapping.insert( make_pair( dbId, path ) ); // } // // DWORD finishTime = GetTickCount(); // DWORD elapsed = finishTime - startTime; // // ostringstream os; // os << "Finished building mapping, time: " << elapsed; // Log( os.str() ); // // std::cout << os.str().c_str(); // // // // get runtime id and path for each track // // put these in hash table //} // // //string //ITunesComWrapper::findPersistentIdForTrack( IITTrack* track ) //{ // // Get the metadata from the track // BSTR bstrTitle = 0; // BSTR bstrArtist = 0; // BSTR bstrAlbum = 0; // bool gotMetadata = // handleComResult( track->get_Name( &bstrTitle ) ) && // handleComResult( track->get_Artist( &bstrArtist ) ) && // handleComResult( track->get_Album( &bstrAlbum ) ); // if ( !gotMetadata ) // { // Log( "Failed to get metadata" ); // return ""; // } // // string titleUtf8 = bstrToUtf8( bstrTitle ); // string artistUtf8 = bstrToUtf8( bstrArtist ); // string albumUtf8 = bstrToUtf8( bstrAlbum ); // // // Get the path to the XML // BSTR bstrPath; // HRESULT res = m_iTunesApp->get_LibraryXMLPath( &bstrPath ); // if ( res != S_OK ) // { // logComError( "COM failed to get library XML path", res ); // return ""; // } // // // Try opening it... // _bstr_t path( bstrPath ); // ///* // FILE* fp; // errno_t err = _wfopen_s( &fp, path, "r" ); // if ( err != 0 ) // { // char buf[100]; // strerror_s( buf, 100, err ); // ostringstream os; // os << "Failed to open iTunes XML file. Error: " << buf; // Log( os.str() ); // return ""; // } //*/ // // ifstream fs( static_cast( path ) ); // if ( !fs ) // { // Log( "Failed to open iTunes XML file" ); // return ""; // } // // // OK, we have the XML file ready and willing // // //} #endif // WIN32 ================================================ FILE: plugins/iTunes/ITunesComWrapper.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef ITUNESCOMWRAPPER_H #define ITUNESCOMWRAPPER_H #ifdef WIN32 #include "ITunesTrack.h" #include "ITunesEventInterface.h" // Needed for CoInitializeEx #ifndef _WIN32_DCOM #define _WIN32_DCOM #endif #include // Must include objbase before iTunes interface or it won't compile #include #include #include #include #include "lib/3rdparty/iTunesCOMAPI/iTunesCOMInterface.h" #include #include class ITunesComEventSink; /** @author Erik Jalevik * @brief Uses COM to query iTunes for track information. * All functions that access COM directly will throw an ITunesException * if something went wrong. */ class ITunesComWrapper { public: /// If you want events, pass in an implementation of the event handler interface, /// leave it as 0 and the event sink won't get initialised. ITunesComWrapper( ITunesEventInterface* handler = 0 ); ~ITunesComWrapper(); /// If this returns false, we didn't manage to initialise COM and all the /// other malarkey required for this to work. bool isValid() { return m_iTunesApp != 0 && m_trackCount != -1 && m_allTracks != 0; } /// Call libraryTrackCount first... long libraryTrackCount(); /// if you call this instead of the libraryTrackCount() equivalent /// all other functions that involve m_allTracks will reference the iPod /// instead FIXME sucks! /// NOTE as above, call this before calling track() long iPodLibraryTrackCount(); /// Then pass an index in the range 0..libraryTrackCount to this function ITunesTrack track( long idx ); /// Or an ITunesIdSet to this. An ITunesIdSet does not necessarily represent /// a track, it could be any iTunes object. If the set of IDs passed in does /// not represent a track, an ITunesException will be thrown. ITunesTrack track( const ITunesEventInterface::ITunesIdSet& ids ); /// Get currently playing track in iTunes ITunesTrack currentTrack(); /// Get state of player (stopped/running etc) ITPlayerState playerState(); /// Get player position in seconds long playerPosition(); /// Get iTunes version std::wstring iTunesVersion(); /// Search library. ITPlaylistSearchField is an iTunes type. std::vector searchForTrack( ITPlaylistSearchField searchField, std::wstring searchTerm ); private: void initialiseCom(); void uninitialiseCom(); void setPersistentIDForTrack( ITunesTrack& t, IITTrack* track ); /// Connects iTunes events to our event sink void connectSink( ITunesEventInterface* handler ); /// Throws if no iPod found in iTunes IITPlaylist* iPodLibraryPlaylist(); static std::wstring bstrToWString( BSTR bstr ); static bool handleComResult( HRESULT res, std::wstring err = L"" ); static void logComError( HRESULT res, std::wstring err ); IiTunes* m_iTunesApp; IITTrackCollection* m_allTracks; long m_trackCount; CComObject* m_sink; DWORD m_sinkId; // needed to close the connection IUnknown* m_sinkAsUnknown; friend ITunesTrack; }; /** @author Erik Jalevik * @brief Internal handler for COM events fired by iTunes. */ class ATL_NO_VTABLE ITunesComEventSink : public CComObjectRootEx, public IDispatch { public: BEGIN_COM_MAP(ITunesComEventSink) COM_INTERFACE_ENTRY( IDispatch ) COM_INTERFACE_ENTRY_IID( DIID__IiTunesEvents, IDispatch ) END_COM_MAP() ITunesComEventSink() : m_handler( 0 ) { } /// You must call this function with a valid handler after initialising this class. void setHandler( ITunesEventInterface* handler ) { m_handler = handler; } // IDispatch stuff HRESULT __stdcall Invoke( DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr ); HRESULT __stdcall GetTypeInfoCount( UINT* ) { return E_NOTIMPL; } HRESULT __stdcall GetTypeInfo( UINT, LCID, ITypeInfo** ) { return E_NOTIMPL; } HRESULT __stdcall GetIDsOfNames( REFIID, LPOLESTR*, UINT, LCID, DISPID* ) { return E_NOTIMPL; } private: ITunesEventInterface* m_handler; void onDatabaseChangedEvent( VARIANT deletedObjectIDs, VARIANT changedObjectIDs ); ITunesEventInterface::ITunesIdSet getIdsFromSafeArray( long index, CComSafeArray& array ); }; // We need one of these things hanging around for the ATL calls to work. // It's initialised in connectSink. class TheAtlModule : public CAtlDllModuleT { }; //class TheAtlModule : public CAtlExeModuleT { }; #endif // WIN32 #endif // ITUNESCOMTHREAD_H ================================================ FILE: plugins/iTunes/ITunesEventInterface.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef ITUNESEVENTINTERFACE_H #define ITUNESEVENTINTERFACE_H #ifdef WIN32 #include /** @author Erik Jalevik * @brief This interface should be implemented by anyone interested in receiving * events from iTunes without having to touch COM. */ class ITunesEventInterface { public: struct ITunesIdSet { long sourceId; long playlistId; long trackId; long dbId; }; virtual void onDatabaseChanged( std::vector deletedObjects, std::vector changedObjects ) = 0; virtual void onPlay( ITunesTrack ) = 0; virtual void onStop( ITunesTrack ) = 0; virtual void onTrackChanged( ITunesTrack ) = 0; virtual void onAboutToPromptUserToQuit() = 0; virtual void onComCallsDisabled() = 0; virtual void onComCallsEnabled() = 0; }; #endif // WIN32 #endif // ITUNESEVENTINTERFACE_H ================================================ FILE: plugins/iTunes/ITunesExceptions.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef ITUNES_EXCEPTIONS_H #define ITUNES_EXCEPTIONS_H #include class ITunesException : public std::runtime_error { public: ITunesException( const char* s = "ITunesException" ) : std::runtime_error( s ) { } }; class PlayCountException : public ITunesException { public: PlayCountException() : ITunesException( "PlayCountException" ) { } }; #endif // ITUNESEXCEPTIONS_H ================================================ FILE: plugins/iTunes/ITunesPlaysDatabase.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "ITunesPlaysDatabase.h" #include "Moose.h" #include "common/c++/logger.h" #include "ITunesTrack.h" #include #include #ifndef WIN32 #include #include #endif #include "sqlite3.h" // this is a macro to ensure useful location information in the log #define logError( db ) \ LOG( 2, "sqlite error: " \ << sqlite3_errcode( db ) \ << " (" << sqlite3_errmsg( db ) << ")" ); ITunesPlaysDatabase::ITunesPlaysDatabase() { #ifdef WIN32 std::string path = Moose::wStringToUtf8( Moose::applicationSupport() + L"\\iTunesPlays.db" ); #else std::string path = Moose::applicationSupport() + "iTunesPlays.db"; #endif if ( sqlite3_open( path.c_str(), &m_db ) != SQLITE_OK ) { LOG( 2, "ERROR: Could not open iTunesPlays Database: " << path ); logError( m_db ); } #ifdef WIN32 prepare(); #endif } void ITunesPlaysDatabase::prepare() { #ifdef WIN32 #define BOOTSTRAP_FLAG L"--bootstrap" #else #define BOOTSTRAP_FLAG "--bootstrap" #endif // if twiddly is already running, quite probably we're already bootstrapping if ( (!isValid() || needsBootstrap()) && !Moose::isTwiddlyRunning() ) Moose::exec( Moose::twiddlyPath(), BOOTSTRAP_FLAG ); } ITunesPlaysDatabase::~ITunesPlaysDatabase() { sqlite3_close( m_db ); //docs says passing NULL is harmless noop } #ifdef WIN32 /* ** A pointer to an instance of this structure is passed as the user-context ** pointer when registering for an unlock-notify callback. */ typedef struct UnlockNotification UnlockNotification; struct UnlockNotification { int fired; /* True after unlock event has occurred */ CRITICAL_SECTION mutex; CONDITION_VARIABLE cond; }; /* ** This function is an unlock-notify callback registered with SQLite. */ static void unlock_notify_cb(void **apArg, int nArg) { for( int i = 0; i < nArg ; i++) { UnlockNotification *p = (UnlockNotification *)apArg[i]; EnterCriticalSection( &p->mutex ); p->fired = 1; //WakeConditionVariable( &p->cond ); LeaveCriticalSection( &p->mutex ); } } /* ** This function assumes that an SQLite API call (either sqlite3_prepare_v2() ** or sqlite3_step()) has just returned SQLITE_LOCKED. The argument is the ** associated database connection. ** ** This function calls sqlite3_unlock_notify() to register for an ** unlock-notify callback, then blocks until that callback is delivered ** and returns SQLITE_OK. The caller should then retry the failed operation. ** ** Or, if sqlite3_unlock_notify() indicates that to block would deadlock ** the system, then this function returns SQLITE_LOCKED immediately. In ** this case the caller should not retry the operation and should roll ** back the current transaction (if any). */ static int wait_for_unlock_notify(sqlite3 *db){ int rc; UnlockNotification un; /* Initialize the UnlockNotification structure. */ un.fired = 0; InitializeCriticalSection( &un.mutex ); //InitializeConditionVariable( &un.cond ); /* Register for an unlock-notify callback. */ rc = sqlite3_unlock_notify( db, unlock_notify_cb, (void *)&un ); //assert( rc==SQLITE_LOCKED || rc==SQLITE_OK ); /* The call to sqlite3_unlock_notify() always returns either SQLITE_LOCKED ** or SQLITE_OK. ** ** If SQLITE_LOCKED was returned, then the system is deadlocked. In this ** case this function needs to return SQLITE_LOCKED to the caller so ** that the current transaction can be rolled back. Otherwise, block ** until the unlock-notify callback is invoked, then return SQLITE_OK. */ if( rc==SQLITE_OK ) { EnterCriticalSection( &un.mutex ); if( !un.fired ) { //SleepConditionVariableCS( &un.cond, &un.mutex, INFINITE ); } LeaveCriticalSection( &un.mutex ); } /* Destroy the mutex and condition variables. */ //pthread_cond_destroy(&un.cond); DeleteCriticalSection( &un.mutex ); return rc; } #else /* ** A pointer to an instance of this structure is passed as the user-context ** pointer when registering for an unlock-notify callback. */ typedef struct UnlockNotification UnlockNotification; struct UnlockNotification { int fired; /* True after unlock event has occurred */ pthread_cond_t cond; /* Condition variable to wait on */ pthread_mutex_t mutex; /* Mutex to protect structure */ }; /* ** This function is an unlock-notify callback registered with SQLite. */ static void unlock_notify_cb(void **apArg, int nArg){ int i; for(i=0; imutex); p->fired = 1; pthread_cond_signal(&p->cond); pthread_mutex_unlock(&p->mutex); } } /* ** This function assumes that an SQLite API call (either sqlite3_prepare_v2() ** or sqlite3_step()) has just returned SQLITE_LOCKED. The argument is the ** associated database connection. ** ** This function calls sqlite3_unlock_notify() to register for an ** unlock-notify callback, then blocks until that callback is delivered ** and returns SQLITE_OK. The caller should then retry the failed operation. ** ** Or, if sqlite3_unlock_notify() indicates that to block would deadlock ** the system, then this function returns SQLITE_LOCKED immediately. In ** this case the caller should not retry the operation and should roll ** back the current transaction (if any). */ static int wait_for_unlock_notify(sqlite3 *db){ int rc; UnlockNotification un; /* Initialize the UnlockNotification structure. */ un.fired = 0; pthread_mutex_init(&un.mutex, 0); pthread_cond_init(&un.cond, 0); /* Register for an unlock-notify callback. */ rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un); assert( rc==SQLITE_LOCKED || rc==SQLITE_OK ); /* The call to sqlite3_unlock_notify() always returns either SQLITE_LOCKED ** or SQLITE_OK. ** ** If SQLITE_LOCKED was returned, then the system is deadlocked. In this ** case this function needs to return SQLITE_LOCKED to the caller so ** that the current transaction can be rolled back. Otherwise, block ** until the unlock-notify callback is invoked, then return SQLITE_OK. */ if( rc==SQLITE_OK ){ pthread_mutex_lock(&un.mutex); if( !un.fired ){ pthread_cond_wait(&un.cond, &un.mutex); } pthread_mutex_unlock(&un.mutex); } /* Destroy the mutex and condition variables. */ pthread_cond_destroy(&un.cond); pthread_mutex_destroy(&un.mutex); return rc; } #endif bool ITunesPlaysDatabase::query( /* utf-8 */ const char* statement, std::string* result ) { LOG( 3, statement ); int error = 0; sqlite3_stmt* stmt = 0; try { const char* tail; error = sqlite3_prepare( m_db, statement, static_cast( strlen( statement ) ), &stmt, &tail ); if ( error != SQLITE_OK ) throw "Could not compile SQL to byte-code"; int busyCount = 0; while( error != SQLITE_DONE && busyCount < 20 ) { error = sqlite3_step( stmt ); switch( error ) { case SQLITE_BUSY: busyCount++; sqlite3_sleep( 25 ); break; case SQLITE_MISUSE: throw "Could not execute SQL - SQLITE_MISUSE"; case SQLITE_ERROR: throw "Could not execute SQL - SQLITE_ERROR"; case SQLITE_ROW: if ( sqlite3_column_count( stmt ) > 0 && result ) { *result = (const char*) sqlite3_column_text( stmt, 0 ); LOG( 3, "SQLite result: `" << *result << '\'' ); } break; case SQLITE_DONE: break; case SQLITE_LOCKED: LOG( 3, "Database locked. Waiting for unlock." ); busyCount++; sqlite3_sleep( 25 ); //wait_for_unlock_notify( m_db ); break; default: throw "Unhandled sqlite3_step() return value"; } } if ( busyCount ) { if ( busyCount >= 20 ) throw "Could not execute SQL - SQLite was too busy"; LOG( 3, "SQLite was busy " << busyCount << " times" ); } } catch( const char* message ) { LOG( 2, "ERROR: " << message ); logError( m_db ); } sqlite3_finalize( stmt ); return error == SQLITE_DONE; } #include "common/c++/fileCreationTime.cpp" bool ITunesPlaysDatabase::isValid() { if (!query( "SELECT COUNT( * ) FROM " TABLE_NAME " LIMIT 0, 1;" )) return false; // uninstallation protection // we store the ctime in the database, so if the database is removed we // don't have this value still stored, which may cause additional issues // the principle is, if the plugin is uninstalled, then reinstalled sometime // the ctime will be different to what we stored at the time of bootstrap // the reason for all this is, if there was a period of uninstallation // in which the db wasn't removed (happens everytime on Windows), we don't // want to scrobble everything played since the uninstallation! std::string r; query( "SELECT value FROM metadata WHERE key='plugin_ctime';", &r ); long const stored_time = std::strtol( r.c_str(), 0, 10 /*base*/ ); time_t actual_time = common::fileCreationTime( Moose::pluginPath() ); LOG( 3, "Plugin binary ctime: " << actual_time ); if (errno == EINVAL || stored_time != actual_time) { LOG( 3, "Uninstallation protection triggered; forcing bootstrap" ); return false; } return true; } bool ITunesPlaysDatabase::needsBootstrap() { std::string out; bool b = query( "SELECT value FROM metadata WHERE key='bootstrap_complete'", &out ); if (!b || out != "true") return true; #ifndef WIN32 b = query( "SELECT value FROM metadata WHERE key='schema'", &out ); if (!b || out != "2") return true; #endif return false; } int ITunesPlaysDatabase::playCount( const ITunesTrack& track ) { #ifdef WIN32 std::string id = Moose::wStringToUtf8( track.persistentId() ); #else std::string id = track.persistentId(); #endif char* format = "SELECT play_count FROM " TABLE_NAME " WHERE persistent_id='%q'"; char* token = sqlite3_mprintf( format, id.c_str() ); std::string result; if ( !query( token, &result ) ) return -1; long c = std::strtol( result.c_str(), 0, 10 /*base*/ ); if ( errno == EINVAL ) return -1; return c; } int ITunesPlaysDatabase::playCountOld( const ITunesTrack& track ) { #ifdef WIN32 std::string id = Moose::wStringToUtf8( track.path() ); char* format = "SELECT play_count FROM itunes_db WHERE path='%q'"; #else std::string id = track.persistentId(); char* format = "SELECT play_count FROM itunes_db WHERE persistent_id='%q'"; #endif char* token = sqlite3_mprintf( format, id.c_str() ); std::string result; if ( !query( token, &result ) ) return -1; long c = std::strtol( result.c_str(), 0, 10 /*base*/ ); if ( errno == EINVAL ) return -1; return c; } ================================================ FILE: plugins/iTunes/ITunesPlaysDatabase.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef ITUNES_PLAYS_DATABASE_H #define ITUNES_PLAYS_DATABASE_H #ifdef WIN32 #include "ITunesTrack.h" #endif #include extern "C" { typedef struct sqlite3 sqlite3; } #define TABLE_NAME "playcounts" /** @author Christian Muehlhaeuser * @contributor Jono Cole * @contributor Max Howell * @contributor */ class ITunesPlaysDatabase { public: ITunesPlaysDatabase(); ~ITunesPlaysDatabase(); /** @returns false if the tables aren't valid or created */ bool isValid(); /** @returns true if the db has no valid bootstrap */ bool needsBootstrap(); /** tracks unknown to us return -1, which means you should verify the * playCount with iTunes before doing anything with that track generally */ int playCount( const class ITunesTrack& track ); /** tracks unknown to us return -1, which means you should verify the * playCount with iTunes before doing anything with that track generally */ int playCountOld( const class ITunesTrack& track ); /** call to sync the just finished playing track, NOTE the implementation * stores what is playing now for the next call to sync() so always call * this every new track */ static void sync(); #ifndef WIN32 /** general init, and if necessary, creates tables, bootstraps db, etc. */ static void init(); /** cleans up, call when plugin is unloaded */ static void finit(); #else bool sync( const ExtendedITunesTrack& ); bool syncOld( const ExtendedITunesTrack& ); #endif protected: /** prepares us for iPod scrobbling, ie creates the tables and bootstraps * the database */ void prepare(); bool query( /* utf-8 */ const char* statement, std::string* result = 0 ); sqlite3* m_db; #ifndef WIN32 static void* onPlayStateChanged( void* ); static void* sync( void* ); static void* syncOld( void* ); static pthread_mutex_t s_mutex; #endif }; #endif //ITUNES_PLAYS_DATABASE_H ================================================ FILE: plugins/iTunes/ITunesPlaysDatabase_mac.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "ITunesPlaysDatabase.h" #include "ITunesTrack.h" #include "ITunesExceptions.h" #include "common/c++/logger.h" #include #include pthread_mutex_t ITunesPlaysDatabase::s_mutex; void //static ITunesPlaysDatabase::init() { ITunesPlaysDatabase().prepare(); pthread_mutex_init( &s_mutex, NULL ); } void //static ITunesPlaysDatabase::finit() { pthread_mutex_destroy( &s_mutex ); } struct PThreadMutexLocker { PThreadMutexLocker( pthread_mutex_t& mutex ) : m_mutex( &mutex ) { pthread_mutex_lock( m_mutex ); } ~PThreadMutexLocker() { pthread_mutex_unlock( m_mutex ); } private: pthread_mutex_t* const m_mutex; }; void* //static ITunesPlaysDatabase::sync( void* that ) { ExtendedITunesTrack& track = * (ExtendedITunesTrack*) that; LOG( 3, "Syncing " << track.path() ); // On trackChange iTunes takes about 4 seconds to update the playCount for // the previous track :( // Thus we try to obtain a new playCount for up to 124 seconds (0+4+8+16+32+64) // We stop then because if the user skips a track, playCount will never // change. However we try for up to 60 as we have no idea how long it may // take for iTunes to update its db. for( int s = 4; s < 128; s += s ) { try { // do as infrequently as possible because it spawns applescript int const diff = track.playCountDifference(); if (diff) { ITunesPlaysDatabase db; // rationale for UPDATE: if the track is not in the db then it // is new since the last ipod sync. Thus it cannot be on the ipod // It will be automatically inserted with the current playcount // when the ipod is next synced and Twiddly is run. // NOTE db.playCount() will return -1 in this case so the logs // will be misleading, usually stating: play_count='0', but this // is fine since the UPDATE will fail in this case. char* format = "UPDATE " TABLE_NAME " SET play_count='%d' WHERE persistent_id='%q'"; char* token = sqlite3_mprintf( format, db.playCount( track ) + diff, track.persistentId().c_str() ); db.query( token ); sqlite3_free( token ); break; } } catch( PlayCountException& ) { LOG( 2, "Applescript failed to obtain playCount" ); // NOTE our options here are an additional scrobble or potentially // lose scrobbles, the latter is perhaps better, as statistically // the user is unlikely to have played the track in question on his // iPod, however the code to achieve that would make this function // far less maintainable } LOG( 3, "Playcount unchanged - sleeping " << s << " seconds..." ); sqlite3_sleep( s * 1000 ); } delete &track; pthread_exit( 0 ); } void* //static ITunesPlaysDatabase::syncOld( void* that ) { ExtendedITunesTrack& track = * (ExtendedITunesTrack*) that; LOG( 3, "Syncing " << track.path() ); // On trackChange iTunes takes about 4 seconds to update the playCount for // the previous track :( // Thus we try to obtain a new playCount for up to 124 seconds (0+4+8+16+32+64) // We stop then because if the user skips a track, playCount will never // change. However we try for up to 60 as we have no idea how long it may // take for iTunes to update its db. for( int s = 4; s < 128; s += s ) { try { // do as infrequently as possible because it spawns applescript int const diff = track.playCountDifference(); if (diff) { ITunesPlaysDatabase db; // rationale for UPDATE: if the track is not in the db then it // is new since the last ipod sync. Thus it cannot be on the ipod // It will be automatically inserted with the current playcount // when the ipod is next synced and Twiddly is run. // NOTE db.playCount() will return -1 in this case so the logs // will be misleading, usually stating: play_count='0', but this // is fine since the UPDATE will fail in this case. char* format = "UPDATE itunes_db SET play_count='%d' WHERE persistent_id='%q'"; char* token = sqlite3_mprintf( format, db.playCountOld( track ) + diff, track.persistentId().c_str() ); db.query( token ); sqlite3_free( token ); break; } } catch( PlayCountException& ) { LOG( 2, "Applescript failed to obtain playCount" ); // NOTE our options here are an additional scrobble or potentially // lose scrobbles, the latter is perhaps better, as statistically // the user is unlikely to have played the track in question on his // iPod, however the code to achieve that would make this function // far less maintainable } LOG( 3, "Playcount unchanged - sleeping " << s << " seconds..." ); sqlite3_sleep( s * 1000 ); } delete &track; pthread_exit( 0 ); } void* //static ITunesPlaysDatabase::onPlayStateChanged( void* ) { /** Behaviour: * Playback started: get current track, exit * Track changed: sync previous track, get current track, exit * Playback stopped (playlist ended): sync last track, clear s_track */ static ExtendedITunesTrack s_track; ExtendedITunesTrack currentTrack = ExtendedITunesTrack::currentTrack(); LOG( 3, "kind: " << s_track.kind() ); { PThreadMutexLocker locker( s_mutex ); if (!s_track.isNull()) { // s_track is the track that has just finished playing pthread_t thread; pthread_create( &thread, NULL, sync, new ExtendedITunesTrack( s_track ) ); pthread_t threadOld; pthread_create( &threadOld, NULL, syncOld, new ExtendedITunesTrack( s_track ) ); } else LOG( 3, "Track is null, won't sync" ); s_track = currentTrack; } pthread_exit( 0 ); } void //static ITunesPlaysDatabase::sync() { pthread_t thread; pthread_create( &thread, NULL, onPlayStateChanged, 0 ); } ================================================ FILE: plugins/iTunes/ITunesPlaysDatabase_win.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "ITunesPlaysDatabase.h" #include "Moose.h" #include "ITunesTrack.h" #include "common/c++/logger.h" #include "sqlite3.h" bool ITunesPlaysDatabase::sync( const ExtendedITunesTrack& track ) { if ( track.isNull() ) { // This means the track has no COM pointer or no path return false; } try { int const diff = track.playCountDifference(); LOGWL( 3, "Diff = " << diff << " for " << track.toString() ); int const twiddlyPlayCount = playCount( track ); // if the track is new and not yet in the database then twiddlyPlayCount // will be -1, therefore sync with the iTunes playCount for this track int const playCountToSync = (twiddlyPlayCount == -1) ? track.playCount() : twiddlyPlayCount + diff; if ( playCountToSync == twiddlyPlayCount ) { // Nothing to do. This isn't really an error, we just happened to sync // even though the playcount hadn't inced. return true; } const char* format = "REPLACE INTO " TABLE_NAME " ( persistent_id, play_count ) VALUES ( '%q', '%d' );"; char* token = sqlite3_mprintf( format, Moose::wStringToUtf8( track.persistentId() ).c_str(), playCountToSync ); LOGWL( 3, "Syncing local db for: " << track.artist() << " - " << track.track() ); bool success = query( token ); sqlite3_free( token ); return success; } catch ( ITunesException& ) { // This means COM failed to retrieve the playcount, abort // NOTE no logging because a higher up function logs already return false; } } bool ITunesPlaysDatabase::syncOld( const ExtendedITunesTrack& track ) { if ( track.isNull() ) { // This means the track has no COM pointer or no path return false; } try { int const diff = track.playCountDifference(); LOGWL( 3, "Diff = " << diff << " for " << track.toString() ); int const twiddlyPlayCount = playCountOld( track ); // if the track is new and not yet in the database then twiddlyPlayCount // will be -1, therefore sync with the iTunes playCount for this track int const playCountToSync = (twiddlyPlayCount == -1) ? track.playCount() : twiddlyPlayCount + diff; if ( playCountToSync == twiddlyPlayCount ) { // Nothing to do. This isn't really an error, we just happened to sync // even though the playcount hadn't inced. return true; } const char* format = "REPLACE INTO itunes_db ( persistent_id, path, play_count ) VALUES ( '%q', '%q', '%d' );"; char* token = sqlite3_mprintf( format, Moose::wStringToUtf8( track.persistentId() ).c_str(), Moose::wStringToUtf8( track.path() ).c_str(), playCountToSync ); LOGWL( 3, "Syncing old local db for: " << track.artist() << " - " << track.track() ); bool success = query( token ); sqlite3_free( token ); return success; } catch ( ITunesException& ) { // This means COM failed to retrieve the playcount, abort // NOTE no logging because a higher up function logs already return false; } } ================================================ FILE: plugins/iTunes/ITunesTrack.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "ITunesTrack.h" #include "common/c++/Logger.h" #include #include #ifdef WIN32 #include "ITunesComWrapper.h" #include // for COleDateTime #include #include #include #endif using namespace std; long stdStringToLong( const std::string& s ) throw( PlayCountException ) { long c = std::strtol( s.c_str(), 0, 10 /*base*/ ); if ( errno == EINVAL ) throw PlayCountException(); return c; } #ifdef WIN32 ITunesTrack::ITunesTrack() : m_comTrack( 0 ) {} ITunesTrack::ITunesTrack( IITTrack* track ) : m_comTrack( track ) { assert( track != 0 ); m_path = pathForTrack( track ); m_id = L"-1"; } ITunesTrack::ITunesTrack( const ITunesTrack& that ) { clone( that ); } ITunesTrack& ITunesTrack::operator=( const ITunesTrack& that ) { // Check for self-assignment if ( &that != this ) { clone( that ); } return *this; } void ITunesTrack::clone( const ITunesTrack& that ) { m_comTrack = that.m_comTrack; m_id = that.m_id; m_path = that.m_path; if ( m_comTrack != 0 ) m_comTrack->AddRef(); } ITunesTrack::~ITunesTrack() { if ( m_comTrack != 0 ) m_comTrack->Release(); } #endif // WIN32 #ifndef WIN32 static void splitTheseFiveCommaSeparatedParts( const std::string& in, std::string out[] ) { unsigned int first = 0; for (int x = 0; x < 4; ++x) { unsigned int const second = in.find( ',', first ); if (second == std::string::npos) throw PlayCountException(); out[x] = in.substr( first, second - first ); first = second + 2; } out[4] = in.substr( first ); } ExtendedITunesTrack ExtendedITunesTrack::currentTrack() { ExtendedITunesTrack t; try { //TODO may as well precompile 3 scripts and then run them with carbon // that should be most efficient computationally and easiest to read method // returns eg. "412EAE1061ABF7D2, 6152, 3, /music/file.mp3", "Audio CD Track" std::string const s = scriptResult( "currentTrack.scpt" ); if( s.length() == 0 ) throw PlayCountException(); std::string parts[5]; splitTheseFiveCommaSeparatedParts( s, parts ); t.m_id = parts[0]; t.m_dbid = parts[1]; t.m_path = parts[3]; t.m_kind = parts[4]; // may throw PlayCountException, if so initialPlayCount stays at -1 // this always happens when iTunes is stopped t.m_initialPlayCount = stdStringToLong( parts[2] ); } catch (PlayCountException&) { LOGL( 3, "Couldn't get current track data" ); } catch (ITunesException& e) { LOGL( 3, (std::string("Couldn't determine current track: ") + e.what()).c_str()); } return t; } #endif long ITunesTrack::playCount() const throw( PlayCountException ) { #ifdef WIN32 if ( m_comTrack == 0 ) return -1; // empty track case long cnt; HRESULT res = m_comTrack->get_PlayedCount( &cnt ); try { ITunesComWrapper::handleComResult( res, L"Failed to get play count for track" ); } catch( ITunesException& ) { throw PlayCountException(); } return cnt; #else std::string s = scriptResult( "playCountForDatabaseId.scpt", m_dbid ); //std::string s = scriptResult( "playCountForPersistentId.scpt", m_id ); return stdStringToLong( s ); //may throw #endif } #ifdef WIN32 wstring ITunesTrack::track() const { if ( m_comTrack == 0 ) return L""; // empty track case BSTR bstrName = 0; HRESULT res = m_comTrack->get_Name( &bstrName ); ITunesComWrapper::handleComResult( res, L"Failed to read title of track" ); return ITunesComWrapper::bstrToWString( bstrName ); } #endif #ifdef WIN32 wstring ITunesTrack::artist() const { if ( m_comTrack == 0 ) return L""; // empty track case BSTR bstrArtist = 0; HRESULT res = m_comTrack->get_Artist( &bstrArtist ); ITunesComWrapper::handleComResult( res, L"Failed to read artist of track" ); return ITunesComWrapper::bstrToWString( bstrArtist ); } #endif #ifdef WIN32 wstring ITunesTrack::albumArtist() const { if ( m_comTrack == 0 ) return L""; // empty track case BSTR bstrAlbumArtist = 0; IITFileOrCDTrack* fileTrack = static_cast(m_comTrack); HRESULT res = m_comTrack->QueryInterface(IID_IITFileOrCDTrack, (void**)&fileTrack); if ( res != S_OK || fileTrack == 0 ) { ITunesComWrapper::logComError( res, L"albumArtist Casting IITrack to IITFileOrCDTrack failed" ); } else { res = fileTrack->get_AlbumArtist( &bstrAlbumArtist ); ITunesComWrapper::logComError( res, L"Failed to read album artist of track" ); fileTrack->Release(); } return ITunesComWrapper::bstrToWString( bstrAlbumArtist ); } #endif #ifdef WIN32 wstring ITunesTrack::album() const { if ( m_comTrack == 0 ) return L""; // empty track case BSTR bstrAlbum = 0; HRESULT res = m_comTrack->get_Album( &bstrAlbum ); ITunesComWrapper::handleComResult( res, L"Failed to read album of track" ); return ITunesComWrapper::bstrToWString( bstrAlbum ); } #endif #ifdef WIN32 wstring ITunesTrack::lastPlayed() const { if ( m_comTrack == 0 ) return L""; // empty track case DATE date; // this is a double HRESULT res = m_comTrack->get_PlayedDate( &date ); ITunesComWrapper::handleComResult( res, L"Failed to read last played date of track" ); // We get a date of 0.00000 if the track has never been played if ( date < 0.00001 && date > -0.00001 ) return L""; COleDateTime oleDate( date ); if ( oleDate.GetStatus() == COleDateTime::valid ) { wostringstream os; os << setfill(L'0') << setw(4) << oleDate.GetYear() << "-" << setw(2) << oleDate.GetMonth() << "-" << setw(2) << oleDate.GetDay() << L" " << setw(2) << oleDate.GetHour() << ":" << setw(2) << oleDate.GetMinute() << ":" << setw(2) << oleDate.GetSecond(); return os.str(); } else { throw ITunesException( "Failed to read last played date of track" ); } } #endif #ifdef WIN32 long ITunesTrack::duration() const { if ( m_comTrack == 0 ) return -1; // empty track case long duration = 0; HRESULT res = m_comTrack->get_Duration( &duration ); ITunesComWrapper::handleComResult( res, L"Failed to read duration of track" ); return duration; } #endif #ifdef WIN32 bool ITunesTrack::isSameAs( const ITunesTrack& that ) { if ( m_comTrack == 0 || that.m_comTrack == 0 ) return false; long myDbId = 0; long theirDbId = 0; HRESULT res = m_comTrack->get_TrackDatabaseID( &myDbId ); ITunesComWrapper::handleComResult( res, L"Failed to read db ID of track" ); res = that.m_comTrack->get_TrackDatabaseID( &theirDbId ); ITunesComWrapper::handleComResult( res, L"Failed to read db ID of track" ); return myDbId == theirDbId; } #endif bool ITunesTrack::isNull() const { #ifdef WIN32 return m_comTrack == 0; #else return m_id == "" || m_kind == "Audio CD Track"; #endif } #ifndef WIN32 #include "Moose.h" std::string //static ITunesTrack::scriptResult( const char* filename, const std::string& argv1 ) throw() { LOG( 4, "Executing script: `" << filename << "' `" << argv1 << '\'' ); std::string command; command += "osascript '"; command += Moose::bundleFolder() + "Contents/Resources/"; command += filename; command += "'"; if ( argv1.size() ) { command += ' ' + argv1; } // avoid iTunes error -54 Moose::setFileDescriptorsCloseOnExec(); FILE* pipe = ::popen( command.c_str(), "r" ); if ( !pipe ) return "ERROR"; char buf[ 128 ]; std::string out; while( fgets( buf, 128, pipe ) ) { out += buf; } ::pclose( pipe ); if ( out.length() ) { out.resize( out.length() - 1 ); // last character is always a \n LOG( 4, "Script result: `" << out << '\'' ); } return out; } #endif #ifdef WIN32 wstring ITunesTrack::pathForTrack( IITTrack* track ) { wstring path; IITFileOrCDTrack* fileTrack = 0; HRESULT res = track->QueryInterface( IID_IITFileOrCDTrack, (void**)&fileTrack ); if ( res != S_OK || fileTrack == 0 ) { // Not ideal, but logging this makes the iPodScrobbler log // really messy for mostly iTunes Match track libraries //ITunesComWrapper::logComError( res, L"Casting IITrack to IITFileOrCDTrack failed" ); } else { BSTR bstrLocation = 0; // BSTR = WCHAR* res = fileTrack->get_Location( &bstrLocation ); if ( res == S_OK ) { path = ITunesComWrapper::bstrToWString( bstrLocation ); } else { BSTR a; BSTR n; fileTrack->get_Artist( &a ); fileTrack->get_Name( &n ); wstring artist = ITunesComWrapper::bstrToWString( a ); wstring track = ITunesComWrapper::bstrToWString( n ); wostringstream os; os << L"COM couldn't get file path for " << artist << L" - " << track; ITunesComWrapper::logComError( res, os.str() ); } fileTrack->Release(); } return path; } #endif #ifdef WIN32 bool ITunesTrack::podcast() { VARIANT_BOOL podcast = FALSE; IITFileOrCDTrack* fileTrack = 0; HRESULT res = m_comTrack->QueryInterface( IID_IITFileOrCDTrack, (void**)&fileTrack ); if ( res != S_OK || fileTrack == 0 ) { ITunesComWrapper::logComError( res, L"podcast Casting IITrack to IITFileOrCDTrack failed" ); } else { res = fileTrack->get_Podcast( &podcast ); if ( res != S_OK ) { BSTR a; BSTR n; fileTrack->get_Artist( &a ); fileTrack->get_Name( &n ); wstring artist = ITunesComWrapper::bstrToWString( a ); wstring track = ITunesComWrapper::bstrToWString( n ); wostringstream os; os << L"COM couldn't get podcast for " << artist << L" - " << track; ITunesComWrapper::logComError( res, os.str() ); } fileTrack->Release(); } return podcast != FALSE; } #endif #ifdef WIN32 bool ITunesTrack::video() { bool video = false; IITFileOrCDTrack* fileTrack = 0; HRESULT res = m_comTrack->QueryInterface( IID_IITFileOrCDTrack, (void**)&fileTrack ); if ( res != S_OK || fileTrack == 0 ) { ITunesComWrapper::logComError( res, L"video Casting IITrack to IITFileOrCDTrack failed" ); } else { ITVideoKind videoKind = ITVideoKindNone; res = fileTrack->get_VideoKind( &videoKind ); if ( res == S_OK ) { video = videoKind != ITVideoKindNone && videoKind != ITVideoKindMusicVideo; } else { BSTR a; BSTR n; fileTrack->get_Artist( &a ); fileTrack->get_Name( &n ); wstring artist = ITunesComWrapper::bstrToWString( a ); wstring track = ITunesComWrapper::bstrToWString( n ); wostringstream os; os << L"COM couldn't get video kind for " << artist << L" - " << track; ITunesComWrapper::logComError( res, os.str() ); } fileTrack->Release(); } return video; } #endif int ExtendedITunesTrack::playCountDifference() const throw( PlayCountException ) { assert( m_initialPlayCount != -2 ); // the default value for a null track int const playCount = this->playCount(); // COM or Applescript failed :( if ( m_initialPlayCount == -1 || playCount == -1 ) throw PlayCountException(); return playCount - m_initialPlayCount; } ================================================ FILE: plugins/iTunes/ITunesTrack.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef ITUNES_TRACK_H #define ITUNES_TRACK_H #include "ITunesExceptions.h" #ifdef WIN32 // Disable warning about exception specifications #pragma warning( disable : 4290 ) struct IITTrack; #endif #include /** @author Max Howell -- mac * @author Erik Jalevik -- windows * @brief Uses AppleScript or COM to retrieve properties of an iTunes track. * All functions that access COM directly will throw an ITunesException * if something went wrong. */ class ITunesTrack { public: #ifdef WIN32 ITunesTrack(); // creates a null track /** Creates a track from a COM track object. Ownership is passed to this * class and it will call Release on the object when done. Never throws. */ ITunesTrack( IITTrack* track ); // Copy ctor (needed because of owned COM pointer) ITunesTrack( const ITunesTrack& that ); ITunesTrack& operator=( const ITunesTrack& that ); ITunesTrack::~ITunesTrack(); /** Unicode */ std::wstring track() const; std::wstring artist() const; std::wstring albumArtist() const; std::wstring album() const; /** Date format: "YYYY-MM-DD HH:MM:SS", returns an empty string if track was never played */ std::wstring lastPlayed() const; /** In seconds */ long duration() const; /** Convenience function for logging etc */ std::wstring toString() const { return artist() + L" - " + track(); } /** Uses runtime ID to compare the two. */ bool isSameAs( const ITunesTrack& that ); /** These two fields are always populated when an iTunes track is created */ std::wstring persistentId() const { return m_id; } std::wstring path() const { return m_path; } void setPersistentId( std::wstring id ) { m_id = id; } /** podcasts have the option of not scrobbling and videos are never scrobbled */ bool podcast(); bool video(); #else /** These two fields are always populated when an iTunes track is created */ std::string persistentId() const { return m_id; } std::string path() const { return m_path; } std::string kind() const { return m_kind; } #endif /** The below fields are real-time */ long playCount() const throw( PlayCountException ); bool isNull() const; protected: #ifdef WIN32 std::wstring m_id; std::wstring m_path; std::wstring pathForTrack( IITTrack* track ); void clone( const ITunesTrack& that ); IITTrack* m_comTrack; #else std::string m_id; // utf8 std::string m_path; // utf8 std::string m_dbid; //database ID std::string m_kind; // "Audio CD Track" etc /** executes an applescript in the bundle and returns the output */ static std::string scriptResult( const char* filename, const std::string& argv1 = "" ) throw(); #endif }; /** @author Max Howell * @brief Specialised variety of ITunesTrack, used for playCount sync * operations, where we need the difference in playCount since the object was * initialised */ class ExtendedITunesTrack : public ITunesTrack { public: ExtendedITunesTrack() : m_initialPlayCount( -2 ) {} #ifdef WIN32 static ExtendedITunesTrack from( const ITunesTrack& that ) { ExtendedITunesTrack t; t.ITunesTrack::operator=( that ); t.m_initialPlayCount = that.playCount(); return t; } #else /** returns the track currently playing in iTunes */ static ExtendedITunesTrack currentTrack(); #endif int playCountDifference() const throw( PlayCountException ); int initialPlayCount() const { return m_initialPlayCount; } void setInitialPlaycount( int pc ) { m_initialPlayCount = pc; } private: int m_initialPlayCount; }; #endif // ITUNESTRACK_H ================================================ FILE: plugins/iTunes/Moose.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef MOOSE_H #define MOOSE_H #include #include "common/c++/string.h" #include "common/c++/logger.h" #ifdef WIN32 #define UNICORN_HKEY L"Software\\Last.fm\\" #define MOOSE_HKEY_A "Software\\Last.fm\\Last.fm" #define MOOSE_HKEY L"Software\\Last.fm\\Last.fm" #define MOOSE_PLUGIN_HKEY_A "Software\\Last.fm\\Client\\Plugins\\itw" #else #define MOOSE_PREFS_PLIST "fm.last.scrobbler" #include #include #include #include #include #endif namespace Moose { /** @returns /-terminated utf8 encoded path */ COMMON_STD_STRING applicationFolder(); /** @returns /-terminated utf8 encoded path */ COMMON_STD_STRING applicationSupport(); /** @returns utf8 encoded Last.fm client application binary path */ COMMON_STD_STRING applicationPath(); /** @returns utf8 encoded path */ COMMON_STD_STRING twiddlyPath(); /** @returns the complete path to the plugin library binary * 16-bit unicode on Windows and local 8-bit on Mac */ COMMON_STD_STRING pluginPath(); bool isTwiddlyRunning(); /** on mac, calls setFileDescriptorsCloseOnExec() for you * on mac all parameters should be utf8 */ bool exec( const COMMON_STD_STRING& command, const COMMON_STD_STRING& space_separated_args ); bool iPodScrobblingEnabled(); #if !defined WIN32 /** returns true if the Moose setting for launch with media player is set */ bool launchWithMediaPlayer(); /** call when you exec something, otherwise you'll get error -54 */ void setFileDescriptorsCloseOnExec(); /** /-terminated */ std::string bundleFolder(); /** use launch services to start / send a message to the scrobbler */ void launchAudioscrobbler( const std::vector& arg ); #endif #ifdef WIN32 /** I'm not sure what this does really --mxcl */ std::wstring fixStr( const std::wstring& ); /** Converts our nice unicode to utf-8 using MS' horrific api call :) */ std::string wStringToUtf8( const std::wstring& ); #endif } #ifndef WIN32 inline std::string Moose::twiddlyPath() { FSRef appRef; LSFindApplicationForInfo( kLSUnknownCreator, CFSTR( "fm.last.Scrobbler" ), NULL, &appRef, NULL ); char path[PATH_MAX]; FSRefMakePath( &appRef, (unsigned char*)path, PATH_MAX ); std::string ret( path ); ret.append( "/Contents/Helpers/iPodScrobbler" ); return ret; } #else inline std::wstring Moose::twiddlyPath() { return applicationFolder() + L"iPodScrobbler.exe"; } #endif #endif //MOOSE_H ================================================ FILE: plugins/iTunes/Moose_mac.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #define AUDIOSCROBBLER_BUNDLEID "fm.last.Scrobbler" #include "Moose.h" #include "common/c++/Logger.h" #include #include #include #include std::string Moose::applicationSupport() { std::string path = std::getenv( "HOME" ); path += "/Library/Application Support/Last.fm/"; return path; } static std::string CFStringToStdString( CFStringRef s ) { std::string r; if (s == NULL) return r; CFIndex n; n = CFStringGetLength( s ); n = CFStringGetMaximumSizeForEncoding( n, kCFStringEncodingUTF8 ); char* buffer = new char[n]; CFStringGetCString( s, buffer, n, kCFStringEncodingUTF8 ); r = buffer; delete[] buffer; return r; } std::string Moose::applicationPath() { FSRef appRef; LSFindApplicationForInfo( kLSUnknownCreator, CFSTR( AUDIOSCROBBLER_BUNDLEID ), NULL, &appRef, NULL ); char path[PATH_MAX]; FSRefMakePath( &appRef, (unsigned char*)path, PATH_MAX ); if ( path == NULL ) return "/Applications/Last.fm Scrobbler.app/Contents/MacOS/Last.fm Scrobbler"; std::string s = path; s.append( "/Contents/MacOS/Last.fm Scrobbler" ); return s; } bool Moose::iPodScrobblingEnabled() { Boolean key_exists; bool b = CFPreferencesGetAppBooleanValue( CFSTR( "iPodScrobblingEnabled"), CFSTR( MOOSE_PREFS_PLIST ), &key_exists ); if (!key_exists) return true; return b; } std::string Moose::applicationFolder() { std::string s = applicationPath(); return s.substr( 0, s.rfind( '/' ) + 1 ); } bool Moose::launchWithMediaPlayer() { return true; CFBooleanRef v = (CFBooleanRef) CFPreferencesCopyAppValue( CFSTR( "LaunchWithMediaPlayer" ), CFSTR( MOOSE_PREFS_PLIST ) ); if (v) { bool b = CFBooleanGetValue( v ); CFRelease( v ); return b; } else return true; } void Moose::setFileDescriptorsCloseOnExec() { int n = 0; int fd = sysconf( _SC_OPEN_MAX ); while (--fd > 2) { int flags = fcntl( fd, F_GETFD, 0 ); if ((flags != -1) && !(flags & FD_CLOEXEC)) { n++; flags |= FD_CLOEXEC; fcntl( fd, F_SETFD, flags ); } } if (n) LOG( 3, "Set " << n << " file descriptors FD_CLOEXEC" ); } void Moose::launchAudioscrobbler( const std::vector& vargs ) { FSRef appRef; LSFindApplicationForInfo( kLSUnknownCreator, CFSTR( AUDIOSCROBBLER_BUNDLEID ), NULL, &appRef, NULL ); const void* arg[vargs.size()]; int index(0); AEDescList argAEList; AECreateList( NULL, 0, FALSE, &argAEList ); for( std::vector::const_iterator i = vargs.begin(); i != vargs.end(); i++ ) { arg[index++] = CFStringCreateWithCString( NULL, i->c_str(), kCFStringEncodingUTF8 ); AEPutPtr( &argAEList, 0, typeChar, i->c_str(), i->length()); } LSApplicationParameters params; params.version = 0; params.flags = kLSLaunchAndHide | kLSLaunchDontSwitch | kLSLaunchAsync;; params.application = &appRef; params.asyncLaunchRefCon = NULL; params.environment = NULL; CFArrayRef args = CFArrayCreate( NULL, ((const void**)arg), vargs.size(), NULL); params.argv = args; AEAddressDesc target; AECreateDesc( typeApplicationBundleID, CFSTR( AUDIOSCROBBLER_BUNDLEID ), 16, &target); AppleEvent event; AECreateAppleEvent ( kCoreEventClass, kAEReopenApplication , &target, kAutoGenerateReturnID, kAnyTransactionID, &event ); AEPutParamDesc( &event, keyAEPropData, &argAEList ); params.initialEvent = &event; LSOpenApplication( ¶ms, NULL ); AEDisposeDesc( &argAEList ); AEDisposeDesc( &target ); } bool Moose::exec( const std::string& command, const std::string& args ) { // fixes error -54 bug, where endless dialogs are spawned // presumably because the child Twiddly process inherits some key // file descriptors from iTunes setFileDescriptorsCloseOnExec(); std::string s = "\"" + command + "\" " + args + " &"; LOG( 3, "Launching `" << s << "'" ) return ( std::system( s.c_str() ) >= 0 ); } #include "common/c++/mac/getBsdProcessList.c" bool Moose::isTwiddlyRunning() { bool found = false; kinfo_proc* processList = NULL; size_t processCount = 0; if ( getBsdProcessList( &processList, &processCount ) ) { LOG( 3, "Failed to get the process list" ); return false; } uint const uid = ::getuid(); for ( size_t processIndex = 0; processIndex < processCount; processIndex++ ) { if ( processList[processIndex].kp_eproc.e_pcred.p_ruid == uid ) { if ( strcmp( processList[processIndex].kp_proc.p_comm, "twiddly" ) == 0 ) { found = true; break; } } } free( processList ); if ( found ) LOG( 3, "Twiddly already running!" ); return found; } std::string Moose::bundleFolder() { std::string path; path += ::getenv( "HOME" ); path += "/Library/iTunes/iTunes Plug-ins/AudioScrobbler.bundle/"; return path; } std::string Moose::pluginPath() { return bundleFolder() + "Contents/MacOS/AudioScrobbler"; } ================================================ FILE: plugins/iTunes/Moose_win.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "Moose.h" #include #include #include #include "common/c++/Logger.h" #include "RegistryUtils.h" // part of ScrobSub using namespace::std; std::wstring Moose::applicationSupport() { // This might not work on Win98 and earlier but we're not officially // supporting them anyway. Upgrading to IE5 will solve the problem. wchar_t acPath[MAX_PATH]; HRESULT h = SHGetFolderPathW( NULL, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, acPath ); if ( h != S_OK ) wcscpy( acPath, L"C:" ); return std::wstring( acPath ) + L"\\Last.fm\\Client"; } /** I'm not sure what this does really --mxcl */ std::wstring Moose::fixStr(const std::wstring& str) { std::wstring ret; if (str.length() == 0) return str; // Mac strings store the length in the first char of the string: size_t len = (size_t)str[0]; ret = str.substr(1); if(len > 0 && len < ret.length()) { ret = ret.substr(0,len); } return ret; } /** Converts our nice unicode to utf-8 using MS' horrific api call :) */ std::string Moose::wStringToUtf8( const std::wstring& wideStr ) { // first call works out required buffer length int recLen = WideCharToMultiByte(CP_UTF8,0,wideStr.c_str(),(int)wideStr.length(),NULL,NULL,NULL,NULL); char* buffer = new char[recLen + 1]; memset(buffer,0,recLen+1); // second call actually converts WideCharToMultiByte(CP_UTF8,0,wideStr.c_str(),(int)wideStr.length(),buffer,recLen,NULL,NULL); std::string ret = buffer; // fuck <-- this comment for the sake of GoogleCode profanity searches delete[] buffer; return ret; } std::wstring Moose::applicationPath() { HKEY h; LONG lResult = RegOpenKeyExA( HKEY_CURRENT_USER, MOOSE_HKEY_A, 0, // reserved KEY_READ, // access mask &h ); wchar_t buffer[MAX_PATH]; buffer[0] = L'\0'; if ( lResult == ERROR_SUCCESS ) { try { RegistryUtils::QueryString( h, L"Path", buffer, MAX_PATH, false ); } catch ( const RegistryUtils::CRegistryException& ) { LOGL( 2, "Client path not found in HKCU" ); } RegCloseKey( h ); } if ( buffer[0] == L'\0' ) { // Couldn't read path from HKCU, try HKLM lResult = RegOpenKeyExA( HKEY_LOCAL_MACHINE, MOOSE_HKEY_A, 0, // reserved KEY_READ, // access mask &h); if ( lResult == ERROR_SUCCESS ) { try { RegistryUtils::QueryString( h, L"Path", buffer, MAX_PATH, false ); } catch ( const RegistryUtils::CRegistryException& ) { LOGL( 2, "Client path not found in HKLM" ); } RegCloseKey( h ); } } if ( buffer[0] == L'\0' ) { LOGL( 1, "Couldn't read the client path from the registry."); return std::wstring(); } return buffer; } std::wstring Moose::applicationFolder() { std::wstring result = Moose::applicationPath(); size_t pos = result.rfind( '\\' ); if ( pos == wstring::npos ) pos = result.rfind( '/' ); if ( pos == wstring::npos ) { LOGL( 2, "Path to exe invalid" ); return std::wstring(); } return result.substr( 0, pos + 1 ); } bool Moose::exec( const std::wstring& command, const std::wstring& args ) { LOGWL( 3, "Launching `" << command << ' ' << args << '\'' ); HINSTANCE h = ShellExecuteW( NULL, L"open", command.c_str(), args.c_str(), NULL, SW_SHOWNORMAL ); if ( h <= reinterpret_cast( 32 ) ) { LOGWL( 3, "Failed launching `" << command << "'\n. ShellExecute error: " << h ); return false; } return true; } bool Moose::isTwiddlyRunning() { bool found = false; HANDLE mutex = ::CreateMutexA( NULL, false, "Twiddly-05F67299-64CC-4775-A10B-0FBF41B6C4D0" ); DWORD const e = ::GetLastError(); if( e == ERROR_ALREADY_EXISTS || e == ERROR_ACCESS_DENIED ) { //Can't create the mutex so twiddly must be running found = true; } //close the handle so that twiddly can create the mutex BOOL success = ::CloseHandle( mutex ); LOGL( 3, "Twiddly mutex closed: " << (success ? "true" : "false") ); if ( found ) LOGL( 3, "Twiddly already running!" ); return found; } std::wstring Moose::pluginPath() { std::wstring path = L"C:\\Program Files\\iTunes\\Plug-Ins\\itw_scrobbler.dll"; HKEY h = NULL; try { LONG l = RegOpenKeyExA( HKEY_LOCAL_MACHINE, MOOSE_PLUGIN_HKEY_A, 0, // reserved KEY_READ, // access mask &h ); if (l == ERROR_SUCCESS) { wchar_t buffer[MAX_PATH]; RegistryUtils::QueryString( h, L"Path", buffer, MAX_PATH, false, path.c_str() /*default*/ ); path = buffer; } } catch (RegistryUtils::CRegistryException&) {} RegCloseKey( h ); return path; } bool Moose::iPodScrobblingEnabled() { bool b = true; HKEY h = NULL; try { LONG l = RegOpenKeyExA( HKEY_CURRENT_USER, MOOSE_HKEY_A, 0, // reserved KEY_READ, // access mask &h ); if (l == ERROR_SUCCESS) { // Qt stores booleans as strings! :( wchar_t buffer[12]; RegistryUtils::QueryString( h, L"iPodScrobblingEnabled", buffer, 12, false, L"true" /*default*/ ); b = wcscmp( buffer, L"true" ) == 0; } } catch (RegistryUtils::CRegistryException&) {} RegCloseKey( h ); return b; } ================================================ FILE: plugins/iTunes/Plist.cpp ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #include "Plist.h" #include #include static const std::string base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; void Plist::read( std::istream &in) { //std::string buffer; std::stringbuf buffer; char tmp; while( in.good() ) { in >> tmp; if( tmp == '<' && ( in.peek() == '?' || in.peek() == '!' ) ) { in.get( buffer, '>' ); in.get(); continue; } else in.putback( tmp ); m_root = Element( in ); } } void Plist::write( std::ostream& out ) const { out << m_root; } Element::~Element() { if( m_type == DATA ) delete[] m_dataPtr; } Element& Element::operator=( const Element& element ) { m_type = element.m_type; m_string = element.m_string; m_dataLength = element.m_dataLength; m_dict = element.m_dict; m_string = element.m_string; m_array = element.m_array; m_indent = element.m_indent; if( element.m_type == DATA ) { m_dataPtr = new char[ m_dataLength ]; memcpy( m_dataPtr, element.m_dataPtr, m_dataLength ); } return *this; } std::string //static Element::trim( const std::string& str ) { std::string retVal; std::string::const_iterator iter; for( iter = str.begin(); iter != str.end(); iter++ ) { if( *iter != ' ' && *iter != '\n' && *iter != '\t' ) retVal+= *iter; } return retVal; } void Element::read(std::istream &in) { std::string elementName = readElementName( in ); if( elementName.find( "" ) == std::string::npos ) { if( curElement.find( "" ) == std::string::npos ) throw std::string( "Parsing error: Missed match key tags." ); m_dict[ key ] = Element( in ); m_dict[ key ].setIndent( m_indent + 1 ); curElement = readElementName( in ); } return; } //remove closing tag from the stream //TODO: check for mismatched elements? readElementName( in ); } std::string Element::readElementName( std::istream& in ) const { std::stringbuf buffer; in.get( buffer, '>' ); std::string elementName = trim( buffer.str() ); elementName += in.get(); return elementName; } std::string Element::readElementContents( std::istream& in ) const { std::stringbuf buffer; in.get( buffer, '<' ); return buffer.str(); } void Element::write(std::ostream &out) const { switch( m_type ) { case PLIST: case ARRAY: { std::vector::const_iterator iter; for( iter = m_array.begin(); iter != m_array.end(); iter++ ) { out << *iter << std::endl; } } break; case DICT: { std::map< std::string, Element >::const_iterator iter; for( iter = m_dict.begin(); iter != m_dict.end(); iter++ ) { out << std::endl; for( int i = 0; i < m_indent; i++ ) out << '\t'; out << iter->first << " = " << iter->second << std::endl; } } break; case STRING: case DATE: case DATA: out << m_string; break; } } void Element::base64decode( std::string &in, char *out ) { if( in.length() > 0 && in.length() % 4 ) { throw std::string( "Error cannot convert from base64 - wrong character length" ); } std::string::iterator iter; char n[4]; unsigned int outIndex = 0; for( iter = in.begin(); iter != in.end(); ) { for( int i = 0; i < 4; i++ ) { n[i] = *(iter++); // the '=' character is used as padding in base64 if( n[i] == '=' ) { n[i] = 0; continue; } //get the index of the character n[i] = base64chars.find( n[i] ); } //redistribute the 4 x 6byte values into 3 x 8byte values //and insert into output buffer. out[outIndex++] = ( n[0] << 2) + ((n[1] & 0x30) >> 4); out[outIndex++] = ((n[1] & 0xf) << 4) + ((n[2] & 0x3c) >> 2); out[outIndex++] = ((n[2] & 0x3) << 6) + n[3]; } } Element& Element::operator[]( const std::string& key ) { if( m_type != DICT ) throw std::string( "Could not access key from non dictionary node." ); return m_dict[ key ]; } Element& Element::operator[]( int index ) { if( m_type != ARRAY && m_type != PLIST ) { throw std::string( "Can only access elements by index with Array or Plist elements." ); } return m_array[ index ]; } const char * const Element::getData() const { if( m_type != DATA ) { throw std::string( "Can only access data from DATA elements" ); } return m_dataPtr; } ================================================ FILE: plugins/iTunes/Plist.h ================================================ /* Copyright 2005-2009 Last.fm Ltd. - Primarily authored by Max Howell, Jono Cole, Erik Jaelevik, Christian Muehlhaeuser This file is part of the Last.fm Desktop Application Suite. lastfm-desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. lastfm-desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with lastfm-desktop. If not, see . */ #ifndef PLIST_H #define PLIST_H #include #include #include /** @author Jono Cole * @brief Represents a single plist/array/dict/data/string/date element * and can read / write from a stream */ class Element { public: Element():m_indent(0){} /** Construct an element by reading from an istream */ explicit Element( std::istream& in ):m_indent(0){ read( in ); } ~Element(); /** Retrieve an indexed element from an array or plist type element */ Element& operator[]( int index ); /** Retrieve an element from a dictionary based on a key */ Element& operator[]( const std::string& key ); /** set the indent size (used for pretty formatting when debugging!) */ void setIndent( int indent ){ m_indent = indent; } /** get data length **/ const int getDataLength() const{ return m_dataLength; } /** get a pointer the data stored in a data element */ const char * const getData() const; /** Populate an Element from data in an istream */ void read( std::istream& in ); /** Write a prettified representation of the element to an ostream */ void write( std::ostream& out ) const; /** Copy constructor calls operator= */ Element( const Element& element ){ *this = element; } /** Overloaded operator= to make sure that data is copied if * the element is a data element */ Element& operator=( const Element& element ); private: enum{ STRING = 0, DATA, DATE, ARRAY, DICT, PLIST} m_type; std::string m_string; std::vector m_array; std::map< std::string, Element > m_dict; unsigned int m_dataLength; char* m_dataPtr; int m_indent; /** read the next element name from the stream * (ie , , etc.) * NOTE calls trim on the elementName * TODO name with trim in it */ std::string readElementName( std::istream& in ) const; /** read the conents of the element ie the data between * and elements */ std::string readElementContents( std::istream& in ) const; /** removes all whitespace, including internal whitespace */ static std::string trim( const std::string& str ); /** decode the base64 encoded in string into the out buffer */ void base64decode( std::string& in, char* out ); }; inline std::istream& operator>>( std::istream& in, Element& element ){ element.read( in ); return in; } inline std::ostream& operator<<( std::ostream& out, const Element& element ){ element.write( out ); return out; } /** @author Jono Cole * @brief Represents a single plist file and can read / write from a stream * using m_root as the root node. */ class Plist { public: Plist(){} Plist( std::istream& in ){ read( in ); } ~Plist(void){}; void read( std::istream& in ); void write( std::ostream& out ) const; template Element& operator[]( T index ){ return m_root[ index ]; } private: Element m_root; }; inline std::istream& operator>>( std::istream& in, Plist& plist ){ plist.read( in ); return in; } inline std::ostream& operator<<( std::ostream& out, const Plist& plist ){ plist.write( out ); return out; } #endif // PLIST_H ================================================ FILE: plugins/iTunes/README.dist ================================================ Edit the version in: main.h iScrobbleWin.rc itunes_install.iss iTunes Scrobbler.plist Note there are two version numbers in the iss file. Build the release build in Visual Studio. Then open the iss and compile to get the installer exe. ================================================ FILE: plugins/iTunes/_iTunes.iss ================================================ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! [CustomMessages] Version=6.0.5.4 [Setup] OutputBaseFilename=iTunesPluginWinSetup_6.0.5.4 ; setup.exe version VersionInfoVersion=6.0.5.4 VersionInfoTextVersion=6.0.5.4 AppName=Last.fm iTunes Plugin AppVerName=Last.fm iTunes Plugin {cm:Version} VersionInfoDescription=Last.fm iTunes Plugin Installer AppPublisher=Last.fm AppPublisherURL=http://www.last.fm AppSupportURL=http://www.last.fm AppUpdatesURL=http://www.last.fm AppCopyright=Copyright Ltd (c) DefaultDirName="{pf}\iTunes\Plug-Ins" UsePreviousAppDir=yes UninstallFilesDir={commonappdata}\Last.fm\Client\UninstITW OutputDir=. Compression=lzma SolidCompression=yes DirExistsWarning=no DisableReadyPage=yes ; Keep this the same across versions, even if they're incompatible. That will ensure ; uninstallation works fine after many upgrades. Can't use GUID as it'll break backward ; compatibility. AppId=Audioscrobbler iTunes Plugin CreateUninstallRegKey=no [Dirs] Name: "{localappdata}\Last.fm\Client" [Registry] ; The name of the final subkey here must match the one in plugins.data Root: HKLM; Subkey: "Software\Last.fm\Client\Plugins\itw"; ValueType: string; ValueName: "Version"; ValueData: "{cm:Version}"; Flags: uninsdeletekey Root: HKLM; Subkey: "Software\Last.fm\Client\Plugins\itw"; ValueType: string; ValueName: "Name"; ValueData: "iTunes"; Flags: uninsdeletekey Root: HKLM; Subkey: "Software\Last.fm\Client\Plugins\itw"; ValueType: string; ValueName: "Path"; ValueData: "{app}\itw_scrobbler.dll"; Flags: uninsdeletekey [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [InstallDelete] ; This is the name of the old old plugin Type: files; Name: "{app}\iScrobbleWin.dll" ; This is the name of the old ASS plugin Type: files; Name: "{app}\audioscrobbler.dll" [UninstallDelete] ; For legacy reasons Type: files; Name: "{app}\audioscrobbler.log" ; Try and delete the localappdata log for the case where the user running the uninstaller is the same as the plugin user Type: files; Name: "{localappdata}\Last.fm\Client\iTunesPlugin.log" [Files] Source: "Release\itw_scrobbler.dll"; DestDir: "{app}"; Flags: ignoreversion [Run] [Code] procedure CurStepChanged(CurStep: TSetupStep); var batfile: String; batcontent: String; uninstaller: String; alreadyAdded: Boolean; cmdToAdd: String; begin if (CurStep = ssPostInstall) then begin //MsgBox('postinstall', mbInformation, MB_OK); batfile := ExpandConstant('{commonappdata}\Last.fm\Client\uninst2.bat'); LoadStringFromFile(batfile, batcontent); //MsgBox('loaded string: ' + batcontent, mbInformation, MB_OK); uninstaller := ExpandConstant('{uninstallexe}'); //MsgBox('uninstaller pre-OEM: ' + uninstaller, mbInformation, MB_OK); alreadyAdded := (Pos(uninstaller, batcontent) <> 0); if (alreadyAdded = False) then begin cmdToAdd := uninstaller + #13#10; //MsgBox('not present, will add: ' + cmdToAdd, mbInformation, MB_OK); SaveStringToFile(batfile, cmdToAdd, True) end; end; end; ================================================ FILE: plugins/iTunes/_iTunes.plist ================================================ CFBundleDevelopmentRegion English CFBundleExecutable AudioScrobbler CFBundleGetInfoString The official Last.fm iTunes AudioScrobbler plugin CFBundleIdentifier fm.last.iTunes Scrobbler CFBundleInfoDictionaryVersion 6.0 CFBundleName AudioScrobbler CFBundleDisplayName Last.fm iTunes AudioScrobbler CFBundlePackageType hvpl CFBundleSignature hook CFBundleVersion 6.0.5.4 CFBundleShortVersionString 6.0.5 ================================================ FILE: plugins/iTunes/_iTunes.rc ================================================ // Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // English (U.K.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 6,0,5,4 PRODUCTVERSION 6,0,5,4 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080904b0" BEGIN VALUE "Comments", "http://www.last.fm" VALUE "CompanyName", "Last.fm" VALUE "FileDescription", "Last.fm iTunes plugin" VALUE "FileVersion", "6, 0, 5, 4" VALUE "InternalName", "itw_scrobbler" VALUE "LegalCopyright", "Copyright (C) 2008" VALUE "OriginalFilename", "itw_scrobbler.dll" VALUE "ProductName", "Last.fm iTunes plugin" VALUE "ProductVersion", "6, 0, 5, 4" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x809, 1200 END END #endif // English (U.K.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: plugins/iTunes/_iTunes.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 4C3C565816849BEC002A3DBC /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C3C565616849BEB002A3DBC /* sqlite3.c */; }; 4C7B8BBE141657F600C2A26C /* iTunesAPI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C7B8BBD141657F600C2A26C /* iTunesAPI.cpp */; }; 4CCE0E96162725220090F29C /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CCE0E95162725220090F29C /* Cocoa.framework */; }; 63233BAF0D7491470009EC65 /* Logger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 63233BAD0D7491470009EC65 /* Logger.cpp */; }; 632E70550DD9FF0200A16D88 /* playCountForDatabaseId.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 632E70520DD9FEF900A16D88 /* playCountForDatabaseId.scpt */; }; 633C62970D6CA1DD00B609B9 /* main_mac.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 633C62960D6CA1DD00B609B9 /* main_mac.cpp */; }; 634E6EF50D3FBA7D00E16E0D /* currentTrackLocation.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 638053560D3E95A10003DB13 /* currentTrackLocation.scpt */; }; 634E6EF60D3FBA7D00E16E0D /* currentTrackPersistentId.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 638053570D3E95A10003DB13 /* currentTrackPersistentId.scpt */; }; 634E6EF70D3FBA7D00E16E0D /* playCountForPersistentId.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 638053580D3E95A10003DB13 /* playCountForPersistentId.scpt */; }; 637452E10D58B02B00429514 /* Moose_mac.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 637452E00D58B02B00429514 /* Moose_mac.cpp */; }; 637A231E0DAA9B1C004590E1 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 637A231D0DAA9B1C004590E1 /* uninstall.sh */; }; 638052470D3E53940003DB13 /* ChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 638052460D3E53940003DB13 /* ChangeLog.txt */; }; 6392AF640D788A2D00E1B4F6 /* ITunesPlaysDatabase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6392AF620D788A2D00E1B4F6 /* ITunesPlaysDatabase.cpp */; }; 63B2F45E0E9FBECD0088AF7A /* ITunesTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 63B2F45D0E9FBECD0088AF7A /* ITunesTrack.cpp */; }; 63E5E4770D6B51C500774857 /* IPodDetector_mac.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 63E5E4760D6B51C500774857 /* IPodDetector_mac.cpp */; }; 63ED46C20DD8D52900F6FBE0 /* currentTrack.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 63ED46BF0DD8D51D00F6FBE0 /* currentTrack.scpt */; }; DC26679C0BD9410900B4ED68 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 01285C0700CC38597F000001 /* main.cpp */; }; DC26679F0BD9410900B4ED68 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */; }; FB0C1E670D9919CD004820B9 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB0C1E660D9919CD004820B9 /* CoreServices.framework */; }; FB0C20D00D9922CD004820B9 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB0C20CF0D9922CD004820B9 /* Carbon.framework */; }; FB3EA6B90D5795BE003DC14E /* ITunesPlaysDatabase_mac.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FB3EA6B70D5795BE003DC14E /* ITunesPlaysDatabase_mac.cpp */; }; FB4ED6F30D6B00CE00F243B8 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB4ED6F20D6B00CE00F243B8 /* IOKit.framework */; }; FB6471A70D80643A007006E4 /* IPod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FB6471A40D806439007006E4 /* IPod.cpp */; }; FB6471A90D80643A007006E4 /* IPod_mac.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FB6471A60D80643A007006E4 /* IPod_mac.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 01285C0200CC31B17F000001 /* iTunesAPI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = iTunesAPI.h; sourceTree = ""; }; 01285C0300CC31B17F000001 /* iTunesVisualAPI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = iTunesVisualAPI.h; sourceTree = ""; }; 01285C0700CC38597F000001 /* main.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 30; path = main.cpp; sourceTree = ""; }; 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = ""; }; 4C3C565616849BEB002A3DBC /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sqlite3.c; path = libs/sqlite3.c; sourceTree = ""; }; 4C3C565716849BEB002A3DBC /* sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sqlite3.h; path = libs/sqlite3.h; sourceTree = ""; }; 4C7B8BBD141657F600C2A26C /* iTunesAPI.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = iTunesAPI.cpp; sourceTree = ""; }; 4CCE0E95162725220090F29C /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/System/Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; 63233BAD0D7491470009EC65 /* Logger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Logger.cpp; path = "../../common/c++/Logger.cpp"; sourceTree = ""; }; 63233BAE0D7491470009EC65 /* Logger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logger.h; path = "../../common/c++/Logger.h"; sourceTree = ""; }; 632E70520DD9FEF900A16D88 /* playCountForDatabaseId.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.scpt; name = playCountForDatabaseId.scpt; path = scripts/playCountForDatabaseId.scpt; sourceTree = ""; }; 633C62960D6CA1DD00B609B9 /* main_mac.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = main_mac.cpp; sourceTree = ""; }; 637452E00D58B02B00429514 /* Moose_mac.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = Moose_mac.cpp; sourceTree = ""; }; 637A1ED30D4FCA15004FA19F /* Moose.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = Moose.h; sourceTree = ""; }; 637A231D0DAA9B1C004590E1 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = uninstall.sh; path = scripts/uninstall.sh; sourceTree = ""; }; 638052460D3E53940003DB13 /* ChangeLog.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ChangeLog.txt; sourceTree = ""; }; 638053560D3E95A10003DB13 /* currentTrackLocation.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.scpt; name = currentTrackLocation.scpt; path = scripts/currentTrackLocation.scpt; sourceTree = ""; }; 638053570D3E95A10003DB13 /* currentTrackPersistentId.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.scpt; name = currentTrackPersistentId.scpt; path = scripts/currentTrackPersistentId.scpt; sourceTree = ""; }; 638053580D3E95A10003DB13 /* playCountForPersistentId.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.scpt; name = playCountForPersistentId.scpt; path = scripts/playCountForPersistentId.scpt; sourceTree = ""; }; 6392AF620D788A2D00E1B4F6 /* ITunesPlaysDatabase.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = ITunesPlaysDatabase.cpp; sourceTree = ""; }; 6392AF630D788A2D00E1B4F6 /* ITunesPlaysDatabase.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = ITunesPlaysDatabase.h; sourceTree = ""; }; 63B2F45D0E9FBECD0088AF7A /* ITunesTrack.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = ITunesTrack.cpp; sourceTree = ""; }; 63E5E4760D6B51C500774857 /* IPodDetector_mac.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = IPodDetector_mac.cpp; sourceTree = ""; }; 63ED46BF0DD8D51D00F6FBE0 /* currentTrack.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.scpt; name = currentTrack.scpt; path = scripts/currentTrack.scpt; sourceTree = ""; }; AF289ABC0CDFDCF200C8C0B4 /* _iTunes.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = _iTunes.plist; sourceTree = ""; }; DC2667A60BD9410900B4ED68 /* AudioScrobbler.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AudioScrobbler.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; FB0C1E660D9919CD004820B9 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = /System/Library/Frameworks/CoreServices.framework; sourceTree = ""; }; FB0C20CF0D9922CD004820B9 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; FB3EA6B70D5795BE003DC14E /* ITunesPlaysDatabase_mac.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = ITunesPlaysDatabase_mac.cpp; sourceTree = ""; }; FB3EA6C80D587677003DC14E /* ITunesTrack.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = ITunesTrack.h; sourceTree = ""; }; FB4ED6F20D6B00CE00F243B8 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = ""; }; FB5345DE0D4A060200BD9819 /* main.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = main.h; sourceTree = ""; }; FB6471A40D806439007006E4 /* IPod.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = IPod.cpp; sourceTree = ""; }; FB6471A50D80643A007006E4 /* IPod.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = IPod.h; sourceTree = ""; }; FB6471A60D80643A007006E4 /* IPod_mac.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = IPod_mac.cpp; sourceTree = ""; }; FBAF702D0D7496B000A4919B /* IPodDetector.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = IPodDetector.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ DC26679E0BD9410900B4ED68 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 4CCE0E96162725220090F29C /* Cocoa.framework in Frameworks */, DC26679F0BD9410900B4ED68 /* CoreFoundation.framework in Frameworks */, FB4ED6F30D6B00CE00F243B8 /* IOKit.framework in Frameworks */, FB0C1E670D9919CD004820B9 /* CoreServices.framework in Frameworks */, FB0C20D00D9922CD004820B9 /* Carbon.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 01285C0000CC31B17F000001 /* iTunesVisualAPI */ = { isa = PBXGroup; children = ( 4C7B8BBD141657F600C2A26C /* iTunesAPI.cpp */, 01285C0200CC31B17F000001 /* iTunesAPI.h */, 01285C0300CC31B17F000001 /* iTunesVisualAPI.h */, ); path = iTunesVisualAPI; sourceTree = ""; }; 089C166AFE841209C02AAC07 /* iTunesPlugIn */ = { isa = PBXGroup; children = ( 638053590D3E95AF0003DB13 /* Scripts */, 08FB77AFFE84173DC02AAC07 /* Source */, 089C1671FE841209C02AAC07 /* External Frameworks and Libraries */, 19C28FB6FE9D52B211CA2CBB /* Products */, 638052460D3E53940003DB13 /* ChangeLog.txt */, AF289ABC0CDFDCF200C8C0B4 /* _iTunes.plist */, ); name = iTunesPlugIn; sourceTree = ""; }; 089C1671FE841209C02AAC07 /* External Frameworks and Libraries */ = { isa = PBXGroup; children = ( 4CCE0E95162725220090F29C /* Cocoa.framework */, FB0C20CF0D9922CD004820B9 /* Carbon.framework */, FB0C1E660D9919CD004820B9 /* CoreServices.framework */, FB4ED6F20D6B00CE00F243B8 /* IOKit.framework */, 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */, ); name = "External Frameworks and Libraries"; sourceTree = ""; }; 08FB77AFFE84173DC02AAC07 /* Source */ = { isa = PBXGroup; children = ( 4C3C565916849BF0002A3DBC /* sqlite3 */, FB6471A40D806439007006E4 /* IPod.cpp */, FB6471A60D80643A007006E4 /* IPod_mac.cpp */, FB6471A50D80643A007006E4 /* IPod.h */, 6392AF620D788A2D00E1B4F6 /* ITunesPlaysDatabase.cpp */, 6392AF630D788A2D00E1B4F6 /* ITunesPlaysDatabase.h */, 63E5E4760D6B51C500774857 /* IPodDetector_mac.cpp */, FBAF702D0D7496B000A4919B /* IPodDetector.h */, 63233BAD0D7491470009EC65 /* Logger.cpp */, 63233BAE0D7491470009EC65 /* Logger.h */, FB5345DE0D4A060200BD9819 /* main.h */, 01285C0700CC38597F000001 /* main.cpp */, 633C62960D6CA1DD00B609B9 /* main_mac.cpp */, FB3EA6B70D5795BE003DC14E /* ITunesPlaysDatabase_mac.cpp */, FB3EA6C80D587677003DC14E /* ITunesTrack.h */, 63B2F45D0E9FBECD0088AF7A /* ITunesTrack.cpp */, 637A1ED30D4FCA15004FA19F /* Moose.h */, 637452E00D58B02B00429514 /* Moose_mac.cpp */, 01285C0000CC31B17F000001 /* iTunesVisualAPI */, ); name = Source; sourceTree = ""; }; 19C28FB6FE9D52B211CA2CBB /* Products */ = { isa = PBXGroup; children = ( DC2667A60BD9410900B4ED68 /* AudioScrobbler.bundle */, ); name = Products; sourceTree = ""; }; 4C3C565916849BF0002A3DBC /* sqlite3 */ = { isa = PBXGroup; children = ( 4C3C565716849BEB002A3DBC /* sqlite3.h */, 4C3C565616849BEB002A3DBC /* sqlite3.c */, ); name = sqlite3; sourceTree = ""; }; 638053590D3E95AF0003DB13 /* Scripts */ = { isa = PBXGroup; children = ( 632E70520DD9FEF900A16D88 /* playCountForDatabaseId.scpt */, 63ED46BF0DD8D51D00F6FBE0 /* currentTrack.scpt */, 637A231D0DAA9B1C004590E1 /* uninstall.sh */, 638053560D3E95A10003DB13 /* currentTrackLocation.scpt */, 638053570D3E95A10003DB13 /* currentTrackPersistentId.scpt */, 638053580D3E95A10003DB13 /* playCountForPersistentId.scpt */, ); name = Scripts; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ DC2667930BD9410900B4ED68 /* iTunes Scrobbler */ = { isa = PBXNativeTarget; buildConfigurationList = DC2667A10BD9410900B4ED68 /* Build configuration list for PBXNativeTarget "iTunes Scrobbler" */; buildPhases = ( DC2667970BD9410900B4ED68 /* Resources */, DC26679B0BD9410900B4ED68 /* Sources */, DC26679E0BD9410900B4ED68 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = "iTunes Scrobbler"; productInstallPath = "/Users/guillerm/Library/iTunes/iTunes Plug-ins"; productName = iTunesPlugIn; productReference = DC2667A60BD9410900B4ED68 /* AudioScrobbler.bundle */; productType = "com.apple.product-type.bundle"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 089C1669FE841209C02AAC07 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0420; }; buildConfigurationList = AF2F40450BD811FE009D75EB /* Build configuration list for PBXProject "_iTunes" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 1; knownRegions = ( English, Japanese, French, German, ); mainGroup = 089C166AFE841209C02AAC07 /* iTunesPlugIn */; projectDirPath = ""; projectRoot = ""; targets = ( DC2667930BD9410900B4ED68 /* iTunes Scrobbler */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ DC2667970BD9410900B4ED68 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 632E70550DD9FF0200A16D88 /* playCountForDatabaseId.scpt in Resources */, 63ED46C20DD8D52900F6FBE0 /* currentTrack.scpt in Resources */, 634E6EF50D3FBA7D00E16E0D /* currentTrackLocation.scpt in Resources */, 634E6EF60D3FBA7D00E16E0D /* currentTrackPersistentId.scpt in Resources */, 634E6EF70D3FBA7D00E16E0D /* playCountForPersistentId.scpt in Resources */, 638052470D3E53940003DB13 /* ChangeLog.txt in Resources */, 637A231E0DAA9B1C004590E1 /* uninstall.sh in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ DC26679B0BD9410900B4ED68 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( DC26679C0BD9410900B4ED68 /* main.cpp in Sources */, FB3EA6B90D5795BE003DC14E /* ITunesPlaysDatabase_mac.cpp in Sources */, 637452E10D58B02B00429514 /* Moose_mac.cpp in Sources */, 63E5E4770D6B51C500774857 /* IPodDetector_mac.cpp in Sources */, 633C62970D6CA1DD00B609B9 /* main_mac.cpp in Sources */, 63233BAF0D7491470009EC65 /* Logger.cpp in Sources */, 6392AF640D788A2D00E1B4F6 /* ITunesPlaysDatabase.cpp in Sources */, FB6471A70D80643A007006E4 /* IPod.cpp in Sources */, FB6471A90D80643A007006E4 /* IPod_mac.cpp in Sources */, 63B2F45E0E9FBECD0088AF7A /* ITunesTrack.cpp in Sources */, 4C7B8BBE141657F600C2A26C /* iTunesAPI.cpp in Sources */, 4C3C565816849BEC002A3DBC /* sqlite3.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ AF2F40460BD811FE009D75EB /* Development */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CODE_SIGN_IDENTITY = "Developer ID Application: Last.fm"; GCC_VERSION = ""; HEADER_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 10.6; PROVISIONING_PROFILE = ""; SDKROOT = macosx; USER_HEADER_SEARCH_PATHS = ../../; }; name = Development; }; AF2F40470BD811FE009D75EB /* Deployment */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CODE_SIGN_IDENTITY = "Developer ID Application: Last.fm"; DEAD_CODE_STRIPPING = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_SYMBOLS_PRIVATE_EXTERN = YES; GCC_VERSION = ""; MACOSX_DEPLOYMENT_TARGET = 10.6; PROVISIONING_PROFILE = ""; SDKROOT = macosx; USER_HEADER_SEARCH_PATHS = ../../; }; name = Deployment; }; AF2F40480BD811FE009D75EB /* Default */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CODE_SIGN_IDENTITY = "Developer ID Application: Last.fm"; GCC_VERSION = ""; MACOSX_DEPLOYMENT_TARGET = 10.6; PROVISIONING_PROFILE = ""; SDKROOT = macosx; USER_HEADER_SEARCH_PATHS = ../../; }; name = Default; }; DC2667A20BD9410900B4ED68 /* Development */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework/Frameworks\"", ); GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_INPUT_FILETYPE = automatic; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = SQLITE_ENABLE_UNLOCK_NOTIFY; GCC_SYMBOLS_PRIVATE_EXTERN = NO; GENERATE_PKGINFO_FILE = YES; HEADER_SEARCH_PATHS = ""; INFOPLIST_FILE = _iTunes.plist; LIBRARY_SEARCH_PATHS = ( "$(inherited)", /usr/local/Cellar/sqlite/3.7.14/lib, ); MACOSX_DEPLOYMENT_TARGET = 10.6; OTHER_CFLAGS = ""; OTHER_LDFLAGS = "-lsqlite3"; OTHER_REZFLAGS = ""; PRODUCT_NAME = AudioScrobbler; REZ_EXECUTABLE = YES; SDKROOT = macosx; SECTORDER_FLAGS = ""; STRIP_INSTALLED_PRODUCT = NO; WARNING_CFLAGS = ( "-Wmost", "-Wno-four-char-constants", "-Wno-unknown-pragmas", ); WRAPPER_EXTENSION = bundle; ZERO_LINK = YES; }; name = Development; }; DC2667A30BD9410900B4ED68 /* Deployment */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; COPY_PHASE_STRIP = YES; DEAD_CODE_STRIPPING = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework/Frameworks\"", ); GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_INPUT_FILETYPE = automatic; GCC_OPTIMIZATION_LEVEL = s; GCC_PREPROCESSOR_DEFINITIONS = ( NDEBUG, SQLITE_ENABLE_UNLOCK_NOTIFY, ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GENERATE_PKGINFO_FILE = YES; HEADER_SEARCH_PATHS = ""; INFOPLIST_FILE = _iTunes.plist; LIBRARY_SEARCH_PATHS = ( "$(inherited)", /usr/local/Cellar/sqlite/3.7.14/lib, ); MACOSX_DEPLOYMENT_TARGET = 10.6; OTHER_CFLAGS = ""; OTHER_LDFLAGS = "-lsqlite3"; OTHER_REZFLAGS = ""; PRODUCT_NAME = AudioScrobbler; REZ_EXECUTABLE = YES; SDKROOT = macosx; SECTORDER_FLAGS = ""; STRIP_STYLE = all; WARNING_CFLAGS = ( "-Wmost", "-Wno-four-char-constants", "-Wno-unknown-pragmas", ); WRAPPER_EXTENSION = bundle; ZERO_LINK = NO; }; name = Deployment; }; DC2667A40BD9410900B4ED68 /* Default */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework/Frameworks\"", ); GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_INPUT_FILETYPE = automatic; GCC_OPTIMIZATION_LEVEL = 3; GCC_PREPROCESSOR_DEFINITIONS = SQLITE_ENABLE_UNLOCK_NOTIFY; GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ""; INFOPLIST_FILE = _iTunes.plist; LIBRARY_SEARCH_PATHS = ( "$(inherited)", /usr/local/Cellar/sqlite/3.7.14/lib, ); MACOSX_DEPLOYMENT_TARGET = 10.6; OTHER_CFLAGS = ""; OTHER_LDFLAGS = "-lsqlite3"; OTHER_REZFLAGS = ""; PRODUCT_NAME = AudioScrobbler; REZ_EXECUTABLE = YES; SDKROOT = macosx; SECTORDER_FLAGS = ""; WARNING_CFLAGS = ( "-Wmost", "-Wno-four-char-constants", "-Wno-unknown-pragmas", ); WRAPPER_EXTENSION = bundle; }; name = Default; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ AF2F40450BD811FE009D75EB /* Build configuration list for PBXProject "_iTunes" */ = { isa = XCConfigurationList; buildConfigurations = ( AF2F40460BD811FE009D75EB /* Development */, AF2F40470BD811FE009D75EB /* Deployment */, AF2F40480BD811FE009D75EB /* Default */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Default; }; DC2667A10BD9410900B4ED68 /* Build configuration list for PBXNativeTarget "iTunes Scrobbler" */ = { isa = XCConfigurationList; buildConfigurations = ( DC2667A20BD9410900B4ED68 /* Development */, DC2667A30BD9410900B4ED68 /* Deployment */, DC2667A40BD9410900B4ED68 /* Default */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Default; }; /* End XCConfigurationList section */ }; rootObject = 089C1669FE841209C02AAC07 /* Project object */; } ================================================ FILE: plugins/iTunes/iTunesVisualAPI/iTunesAPI.cpp ================================================ // // File: iTunesAPI.c // // Abstract: part of iTunes Visual SDK // // Version: 2.0 // // Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ( "Apple" ) // in consideration of your agreement to the following terms, and your use, // installation, modification or redistribution of this Apple software // constitutes acceptance of these terms. If you do not agree with these // terms, please do not use, install, modify or redistribute this Apple // software. // // In consideration of your agreement to abide by the following terms, and // subject to these terms, Apple grants you a personal, non - exclusive // license, under Apple's copyrights in this original Apple software ( the // "Apple Software" ), to use, reproduce, modify and redistribute the Apple // Software, with or without modifications, in source and / or binary forms; // provided that if you redistribute the Apple Software in its entirety and // without modifications, you must retain this notice and the following text // and disclaimers in all such redistributions of the Apple Software. Neither // the name, trademarks, service marks or logos of Apple Inc. may be used to // endorse or promote products derived from the Apple Software without specific // prior written permission from Apple. Except as expressly stated in this // notice, no other rights or licenses, express or implied, are granted by // Apple herein, including but not limited to any patent rights that may be // infringed by your derivative works or by other works in which the Apple // Software may be incorporated. // // The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO // WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED // WARRANTIES OF NON - INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION // ALONE OR IN COMBINATION WITH YOUR PRODUCTS. // // IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR // CONSEQUENTIAL DAMAGES ( INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION ) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION // AND / OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER // UNDER THEORY OF CONTRACT, TORT ( INCLUDING NEGLIGENCE ), STRICT LIABILITY OR // OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Copyright © 2000-2011 Apple Inc. All Rights Reserved. // #include "iTunesAPI.h" #include "iTunesVisualAPI.h" #include // SetNumVersion // void SetNumVersion(NumVersion *numVersion, UInt8 majorRev, UInt8 minorAndBugRev, UInt8 stage, UInt8 nonRelRev) { numVersion->majorRev = majorRev; numVersion->minorAndBugRev = minorAndBugRev; numVersion->stage = stage; numVersion->nonRelRev = nonRelRev; } // ITCallApplicationInternal // static OSStatus ITCallApplicationInternal(void *appCookie, ITAppProcPtr handler, OSType message, UInt32 messageMajorVersion, UInt32 messageMinorVersion, PlayerMessageInfo *messageInfo) { PlayerMessageInfo localMessageInfo; if (messageInfo == nil) { memset(&localMessageInfo, 0, sizeof(localMessageInfo)); messageInfo = &localMessageInfo; } messageInfo->messageMajorVersion = messageMajorVersion; messageInfo->messageMinorVersion = messageMinorVersion; messageInfo->messageInfoSize = sizeof(PlayerMessageInfo); return handler(appCookie, message, messageInfo); } // ITCallApplication // OSStatus ITCallApplication(void *appCookie, ITAppProcPtr handler, OSType message, PlayerMessageInfo *messageInfo) { return ITCallApplicationInternal(appCookie, handler, message, kITPluginMajorMessageVersion, kITPluginMinorMessageVersion, messageInfo); } // PlayerSetFullScreen // OSStatus PlayerSetFullScreen(void *appCookie, ITAppProcPtr appProc, Boolean fullScreen) { PlayerMessageInfo messageInfo; memset(&messageInfo, 0, sizeof(messageInfo)); messageInfo.u.setFullScreenMessage.fullScreen = fullScreen; return ITCallApplication(appCookie, appProc, kPlayerSetFullScreenMessage, &messageInfo); } // PlayerRequestCurrentTrackCoverArt // OSStatus PlayerRequestCurrentTrackCoverArt(void *appCookie, ITAppProcPtr appProc) { OSStatus status; status = ITCallApplication(appCookie, appProc, kPlayerRequestCurrentTrackCoverArtMessage, nil); return status; } // PlayerGetPluginData // OSStatus PlayerGetPluginData(void *appCookie, ITAppProcPtr appProc, void *dataPtr, UInt32 dataBufferSize, UInt32 *dataSize) { OSStatus status; PlayerMessageInfo messageInfo; memset(&messageInfo, 0, sizeof(messageInfo)); messageInfo.u.getPluginDataMessage.dataPtr = dataPtr; messageInfo.u.getPluginDataMessage.dataBufferSize = dataBufferSize; status = ITCallApplication(appCookie, appProc, kPlayerGetPluginDataMessage, &messageInfo); if (dataSize != nil) *dataSize = messageInfo.u.getPluginDataMessage.dataSize; return status; } // PlayerSetPluginData // OSStatus PlayerSetPluginData(void *appCookie, ITAppProcPtr appProc, void *dataPtr, UInt32 dataSize) { PlayerMessageInfo messageInfo; memset(&messageInfo, 0, sizeof(messageInfo)); messageInfo.u.setPluginDataMessage.dataPtr = dataPtr; messageInfo.u.setPluginDataMessage.dataSize = dataSize; return ITCallApplication(appCookie, appProc, kPlayerSetPluginDataMessage, &messageInfo); } // PlayerGetPluginNamedData // OSStatus PlayerGetPluginNamedData(void *appCookie, ITAppProcPtr appProc, ConstStringPtr dataName, void *dataPtr, UInt32 dataBufferSize, UInt32 *dataSize) { OSStatus status; PlayerMessageInfo messageInfo; memset(&messageInfo, 0, sizeof(messageInfo)); messageInfo.u.getPluginNamedDataMessage.dataName = dataName; messageInfo.u.getPluginNamedDataMessage.dataPtr = dataPtr; messageInfo.u.getPluginNamedDataMessage.dataBufferSize = dataBufferSize; status = ITCallApplication(appCookie, appProc, kPlayerGetPluginNamedDataMessage, &messageInfo); if (dataSize != nil) *dataSize = messageInfo.u.getPluginNamedDataMessage.dataSize; return status; } // PlayerSetPluginNamedData // OSStatus PlayerSetPluginNamedData(void *appCookie, ITAppProcPtr appProc, ConstStringPtr dataName, void *dataPtr, UInt32 dataSize) { PlayerMessageInfo messageInfo; memset(&messageInfo, 0, sizeof(messageInfo)); messageInfo.u.setPluginNamedDataMessage.dataName = dataName; messageInfo.u.setPluginNamedDataMessage.dataPtr = dataPtr; messageInfo.u.setPluginNamedDataMessage.dataSize = dataSize; return ITCallApplication(appCookie, appProc, kPlayerSetPluginNamedDataMessage, &messageInfo); } // PlayerIdle // OSStatus PlayerIdle(void *appCookie, ITAppProcPtr appProc) { return ITCallApplication(appCookie, appProc, kPlayerIdleMessage, nil); } // PlayerShowAbout // void PlayerShowAbout(void *appCookie, ITAppProcPtr appProc) { ITCallApplication(appCookie, appProc, kPlayerShowAboutMessage, nil); } // PlayerOpenURL // void PlayerOpenURL(void *appCookie, ITAppProcPtr appProc, SInt8 *string, UInt32 length) { PlayerMessageInfo messageInfo; memset(&messageInfo, 0, sizeof(messageInfo)); messageInfo.u.openURLMessage.url = string; messageInfo.u.openURLMessage.length = length; ITCallApplication(appCookie, appProc, kPlayerOpenURLMessage, &messageInfo); } // PlayerUnregisterPlugin // OSStatus PlayerUnregisterPlugin(void *appCookie, ITAppProcPtr appProc, PlayerMessageInfo *messageInfo) { return ITCallApplication(appCookie, appProc, kPlayerUnregisterPluginMessage, messageInfo); } // PlayerRegisterVisualPlugin // OSStatus PlayerRegisterVisualPlugin(void *appCookie, ITAppProcPtr appProc, PlayerMessageInfo *messageInfo) { return ITCallApplicationInternal(appCookie, appProc, kPlayerRegisterVisualPluginMessage, kITVisualPluginMajorMessageVersion, kITVisualPluginMinorMessageVersion, messageInfo); } // PlayerGetPluginITFileSpec // OSStatus PlayerGetPluginITFileSpec(void *appCookie, ITAppProcPtr appProc, ITFileSpec *pluginFileSpec) { PlayerMessageInfo messageInfo; memset(&messageInfo, 0, sizeof(messageInfo)); messageInfo.u.getPluginITFileSpecMessage.fileSpec = pluginFileSpec; return ITCallApplication(appCookie, appProc, kPlayerGetPluginITFileSpecMessage, &messageInfo); } // PlayerGetFileTrackInfo // OSStatus PlayerGetFileTrackInfo(void *appCookie, ITAppProcPtr appProc, const ITFileSpec *fileSpec, ITTrackInfo *trackInfo) { PlayerMessageInfo messageInfo; memset(&messageInfo, 0, sizeof(messageInfo)); messageInfo.u.getFileTrackInfoMessage.fileSpec = fileSpec; messageInfo.u.getFileTrackInfoMessage.trackInfo = trackInfo; return ITCallApplication(appCookie, appProc, kPlayerGetFileTrackInfoMessage, &messageInfo); } // PlayerSetFileTrackInfo // OSStatus PlayerSetFileTrackInfo(void *appCookie, ITAppProcPtr appProc, const ITFileSpec *fileSpec, const ITTrackInfo *trackInfo) { PlayerMessageInfo messageInfo; memset(&messageInfo, 0, sizeof(messageInfo)); messageInfo.u.setFileTrackInfoMessage.fileSpec = fileSpec; messageInfo.u.setFileTrackInfoMessage.trackInfo = trackInfo; return ITCallApplication(appCookie, appProc, kPlayerSetFileTrackInfoMessage, &messageInfo); } // PlayerGetITTrackInfoSize // OSStatus PlayerGetITTrackInfoSize(void *appCookie, ITAppProcPtr appProc, UInt32 appPluginMajorVersion, UInt32 appPluginMinorVersion, UInt32 *itTrackInfoSize) { PlayerMessageInfo messageInfo; OSStatus status; /* Note: appPluginMajorVersion and appPluginMinorVersion are the versions given to the plugin by iTunes in the plugin's init message. These versions are *not* the version of the API used when the plugin was compiled. */ *itTrackInfoSize = 0; memset(&messageInfo, 0, sizeof(messageInfo)); status = ITCallApplication(appCookie, appProc, kPlayerGetITTrackInfoSizeMessage, &messageInfo); if( status == noErr ) { *itTrackInfoSize = messageInfo.u.getITTrackInfoSizeMessage.itTrackInfoSize; } else if( appPluginMajorVersion == 10 && appPluginMinorVersion == 2 ) { // iTunes 2.0.x *itTrackInfoSize = ((UInt32)(uintptr_t) &((ITTrackInfo *) 0)->composer); status = noErr; } else if( appPluginMajorVersion == 10 && appPluginMinorVersion == 3 ) { // iTunes 3.0.x *itTrackInfoSize = ((UInt32)(uintptr_t) &((ITTrackInfo *) 0)->beatsPerMinute); status = noErr; } else { // iTunes 4.0 and later implement the kPlayerGetITTrackInfoSizeMessage message. If you got here // then the appPluginMajorVersion or appPluginMinorVersion are incorrect. status = paramErr; } if( status == noErr && (*itTrackInfoSize) > sizeof(ITTrackInfo) ) { // iTunes is using a larger ITTrackInfo than the one when this plugin was compiled. Pin *itTrackInfoSize to the plugin's known size *itTrackInfoSize = sizeof(ITTrackInfo); } return status; } ================================================ FILE: plugins/iTunes/iTunesVisualAPI/iTunesAPI.h ================================================ // // File: iTunesAPI.h // // Abstract: part of iTunes Visual SDK // // Version: 2.0 // // Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ( "Apple" ) // in consideration of your agreement to the following terms, and your use, // installation, modification or redistribution of this Apple software // constitutes acceptance of these terms. If you do not agree with these // terms, please do not use, install, modify or redistribute this Apple // software. // // In consideration of your agreement to abide by the following terms, and // subject to these terms, Apple grants you a personal, non - exclusive // license, under Apple's copyrights in this original Apple software ( the // "Apple Software" ), to use, reproduce, modify and redistribute the Apple // Software, with or without modifications, in source and / or binary forms; // provided that if you redistribute the Apple Software in its entirety and // without modifications, you must retain this notice and the following text // and disclaimers in all such redistributions of the Apple Software. Neither // the name, trademarks, service marks or logos of Apple Inc. may be used to // endorse or promote products derived from the Apple Software without specific // prior written permission from Apple. Except as expressly stated in this // notice, no other rights or licenses, express or implied, are granted by // Apple herein, including but not limited to any patent rights that may be // infringed by your derivative works or by other works in which the Apple // Software may be incorporated. // // The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO // WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED // WARRANTIES OF NON - INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION // ALONE OR IN COMBINATION WITH YOUR PRODUCTS. // // IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR // CONSEQUENTIAL DAMAGES ( INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION ) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION // AND / OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER // UNDER THEORY OF CONTRACT, TORT ( INCLUDING NEGLIGENCE ), STRICT LIABILITY OR // OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Copyright © 2000-2011 Apple Inc. All Rights Reserved. // #ifndef ITUNESAPI_H_ #define ITUNESAPI_H_ #if PRAGMA_ONCE #pragma once #endif #if defined(_MSC_VER) #define TARGET_OS_MAC 0 #define TARGET_OS_WIN32 1 #else #define TARGET_OS_MAC 1 #define TARGET_OS_WIN32 0 #endif #if TARGET_OS_MAC #include #include #include #include #endif #if TARGET_OS_WIN32 #include #endif #if !defined(__CONDITIONALMACROS__) typedef unsigned long UInt32; typedef signed long SInt32; typedef unsigned short UInt16; typedef signed short SInt16; typedef unsigned char UInt8; typedef signed char SInt8; typedef UInt32 OptionBits; typedef UInt8 Str255[256]; typedef UInt8 Str63[64]; typedef UInt8 * StringPtr; typedef const UInt8 * ConstStringPtr; typedef UInt32 OSType; typedef SInt32 OSStatus; typedef UInt16 UniChar; typedef double Float64; #if TARGET_OS_WIN32 typedef unsigned __int64 UInt64; #else typedef unsigned long long UInt64; #endif typedef void ** Handle; struct NumVersion { UInt8 majorRev; UInt8 minorAndBugRev; UInt8 stage; UInt8 nonRelRev; }; typedef struct NumVersion NumVersion; struct Point { short v; short h; }; typedef struct Point Point; struct Rect { short top; short left; short bottom; short right; }; typedef struct Rect Rect; typedef UInt8 Boolean; typedef UInt32 UnsignedFixed; typedef void * LogicalAddress; #define false 0 #define true 1 #define nil NULL enum { noErr = 0, unimpErr = -4, readErr = -19, writErr = -20, openErr = -23, closErr = -24, dirFulErr = -33, dskFulErr = -34, nsvErr = -35, ioErr = -36, bdNamErr = -37, fnOpnErr = -38, eofErr = -39, posErr = -40, tmfoErr = -42, fnfErr = -43, wPrErr = -44, fLckdErr = -45, vLckdErr = -46, fBsyErr = -47, dupFNErr = -48, opWrErr = -49, paramErr = -50, permErr = -54, nsDrvErr = -56, wrPermErr = -61, memFullErr = -108, dirNFErr = -120, badMovErr = -122 }; enum { developStage = 0x20, alphaStage = 0x40, betaStage = 0x60, finalStage = 0x80 }; struct SoundComponentData { long flags; OSType format; short numChannels; short sampleSize; UnsignedFixed sampleRate; long sampleCount; UInt8 * buffer; long reserved; }; typedef struct SoundComponentData SoundComponentData; struct AudioStreamBasicDescription { Float64 mSampleRate; UInt32 mFormatID; UInt32 mFormatFlags; UInt32 mBytesPerPacket; UInt32 mFramesPerPacket; UInt32 mBytesPerFrame; UInt32 mChannelsPerFrame; UInt32 mBitsPerChannel; UInt32 mReserved; }; typedef struct AudioStreamBasicDescription AudioStreamBasicDescription; #endif #if TARGET_OS_WIN32 #define VISUAL_PLATFORM_VIEW HWND #define VISUAL_PLATFORM_DATA LPCVOID #else #ifdef __OBJC__ @class NSView; #else struct NSView; #endif #define VISUAL_PLATFORM_VIEW NSView* #define VISUAL_PLATFORM_DATA CFDataRef #endif #ifdef __cplusplus extern "C" { #endif #if PRAGMA_STRUCT_ALIGN #pragma options align=power #elif PRAGMA_STRUCT_PACKPUSH #pragma pack(push, 4) #elif PRAGMA_STRUCT_PACK #pragma pack(4) #endif enum { kITPluginMajorMessageVersion = 10, kITPluginMinorMessageVersion = 9 }; enum { kTrackSupportsID3Tags = (1u << 0), kTrackHasVariableBitRate = (1u << 1), kTrackHasVideo = (1u << 6) /* Track has video track which can be played in iTunes */ }; typedef OptionBits ITTrackAttributes; enum { /* These mask values are specified in ITTrackInfo.validFields to indicate which fields contain valid data */ kITTIFieldInvalid = 0, kITTINameFieldMask = (1u << 0), kITTIFileNameFieldMask = (1u << 1), kITTIArtistFieldMask = (1u << 2), kITTIAlbumFieldMask = (1u << 3), kITTIGenreFieldMask = (1u << 4), kITTIKindFieldMask = (1u << 5), kITTITrackNumberFieldsMask = (1u << 6), kITTIYearFieldMask = (1u << 7), kITTISoundVolumeFieldMask = (1u << 8), kITTIEQPresetFieldMask = (1u << 9), kITTICommentsFieldMask = (1u << 10), kITTITotalTimeFieldMask = (1u << 11), kITTIStartTimeFieldMask = (1u << 12), kITTIStopTimeFieldMask = (1u << 13), kITTISizeFieldMask = (1u << 14), kITTIBitRateFieldMask = (1u << 15), kITTISampleRateFieldMask = (1u << 16), kITTIAttributesFieldMask = (1u << 17), kITTIFileTypeFieldMask = (1u << 18), kITTIDateFieldMask = (1u << 19), // kITTIFileCreatorFieldMask = (1u << 20), /* Removed in iTunes 9.0.2 */ kITTIComposerFieldMask = (1u << 21), /* Added in iTunes 3.0 */ kITTICompilationFieldMask = (1u << 22), /* Added in iTunes 3.0 */ kITTIDiscNumberFieldsMask = (1u << 23), /* Added in iTunes 3.0 */ kITTITrackRatingFieldMask = (1u << 24), /* Added in iTunes 3.0 - used to be called kITTIUserRatingFieldMask */ kITTIPlayCountFieldMask = (1u << 25), /* Added in iTunes 3.0 */ kITTILastPlayDateFieldMask = (1u << 26), /* Added in iTunes 3.0 */ kITTIBeatsPerMinuteFieldMask = (1u << 27), /* Added in iTunes 4.0 */ kITTIGroupingFieldMask = (1u << 28), /* Added in iTunes 4.2 */ kITTIGaplessAlbumFieldMask = (1u << 29), /* Added in iTunes 7.0 */ kITTIAlbumArtistFieldMask = (1u << 30) /* Added in iTunes 7.0 */ }; typedef OptionBits ITTIFieldMask; #define kLastKnownITTIField kITTIAlbumArtistFieldMask #define kAllKnownITTIFieldsMask ((((UInt32) kLastKnownITTIField) << 1) - 1) enum { kITTIUserModifiableFieldsMask = kITTINameFieldMask | kITTIArtistFieldMask | kITTIAlbumFieldMask | kITTIGroupingFieldMask | kITTIGenreFieldMask | kITTITrackNumberFieldsMask | kITTIYearFieldMask | kITTISoundVolumeFieldMask | kITTIEQPresetFieldMask | kITTICommentsFieldMask | kITTIStartTimeFieldMask | kITTIStopTimeFieldMask | kITTIComposerFieldMask | kITTICompilationFieldMask | kITTIDiscNumberFieldsMask | kITTITrackRatingFieldMask | kITTIBeatsPerMinuteFieldMask | kITTIGaplessAlbumFieldMask | kITTIAlbumArtistFieldMask }; typedef UniChar ITUniStr255[256]; /* Similar to Str255. First element is length of string in characters. */ typedef UniChar * ITUniStringPtr; typedef const UniChar * ConstITUniStringPtr; #if TARGET_OS_MAC typedef FSRef ITFileSpec; #endif #if TARGET_OS_WIN32 #define kITFileSpecMaxPathLength (MAX_PATH - 1) typedef struct ITFileSpec { UInt16 length; // Length in characters UniChar fullPath[kITFileSpecMaxPathLength]; } ITFileSpec; #endif struct ITTrackInfo { ITTIFieldMask validFields; UInt32 recordLength; /* Size of this structure in bytes */ ITUniStr255 name; ITUniStr255 fileName; ITUniStr255 artist; ITUniStr255 album; ITUniStr255 genre; ITUniStr255 kind; ITUniStr255 eqPresetName; ITUniStr255 comments; UInt32 trackNumber; UInt32 numTracks; UInt16 year; SInt16 soundVolumeAdjustment; /* Valid range is -255 to +255 */ UInt32 totalTimeInMS; UInt32 startTimeInMS; UInt32 stopTimeInMS; UInt32 date; UInt32 oldSizeInBytes; /* Deprecated in iTunes 7.1 */ UInt32 bitRate; UInt32 oldSampleRateFixed; /* Deprecated in iTunes 5.0 */ OSType fileType; OSType obsoleteFileCreator; /* Removed in iTunes 9.0.2 */ ITTrackAttributes attributes; ITTrackAttributes validAttributes; /* Mask indicating which attributes are applicable */ ITUniStr255 composer; /* Added in iTunes 3.0 */ Boolean isCompilationTrack; /* Added in iTunes 3.0 */ Boolean partOfGaplessAlbum; /* Added in iTunes 7.0 (was reserved) */ UInt16 trackRating; /* Added in iTunes 3.0. 0 = unrated, valid values are 20, 40, 60, 80 and 100. Used to be called userRating */ UInt16 discNumber; /* Added in iTunes 3.0 */ UInt16 numDiscs; /* Added in iTunes 3.0 */ UInt32 playCount; /* Added in iTunes 3.0 */ UInt32 lastPlayDate; /* Added in iTunes 3.0 */ UInt16 beatsPerMinute; /* Added in iTunes 4.0 */ UInt16 reserved; /* Reserved. Must be zero. */ ITUniStr255 grouping; /* Added in iTunes 4.0 */ float sampleRateFloat; /* Added in iTunes 5.0 */ ITUniStr255 albumArtist; /* Added in iTunes 7.0 */ UInt64 sizeInBytes; /* Added in iTunes 7.1 */ }; typedef struct ITTrackInfo ITTrackInfo; enum { kStreamInfoOption_OverrideAll = 0x00000001 }; struct ITStreamInfo { SInt32 version; ITUniStr255 streamTitle; ITUniStr255 streamURL; ITUniStr255 streamMessage; OptionBits options; // added in iTunes 10.3 - add at the end of struct to maintain compatibility ITUniStr255 streamName; // added in iTunes 10.3 - add at the end of struct to maintain compatibility }; typedef struct ITStreamInfo ITStreamInfo; enum { /* messages sent to plugin main */ kPluginInitMessage = 'init', kPluginCleanupMessage = 'clr ', kPluginPrepareToQuitMessage = 'prqt', kPluginIdleMessage = 'idle' }; enum { /* PluginInitMessage.options */ kPluginWantsIdleMessages = (1u << 1), /* Send idle messages to plugin main */ kPluginWantsToBeLeftOpen = (1u << 2), /* Don't close this plugin just because it didn't register anyone */ kPluginWantsDisplayNotification = (1u << 5) /* The plugin wants to know when the display depth/size changes */ }; enum { /* iTunes API messages */ kPlayerRegisterVisualPluginMessage = 'regv', /* Register a visual plugin */ /* Available for all plugins */ kPlayerUnregisterPluginMessage = 'unrg', /* Unregister the plugin this comes from */ kPlayerIdleMessage = 'idle', /* Give iTunes some time */ kPlayerShowAboutMessage = 'abou', /* Show the about box. */ kPlayerOpenURLMessage = 'url ', /* Open a URL */ kPlayerSetPluginDataMessage = 'sprf', /* Set plugin preferences */ kPlayerGetPluginDataMessage = 'gprf', /* Get plugin preferences */ kPlayerSetPluginNamedDataMessage = 'snpr', /* Set plugin named preferenes */ kPlayerGetPluginNamedDataMessage = 'gnpr', /* Get plugin named preferenes */ kPlayerGetFileTrackInfoMessage = 'gfti', /* Query iTunes for information about a file */ kPlayerSetFileTrackInfoMessage = 'sfti', /* Ask iTunes to set information about a file */ kPlayerGetITTrackInfoSizeMessage = 'itsz', /* Query iTunes for the sizeof(ITTrackInfo). This allows newer plugins to correctly workd with older versions of iTunes. */ kPlayerHandleMacOSEventMessage = 'evnt', /* Tell player to handle unhandled event */ kPlayerGetPluginITFileSpecMessage = 'itfs', /* Get the location of the plugin executable (iTunes 4.1 or later) */ kPluginDisplayChangedMessage = 'disp' /* Something about some display has changed */ }; struct PlayerMessageInfo; typedef OSStatus (*ITAppProcPtr)(void *appCookie, OSType message, struct PlayerMessageInfo *messageInfo); /* Plugin main Messages */ struct PluginInitMessage { UInt32 majorVersion; /* Input */ UInt32 minorVersion; /* Input */ void * appCookie; /* Input */ ITAppProcPtr appProc; /* Input */ OptionBits options; /* Output, see above for values */ void * refCon; /* Output */ }; typedef struct PluginInitMessage PluginInitMessage; struct PluginMessageInfo { union { PluginInitMessage initMessage; } u; }; typedef struct PluginMessageInfo PluginMessageInfo; /* Plugin main entry point message handler */ typedef OSStatus (*PluginProcPtr)(OSType message, PluginMessageInfo *messageInfo, void *refCon); /* Visual plugin message handler */ struct VisualPluginMessageInfo; typedef OSStatus (*VisualPluginProcPtr)(OSType message, struct VisualPluginMessageInfo *messageInfo, void *refCon); /* Callbacks to iTunes */ struct PlayerOpenURLMessage { SInt8 * url; UInt32 length; }; typedef struct PlayerOpenURLMessage PlayerOpenURLMessage; struct PlayerSetPluginDataMessage { void * dataPtr; /* Input */ UInt32 dataSize; /* Input */ }; typedef struct PlayerSetPluginDataMessage PlayerSetPluginDataMessage; struct PlayerGetPluginDataMessage { void * dataPtr; /* Input */ UInt32 dataBufferSize; /* Input */ UInt32 dataSize; /* Output */ }; typedef struct PlayerGetPluginDataMessage PlayerGetPluginDataMessage; struct PlayerSetPluginNamedDataMessage { ConstStringPtr dataName; /* Input */ void * dataPtr; /* Input */ UInt32 dataSize; /* Input */ }; typedef struct PlayerSetPluginNamedDataMessage PlayerSetPluginNamedDataMessage; struct PlayerGetPluginNamedDataMessage { ConstStringPtr dataName; /* Input */ void * dataPtr; /* Input */ UInt32 dataBufferSize; /* Input */ UInt32 dataSize; /* Output */ }; typedef struct PlayerGetPluginNamedDataMessage PlayerGetPluginNamedDataMessage; struct PlayerGetPluginITFileSpecMessage { ITFileSpec * fileSpec; /* Output */ }; typedef struct PlayerGetPluginITFileSpecMessage PlayerGetPluginITFileSpecMessage; struct PlayerGetFileTrackInfoMessage { const ITFileSpec * fileSpec; /* Input */ ITTrackInfo * trackInfo; /* Output */ }; typedef struct PlayerGetFileTrackInfoMessage PlayerGetFileTrackInfoMessage; struct PlayerSetFileTrackInfoMessage { const ITFileSpec * fileSpec; /* Input */ const ITTrackInfo * trackInfo; /* Input */ }; typedef struct PlayerSetFileTrackInfoMessage PlayerSetFileTrackInfoMessage; struct PlayerGetITTrackInfoSizeMessage { UInt32 itTrackInfoSize; /* Output */ }; typedef struct PlayerGetITTrackInfoSizeMessage PlayerGetITTrackInfoSizeMessage; /* iTunes API callback visual structures */ enum { /* PlayerRegisterVisualPluginMessage.options */ /* Plugin uses 3D exclusively (OpenGL or Direct3D), iTunes does not need to invalidate the backing native view. */ kVisualUsesOnly3D = (1u << 0), /* Plugin supports discrete or integrated graphics without requiring a mode switch. */ kVisualSupportsMuxedGraphics = (1u << 1), /* Mac-only: Plugin adds an NSView-based subview to the one provided, iTunes does not need to invalidate the backing native view.*/ kVisualUsesSubview = (1u << 2), /* Plugin wants periodic idle messages. Note that these idle messages are not for drawing, only for internal processing. */ kVisualWantsIdleMessages = (1u << 3), /* Plugin supports a configure dialog, iTunes will display an item in the menus to show it. */ kVisualWantsConfigure = (1u << 5) }; struct PlayerRegisterVisualPluginMessage { /* Input from plugin */ ITUniStr255 name; /* Displayed in the Visualizer menu */ OptionBits options; /* See above */ OSType creator; /* Identifies the plugin */ NumVersion pluginVersion; /* Version number of the plugin */ VisualPluginProcPtr handler; /* Handler for the plugin's messages */ void * registerRefCon; /* RefCon for the plugin's handler */ UInt32 pulseRateInHz; /* Send the plugin a "pulse" message N times a second (max ~= 120 but may vary) */ UInt32 numWaveformChannels; /* 0-2 waveforms requested */ UInt32 numSpectrumChannels; /* 0-2 spectrums requested */ SInt32 minWidth; /* Minimum resizeable width (0 for no restriction) */ SInt32 minHeight; /* Minimum resizeable height (0 for no restriction) */ SInt32 maxWidth; /* Maximum resizeable width (0 for no limit) */ SInt32 maxHeight; /* Maximum resizeable height (0 for no limit) */ }; typedef struct PlayerRegisterVisualPluginMessage PlayerRegisterVisualPluginMessage; struct PlayerSetFullScreenMessage { Boolean fullScreen; }; typedef struct PlayerSetFullScreenMessage PlayerSetFullScreenMessage; // iTunes API callback union structure struct PlayerMessageInfo { UInt32 messageMajorVersion; /* Should be kITPluginMajorMessageVersion */ UInt32 messageMinorVersion; /* Should be kITPluginMinorMessageVersion */ UInt32 messageInfoSize; /* Should be sizeof(PlayerMessageInfo) */ union { PlayerOpenURLMessage openURLMessage; PlayerSetPluginDataMessage setPluginDataMessage; PlayerGetPluginDataMessage getPluginDataMessage; PlayerSetPluginNamedDataMessage setPluginNamedDataMessage; PlayerGetPluginNamedDataMessage getPluginNamedDataMessage; PlayerGetFileTrackInfoMessage getFileTrackInfoMessage; PlayerSetFileTrackInfoMessage setFileTrackInfoMessage; PlayerGetITTrackInfoSizeMessage getITTrackInfoSizeMessage; PlayerGetPluginITFileSpecMessage getPluginITFileSpecMessage; // visual APIs PlayerRegisterVisualPluginMessage registerVisualPluginMessage; PlayerSetFullScreenMessage setFullScreenMessage; } u; }; typedef struct PlayerMessageInfo PlayerMessageInfo; extern OSStatus ITCallApplication(void *appCookie, ITAppProcPtr appProc, OSType message, PlayerMessageInfo *messageInfo); extern void SetNumVersion(NumVersion *numVersion, UInt8 majorRev, UInt8 minorAndBugRev, UInt8 stage, UInt8 nonRelRev); /* For all plugins */ extern OSStatus PlayerUnregisterPlugin(void *appCookie, ITAppProcPtr appProc, PlayerMessageInfo *messageInfo); extern OSStatus PlayerIdle(void *appCookie, ITAppProcPtr appProc); extern void PlayerShowAbout(void *appCookie, ITAppProcPtr appProc); extern void PlayerOpenURL(void *appCookie, ITAppProcPtr appProc, SInt8 *string, UInt32 length); extern OSStatus PlayerGetPluginData(void *appCookie, ITAppProcPtr appProc, void *dataPtr, UInt32 dataBufferSize, UInt32 *dataSize); extern OSStatus PlayerSetPluginData(void *appCookie, ITAppProcPtr appProc, void *dataPtr, UInt32 dataSize); extern OSStatus PlayerGetPluginNamedData(void *appCookie, ITAppProcPtr appProc, ConstStringPtr dataName, void *dataPtr, UInt32 dataBufferSize, UInt32 *dataSize); extern OSStatus PlayerSetPluginNamedData(void *appCookie, ITAppProcPtr appProc, ConstStringPtr dataName, void *dataPtr, UInt32 dataSize); extern OSStatus PlayerGetFileTrackInfo(void *appCookie, ITAppProcPtr appProc, const ITFileSpec *fileSpec, ITTrackInfo *trackInfo); extern OSStatus PlayerSetFileTrackInfo(void *appCookie, ITAppProcPtr appProc, const ITFileSpec *fileSpec, const ITTrackInfo *trackInfo); extern OSStatus PlayerGetITTrackInfoSize(void *appCookie, ITAppProcPtr appProc, UInt32 appPluginMajorVersion, UInt32 appPluginMinorVersion, UInt32 *itTrackInfoSize); extern OSStatus PlayerGetPluginITFileSpec(void *appCookie, ITAppProcPtr appProc, ITFileSpec *pluginFileSpec); /* iTunes APIs For visual plugins */ enum { kPlayerSetFullScreenMessage = 'sful', /* Set full screen mode */ kPlayerSetFullScreenOptionsMessage = 'sfop', /* Set full screen options */ /* Request the current player track cover artwork. It will be returned asynchronously via kVisualPluginCoverArtworkMessage. */ kPlayerRequestCurrentTrackCoverArtMessage = 'rart' }; extern OSStatus PlayerRegisterVisualPlugin(void *appCookie, ITAppProcPtr appProc, PlayerMessageInfo *messageInfo); extern OSStatus PlayerSetFullScreen(void *appCookie, ITAppProcPtr appProc, Boolean fullScreen); extern OSStatus PlayerSetFullScreenOptions(void *appCookie, ITAppProcPtr appProc, SInt16 minBitDepth, SInt16 maxBitDepth, SInt16 preferredBitDepth, SInt16 desiredWidth, SInt16 desiredHeight); extern OSStatus PlayerRequestCurrentTrackCoverArt(void *appCookie, ITAppProcPtr appProc); #if PRAGMA_STRUCT_ALIGN #pragma options align=reset #elif PRAGMA_STRUCT_PACKPUSH #pragma pack(pop) #elif PRAGMA_STRUCT_PACK #pragma pack() #endif #ifdef __cplusplus } #endif #endif /* ITUNESAPI_H_ */ ================================================ FILE: plugins/iTunes/iTunesVisualAPI/iTunesVisualAPI.h ================================================ // // File: iTunesVisualAPI.h // // Abstract: part of iTunes Visual SDK // // Version: 2.0 // // Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ( "Apple" ) // in consideration of your agreement to the following terms, and your use, // installation, modification or redistribution of this Apple software // constitutes acceptance of these terms. If you do not agree with these // terms, please do not use, install, modify or redistribute this Apple // software. // // In consideration of your agreement to abide by the following terms, and // subject to these terms, Apple grants you a personal, non - exclusive // license, under Apple's copyrights in this original Apple software ( the // "Apple Software" ), to use, reproduce, modify and redistribute the Apple // Software, with or without modifications, in source and / or binary forms; // provided that if you redistribute the Apple Software in its entirety and // without modifications, you must retain this notice and the following text // and disclaimers in all such redistributions of the Apple Software. Neither // the name, trademarks, service marks or logos of Apple Inc. may be used to // endorse or promote products derived from the Apple Software without specific // prior written permission from Apple. Except as expressly stated in this // notice, no other rights or licenses, express or implied, are granted by // Apple herein, including but not limited to any patent rights that may be // infringed by your derivative works or by other works in which the Apple // Software may be incorporated. // // The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO // WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED // WARRANTIES OF NON - INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION // ALONE OR IN COMBINATION WITH YOUR PRODUCTS. // // IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR // CONSEQUENTIAL DAMAGES ( INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION ) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION // AND / OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER // UNDER THEORY OF CONTRACT, TORT ( INCLUDING NEGLIGENCE ), STRICT LIABILITY OR // OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Copyright © 2000-2011 Apple Inc. All Rights Reserved. // #ifndef ITUNESVISUALAPI_H_ #define ITUNESVISUALAPI_H_ #include "iTunesAPI.h" #if PRAGMA_ONCE #pragma once #endif #ifdef __cplusplus extern "C" { #endif #if PRAGMA_STRUCT_ALIGN #pragma options align=power #elif PRAGMA_STRUCT_PACKPUSH #pragma pack(push, 4) #elif PRAGMA_STRUCT_PACK #pragma pack(4) #endif enum { kCurrentITStreamInfoVersion = 1 }; enum { kITVisualPluginMajorMessageVersion = 10, kITVisualPluginMinorMessageVersion = 7 }; enum { /* VisualPlugin messages */ kVisualPluginInitMessage = 'init', kVisualPluginCleanupMessage = 'clr ', kVisualPluginIdleMessage = 'null', kVisualPluginConfigureMessage = 'cnfg', /* Configure the plugin. */ kVisualPluginEnableMessage = 'von ', /* Enable the plugin and make it available to the user (automatic). */ kVisualPluginDisableMessage = 'voff', /* Disable the plugin. */ kVisualPluginActivateMessage = 'Vact', /* Visualizer is being shown on screen (allocate large memory here) */ kVisualPluginWindowChangedMessage = 'Vmov', /* The visualizer context was moved to a new window. A draw message will be sent immediately. */ kVisualPluginDeactivateMessage = 'Vdct', /* Visualizer is being removed from the screen (deallocate large memory here) */ kVisualPluginPulseMessage = 'Vpls', /* Sent at the rate requested during plugin registration. Contains new data if currently playing audio. */ kVisualPluginDrawMessage = 'Vdrw', /* Draw a new frame. Sent when the OS decides to repaint the backing view. */ kVisualPluginFrameChangedMessage = 'Vfrm', /* The visualizer area resized. A draw message will be sent immediately. */ kVisualPluginPlayMessage = 'Vply', /* Starting playback. */ kVisualPluginChangeTrackMessage = 'Ctrk', /* Current track changed or info about the current track has changed. */ kVisualPluginSetPositionMessage = 'setp', /* Setting the position of the currently playing track. */ kVisualPluginStopMessage = 'vstp', /* Stopping playback. */ kVisualPluginCoverArtMessage = 'Vart', /* Delivers the current track artwork as requested by the plugin. Plugin must retain/copy it if it wants to keep it. */ kVisualPluginDisplayChangedMessage = 'dchn' /* Something about display state changed. */ }; /* VisualPlugin messages */ enum { kVisualMaxDataChannels = 2, kVisualNumWaveformEntries = 512, kVisualNumSpectrumEntries = 512 }; enum { /* CoverArt format types */ kVisualCoverArtFormatJPEG = 13, kVisualCoverArtFormatPNG = 14, kVisualCoverArtFormatBMP = 27 }; enum { /* Activate options */ kWindowIsFullScreen = (1u << 0) }; struct RenderVisualData { UInt8 numWaveformChannels; UInt8 waveformData[kVisualMaxDataChannels][kVisualNumWaveformEntries]; UInt8 numSpectrumChannels; UInt8 spectrumData[kVisualMaxDataChannels][kVisualNumSpectrumEntries]; }; typedef struct RenderVisualData RenderVisualData; struct VisualPluginInitMessage { UInt32 messageMajorVersion; /* Input */ UInt32 messageMinorVersion; /* Input */ NumVersion appVersion; /* Input */ void * appCookie; /* Input */ ITAppProcPtr appProc; /* Input */ OptionBits unused; /* N/A */ void * refCon; /* Output */ }; typedef struct VisualPluginInitMessage VisualPluginInitMessage; struct VisualPluginActivateMessage { VISUAL_PLATFORM_VIEW view; /* Input - plugin should draw in entire bounds */ OptionBits options; /* Input */ }; typedef struct VisualPluginActivateMessage VisualPluginActivateMessage; struct VisualPluginWindowChangedMessage { OptionBits options; /* Input */ }; typedef struct VisualPluginWindowChangedMessage VisualPluginWindowChangedMessage; struct VisualPluginPulseMessage { RenderVisualData * renderData; /* Input */ UInt32 timeStampID; /* Input */ UInt32 currentPositionInMS; /* Input */ UInt32 newPulseRateInHz; /* Input/Output - contains current rate on input, modify it to get a new rate. */ }; typedef struct VisualPluginPulseMessage VisualPluginPulseMessage; struct VisualPluginPlayMessage { ITTrackInfo * trackInfo; /* Input */ ITStreamInfo * streamInfo; /* Input */ AudioStreamBasicDescription audioFormat; /* Input */ UInt32 bitRate; /* Input */ SInt32 volume; /* Input */ }; typedef struct VisualPluginPlayMessage VisualPluginPlayMessage; struct VisualPluginChangeTrackMessage { ITTrackInfo * trackInfo; /* Input */ ITStreamInfo * streamInfo; /* Input */ }; typedef struct VisualPluginChangeTrackMessage VisualPluginChangeTrackMessage; struct VisualPluginCoverArtMessage { VISUAL_PLATFORM_DATA coverArt; /* input - client must retain (mac) or copy (windows) the data if they want to keep it after this message completes. - note that coverArt will be NULL if the current track has no artwork */ UInt32 coverArtSize; /* input - size of the coverArt in bytes */ UInt32 coverArtFormat; /* input - format of cover art */ }; typedef struct VisualPluginCoverArtMessage VisualPluginCoverArtMessage; struct VisualPluginSetPositionMessage { UInt32 positionTimeInMS; /* Input */ }; typedef struct VisualPluginSetPositionMessage VisualPluginSetPositionMessage; enum { kVisualDisplayDepthChanged = 1 << 0, /* the display's depth has changed */ kVisualDisplayRectChanged = 1 << 1, /* the display's location changed */ kVisualWindowMoved = 1 << 2, /* the window has moved location */ kVisualDisplayConfigChanged = 1 << 3, /* something else about the display changed */ }; struct VisualPluginDisplayChangedMessage { UInt32 flags; /* Input */ }; typedef struct VisualPluginDisplayChangedMessage VisualPluginDisplayChangedMessage; struct VisualPluginMessageInfo { union { VisualPluginInitMessage initMessage; VisualPluginActivateMessage activateMessage; VisualPluginWindowChangedMessage windowChangedMessage; VisualPluginPulseMessage pulseMessage; VisualPluginPlayMessage playMessage; VisualPluginChangeTrackMessage changeTrackMessage; VisualPluginSetPositionMessage setPositionMessage; VisualPluginCoverArtMessage coverArtMessage; VisualPluginDisplayChangedMessage displayChangedMessage; } u; }; typedef struct VisualPluginMessageInfo VisualPluginMessageInfo; #if PRAGMA_STRUCT_ALIGN #pragma options align=reset #elif PRAGMA_STRUCT_PACKPUSH #pragma pack(pop) #elif PRAGMA_STRUCT_PACK #pragma pack() #endif #ifdef __cplusplus } #endif #endif /* ITUNESVISUALAPI_H_ */ ================================================ FILE: plugins/iTunes/libs/sqlite3.c ================================================ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite ** version 3.7.14.1. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements ** of 5% or more are commonly seen when SQLite is compiled as a single ** translation unit. ** ** This file is all you need to compile SQLite. To use SQLite in other ** programs, you need this file and the "sqlite3.h" header file that defines ** the programming interface to the SQLite library. (If you do not have ** the "sqlite3.h" header file at hand, you will find a copy embedded within ** the text of this file. Search for "Begin file sqlite3.h" to find the start ** of the embedded sqlite3.h header file.) Additional code files may be needed ** if you want a wrapper to interface SQLite with your choice of programming ** language. The code for the "sqlite3" command-line shell is also in a ** separate file. This file contains only code for the core SQLite library. */ #define SQLITE_CORE 1 #define SQLITE_AMALGAMATION 1 #ifndef SQLITE_PRIVATE # define SQLITE_PRIVATE static #endif #ifndef SQLITE_API # define SQLITE_API #endif /************** Begin file sqliteInt.h ***************************************/ /* ** 2001 September 15 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** Internal interface definitions for SQLite. ** */ #ifndef _SQLITEINT_H_ #define _SQLITEINT_H_ /* ** These #defines should enable >2GB file support on POSIX if the ** underlying operating system supports it. If the OS lacks ** large file support, or if the OS is windows, these should be no-ops. ** ** Ticket #2739: The _LARGEFILE_SOURCE macro must appear before any ** system #includes. Hence, this block of code must be the very first ** code in all source files. ** ** Large file support can be disabled using the -DSQLITE_DISABLE_LFS switch ** on the compiler command line. This is necessary if you are compiling ** on a recent machine (ex: Red Hat 7.2) but you want your code to work ** on an older machine (ex: Red Hat 6.0). If you compile on Red Hat 7.2 ** without this option, LFS is enable. But LFS does not exist in the kernel ** in Red Hat 6.0, so the code won't work. Hence, for maximum binary ** portability you should omit LFS. ** ** Similar is true for Mac OS X. LFS is only supported on Mac OS X 9 and later. */ #ifndef SQLITE_DISABLE_LFS # define _LARGE_FILE 1 # ifndef _FILE_OFFSET_BITS # define _FILE_OFFSET_BITS 64 # endif # define _LARGEFILE_SOURCE 1 #endif /* ** Include the configuration header output by 'configure' if we're using the ** autoconf-based build */ #ifdef _HAVE_SQLITE_CONFIG_H #include "config.h" #endif /************** Include sqliteLimit.h in the middle of sqliteInt.h ***********/ /************** Begin file sqliteLimit.h *************************************/ /* ** 2007 May 7 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** ** This file defines various limits of what SQLite can process. */ /* ** The maximum length of a TEXT or BLOB in bytes. This also ** limits the size of a row in a table or index. ** ** The hard limit is the ability of a 32-bit signed integer ** to count the size: 2^31-1 or 2147483647. */ #ifndef SQLITE_MAX_LENGTH # define SQLITE_MAX_LENGTH 1000000000 #endif /* ** This is the maximum number of ** ** * Columns in a table ** * Columns in an index ** * Columns in a view ** * Terms in the SET clause of an UPDATE statement ** * Terms in the result set of a SELECT statement ** * Terms in the GROUP BY or ORDER BY clauses of a SELECT statement. ** * Terms in the VALUES clause of an INSERT statement ** ** The hard upper limit here is 32676. Most database people will ** tell you that in a well-normalized database, you usually should ** not have more than a dozen or so columns in any table. And if ** that is the case, there is no point in having more than a few ** dozen values in any of the other situations described above. */ #ifndef SQLITE_MAX_COLUMN # define SQLITE_MAX_COLUMN 2000 #endif /* ** The maximum length of a single SQL statement in bytes. ** ** It used to be the case that setting this value to zero would ** turn the limit off. That is no longer true. It is not possible ** to turn this limit off. */ #ifndef SQLITE_MAX_SQL_LENGTH # define SQLITE_MAX_SQL_LENGTH 1000000000 #endif /* ** The maximum depth of an expression tree. This is limited to ** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might ** want to place more severe limits on the complexity of an ** expression. ** ** A value of 0 used to mean that the limit was not enforced. ** But that is no longer true. The limit is now strictly enforced ** at all times. */ #ifndef SQLITE_MAX_EXPR_DEPTH # define SQLITE_MAX_EXPR_DEPTH 1000 #endif /* ** The maximum number of terms in a compound SELECT statement. ** The code generator for compound SELECT statements does one ** level of recursion for each term. A stack overflow can result ** if the number of terms is too large. In practice, most SQL ** never has more than 3 or 4 terms. Use a value of 0 to disable ** any limit on the number of terms in a compount SELECT. */ #ifndef SQLITE_MAX_COMPOUND_SELECT # define SQLITE_MAX_COMPOUND_SELECT 500 #endif /* ** The maximum number of opcodes in a VDBE program. ** Not currently enforced. */ #ifndef SQLITE_MAX_VDBE_OP # define SQLITE_MAX_VDBE_OP 25000 #endif /* ** The maximum number of arguments to an SQL function. */ #ifndef SQLITE_MAX_FUNCTION_ARG # define SQLITE_MAX_FUNCTION_ARG 127 #endif /* ** The maximum number of in-memory pages to use for the main database ** table and for temporary tables. The SQLITE_DEFAULT_CACHE_SIZE */ #ifndef SQLITE_DEFAULT_CACHE_SIZE # define SQLITE_DEFAULT_CACHE_SIZE 2000 #endif #ifndef SQLITE_DEFAULT_TEMP_CACHE_SIZE # define SQLITE_DEFAULT_TEMP_CACHE_SIZE 500 #endif /* ** The default number of frames to accumulate in the log file before ** checkpointing the database in WAL mode. */ #ifndef SQLITE_DEFAULT_WAL_AUTOCHECKPOINT # define SQLITE_DEFAULT_WAL_AUTOCHECKPOINT 1000 #endif /* ** The maximum number of attached databases. This must be between 0 ** and 62. The upper bound on 62 is because a 64-bit integer bitmap ** is used internally to track attached databases. */ #ifndef SQLITE_MAX_ATTACHED # define SQLITE_MAX_ATTACHED 10 #endif /* ** The maximum value of a ?nnn wildcard that the parser will accept. */ #ifndef SQLITE_MAX_VARIABLE_NUMBER # define SQLITE_MAX_VARIABLE_NUMBER 999 #endif /* Maximum page size. The upper bound on this value is 65536. This a limit ** imposed by the use of 16-bit offsets within each page. ** ** Earlier versions of SQLite allowed the user to change this value at ** compile time. This is no longer permitted, on the grounds that it creates ** a library that is technically incompatible with an SQLite library ** compiled with a different limit. If a process operating on a database ** with a page-size of 65536 bytes crashes, then an instance of SQLite ** compiled with the default page-size limit will not be able to rollback ** the aborted transaction. This could lead to database corruption. */ #ifdef SQLITE_MAX_PAGE_SIZE # undef SQLITE_MAX_PAGE_SIZE #endif #define SQLITE_MAX_PAGE_SIZE 65536 /* ** The default size of a database page. */ #ifndef SQLITE_DEFAULT_PAGE_SIZE # define SQLITE_DEFAULT_PAGE_SIZE 1024 #endif #if SQLITE_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE # undef SQLITE_DEFAULT_PAGE_SIZE # define SQLITE_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE #endif /* ** Ordinarily, if no value is explicitly provided, SQLite creates databases ** with page size SQLITE_DEFAULT_PAGE_SIZE. However, based on certain ** device characteristics (sector-size and atomic write() support), ** SQLite may choose a larger value. This constant is the maximum value ** SQLite will choose on its own. */ #ifndef SQLITE_MAX_DEFAULT_PAGE_SIZE # define SQLITE_MAX_DEFAULT_PAGE_SIZE 8192 #endif #if SQLITE_MAX_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE # undef SQLITE_MAX_DEFAULT_PAGE_SIZE # define SQLITE_MAX_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE #endif /* ** Maximum number of pages in one database file. ** ** This is really just the default value for the max_page_count pragma. ** This value can be lowered (or raised) at run-time using that the ** max_page_count macro. */ #ifndef SQLITE_MAX_PAGE_COUNT # define SQLITE_MAX_PAGE_COUNT 1073741823 #endif /* ** Maximum length (in bytes) of the pattern in a LIKE or GLOB ** operator. */ #ifndef SQLITE_MAX_LIKE_PATTERN_LENGTH # define SQLITE_MAX_LIKE_PATTERN_LENGTH 50000 #endif /* ** Maximum depth of recursion for triggers. ** ** A value of 1 means that a trigger program will not be able to itself ** fire any triggers. A value of 0 means that no trigger programs at all ** may be executed. */ #ifndef SQLITE_MAX_TRIGGER_DEPTH # define SQLITE_MAX_TRIGGER_DEPTH 1000 #endif /************** End of sqliteLimit.h *****************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /* Disable nuisance warnings on Borland compilers */ #if defined(__BORLANDC__) #pragma warn -rch /* unreachable code */ #pragma warn -ccc /* Condition is always true or false */ #pragma warn -aus /* Assigned value is never used */ #pragma warn -csu /* Comparing signed and unsigned */ #pragma warn -spa /* Suspicious pointer arithmetic */ #endif /* Needed for various definitions... */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif /* ** Include standard header files as necessary */ #ifdef HAVE_STDINT_H #include #endif #ifdef HAVE_INTTYPES_H #include #endif /* ** The following macros are used to cast pointers to integers and ** integers to pointers. The way you do this varies from one compiler ** to the next, so we have developed the following set of #if statements ** to generate appropriate macros for a wide range of compilers. ** ** The correct "ANSI" way to do this is to use the intptr_t type. ** Unfortunately, that typedef is not available on all compilers, or ** if it is available, it requires an #include of specific headers ** that vary from one machine to the next. ** ** Ticket #3860: The llvm-gcc-4.2 compiler from Apple chokes on ** the ((void*)&((char*)0)[X]) construct. But MSVC chokes on ((void*)(X)). ** So we have to define the macros in different ways depending on the ** compiler. */ #if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */ # define SQLITE_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) # define SQLITE_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X)) #elif !defined(__GNUC__) /* Works for compilers other than LLVM */ # define SQLITE_INT_TO_PTR(X) ((void*)&((char*)0)[X]) # define SQLITE_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) #elif defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */ # define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X)) # define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X)) #else /* Generates a warning - but it always works */ # define SQLITE_INT_TO_PTR(X) ((void*)(X)) # define SQLITE_PTR_TO_INT(X) ((int)(X)) #endif /* ** The SQLITE_THREADSAFE macro must be defined as 0, 1, or 2. ** 0 means mutexes are permanently disable and the library is never ** threadsafe. 1 means the library is serialized which is the highest ** level of threadsafety. 2 means the libary is multithreaded - multiple ** threads can use SQLite as long as no two threads try to use the same ** database connection at the same time. ** ** Older versions of SQLite used an optional THREADSAFE macro. ** We support that for legacy. */ #if !defined(SQLITE_THREADSAFE) #if defined(THREADSAFE) # define SQLITE_THREADSAFE THREADSAFE #else # define SQLITE_THREADSAFE 1 /* IMP: R-07272-22309 */ #endif #endif /* ** Powersafe overwrite is on by default. But can be turned off using ** the -DSQLITE_POWERSAFE_OVERWRITE=0 command-line option. */ #ifndef SQLITE_POWERSAFE_OVERWRITE # define SQLITE_POWERSAFE_OVERWRITE 1 #endif /* ** The SQLITE_DEFAULT_MEMSTATUS macro must be defined as either 0 or 1. ** It determines whether or not the features related to ** SQLITE_CONFIG_MEMSTATUS are available by default or not. This value can ** be overridden at runtime using the sqlite3_config() API. */ #if !defined(SQLITE_DEFAULT_MEMSTATUS) # define SQLITE_DEFAULT_MEMSTATUS 1 #endif /* ** Exactly one of the following macros must be defined in order to ** specify which memory allocation subsystem to use. ** ** SQLITE_SYSTEM_MALLOC // Use normal system malloc() ** SQLITE_WIN32_MALLOC // Use Win32 native heap API ** SQLITE_ZERO_MALLOC // Use a stub allocator that always fails ** SQLITE_MEMDEBUG // Debugging version of system malloc() ** ** On Windows, if the SQLITE_WIN32_MALLOC_VALIDATE macro is defined and the ** assert() macro is enabled, each call into the Win32 native heap subsystem ** will cause HeapValidate to be called. If heap validation should fail, an ** assertion will be triggered. ** ** (Historical note: There used to be several other options, but we've ** pared it down to just these three.) ** ** If none of the above are defined, then set SQLITE_SYSTEM_MALLOC as ** the default. */ #if defined(SQLITE_SYSTEM_MALLOC) \ + defined(SQLITE_WIN32_MALLOC) \ + defined(SQLITE_ZERO_MALLOC) \ + defined(SQLITE_MEMDEBUG)>1 # error "Two or more of the following compile-time configuration options\ are defined but at most one is allowed:\ SQLITE_SYSTEM_MALLOC, SQLITE_WIN32_MALLOC, SQLITE_MEMDEBUG,\ SQLITE_ZERO_MALLOC" #endif #if defined(SQLITE_SYSTEM_MALLOC) \ + defined(SQLITE_WIN32_MALLOC) \ + defined(SQLITE_ZERO_MALLOC) \ + defined(SQLITE_MEMDEBUG)==0 # define SQLITE_SYSTEM_MALLOC 1 #endif /* ** If SQLITE_MALLOC_SOFT_LIMIT is not zero, then try to keep the ** sizes of memory allocations below this value where possible. */ #if !defined(SQLITE_MALLOC_SOFT_LIMIT) # define SQLITE_MALLOC_SOFT_LIMIT 1024 #endif /* ** We need to define _XOPEN_SOURCE as follows in order to enable ** recursive mutexes on most Unix systems. But Mac OS X is different. ** The _XOPEN_SOURCE define causes problems for Mac OS X we are told, ** so it is omitted there. See ticket #2673. ** ** Later we learn that _XOPEN_SOURCE is poorly or incorrectly ** implemented on some systems. So we avoid defining it at all ** if it is already defined or if it is unneeded because we are ** not doing a threadsafe build. Ticket #2681. ** ** See also ticket #2741. */ #if !defined(_XOPEN_SOURCE) && !defined(__DARWIN__) && !defined(__APPLE__) && SQLITE_THREADSAFE # define _XOPEN_SOURCE 500 /* Needed to enable pthread recursive mutexes */ #endif /* ** The TCL headers are only needed when compiling the TCL bindings. */ #if defined(SQLITE_TCL) || defined(TCLSH) # include #endif /* ** NDEBUG and SQLITE_DEBUG are opposites. It should always be true that ** defined(NDEBUG)==!defined(SQLITE_DEBUG). If this is not currently true, ** make it true by defining or undefining NDEBUG. ** ** Setting NDEBUG makes the code smaller and run faster by disabling the ** number assert() statements in the code. So we want the default action ** to be for NDEBUG to be set and NDEBUG to be undefined only if SQLITE_DEBUG ** is set. Thus NDEBUG becomes an opt-in rather than an opt-out ** feature. */ #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) # define NDEBUG 1 #endif #if defined(NDEBUG) && defined(SQLITE_DEBUG) # undef NDEBUG #endif /* ** The testcase() macro is used to aid in coverage testing. When ** doing coverage testing, the condition inside the argument to ** testcase() must be evaluated both true and false in order to ** get full branch coverage. The testcase() macro is inserted ** to help ensure adequate test coverage in places where simple ** condition/decision coverage is inadequate. For example, testcase() ** can be used to make sure boundary values are tested. For ** bitmask tests, testcase() can be used to make sure each bit ** is significant and used at least once. On switch statements ** where multiple cases go to the same block of code, testcase() ** can insure that all cases are evaluated. ** */ #ifdef SQLITE_COVERAGE_TEST SQLITE_PRIVATE void sqlite3Coverage(int); # define testcase(X) if( X ){ sqlite3Coverage(__LINE__); } #else # define testcase(X) #endif /* ** The TESTONLY macro is used to enclose variable declarations or ** other bits of code that are needed to support the arguments ** within testcase() and assert() macros. */ #if !defined(NDEBUG) || defined(SQLITE_COVERAGE_TEST) # define TESTONLY(X) X #else # define TESTONLY(X) #endif /* ** Sometimes we need a small amount of code such as a variable initialization ** to setup for a later assert() statement. We do not want this code to ** appear when assert() is disabled. The following macro is therefore ** used to contain that setup code. The "VVA" acronym stands for ** "Verification, Validation, and Accreditation". In other words, the ** code within VVA_ONLY() will only run during verification processes. */ #ifndef NDEBUG # define VVA_ONLY(X) X #else # define VVA_ONLY(X) #endif /* ** The ALWAYS and NEVER macros surround boolean expressions which ** are intended to always be true or false, respectively. Such ** expressions could be omitted from the code completely. But they ** are included in a few cases in order to enhance the resilience ** of SQLite to unexpected behavior - to make the code "self-healing" ** or "ductile" rather than being "brittle" and crashing at the first ** hint of unplanned behavior. ** ** In other words, ALWAYS and NEVER are added for defensive code. ** ** When doing coverage testing ALWAYS and NEVER are hard-coded to ** be true and false so that the unreachable code then specify will ** not be counted as untested code. */ #if defined(SQLITE_COVERAGE_TEST) # define ALWAYS(X) (1) # define NEVER(X) (0) #elif !defined(NDEBUG) # define ALWAYS(X) ((X)?1:(assert(0),0)) # define NEVER(X) ((X)?(assert(0),1):0) #else # define ALWAYS(X) (X) # define NEVER(X) (X) #endif /* ** Return true (non-zero) if the input is a integer that is too large ** to fit in 32-bits. This macro is used inside of various testcase() ** macros to verify that we have tested SQLite for large-file support. */ #define IS_BIG_INT(X) (((X)&~(i64)0xffffffff)!=0) /* ** The macro unlikely() is a hint that surrounds a boolean ** expression that is usually false. Macro likely() surrounds ** a boolean expression that is usually true. GCC is able to ** use these hints to generate better code, sometimes. */ #if defined(__GNUC__) && 0 # define likely(X) __builtin_expect((X),1) # define unlikely(X) __builtin_expect((X),0) #else # define likely(X) !!(X) # define unlikely(X) !!(X) #endif /************** Include sqlite3.h in the middle of sqliteInt.h ***************/ /************** Begin file sqlite3.h *****************************************/ /* ** 2001 September 15 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This header file defines the interface that the SQLite library ** presents to client programs. If a C-function, structure, datatype, ** or constant definition does not appear in this file, then it is ** not a published API of SQLite, is subject to change without ** notice, and should not be referenced by programs that use SQLite. ** ** Some of the definitions that are in this file are marked as ** "experimental". Experimental interfaces are normally new ** features recently added to SQLite. We do not anticipate changes ** to experimental interfaces but reserve the right to make minor changes ** if experience from use "in the wild" suggest such changes are prudent. ** ** The official C-language API documentation for SQLite is derived ** from comments in this file. This file is the authoritative source ** on how SQLite interfaces are suppose to operate. ** ** The name of this file under configuration management is "sqlite.h.in". ** The makefile makes some minor changes to this file (such as inserting ** the version number) and changes its name to "sqlite3.h" as ** part of the build process. */ #ifndef _SQLITE3_H_ #define _SQLITE3_H_ #include /* Needed for the definition of va_list */ /* ** Make sure we can call this stuff from C++. */ #if 0 extern "C" { #endif /* ** Add the ability to override 'extern' */ #ifndef SQLITE_EXTERN # define SQLITE_EXTERN extern #endif #ifndef SQLITE_API # define SQLITE_API #endif /* ** These no-op macros are used in front of interfaces to mark those ** interfaces as either deprecated or experimental. New applications ** should not use deprecated interfaces - they are support for backwards ** compatibility only. Application writers should be aware that ** experimental interfaces are subject to change in point releases. ** ** These macros used to resolve to various kinds of compiler magic that ** would generate warning messages when they were used. But that ** compiler magic ended up generating such a flurry of bug reports ** that we have taken it all out and gone back to using simple ** noop macros. */ #define SQLITE_DEPRECATED #define SQLITE_EXPERIMENTAL /* ** Ensure these symbols were not defined by some previous header file. */ #ifdef SQLITE_VERSION # undef SQLITE_VERSION #endif #ifdef SQLITE_VERSION_NUMBER # undef SQLITE_VERSION_NUMBER #endif /* ** CAPI3REF: Compile-Time Library Version Numbers ** ** ^(The [SQLITE_VERSION] C preprocessor macro in the sqlite3.h header ** evaluates to a string literal that is the SQLite version in the ** format "X.Y.Z" where X is the major version number (always 3 for ** SQLite3) and Y is the minor version number and Z is the release number.)^ ** ^(The [SQLITE_VERSION_NUMBER] C preprocessor macro resolves to an integer ** with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same ** numbers used in [SQLITE_VERSION].)^ ** The SQLITE_VERSION_NUMBER for any given release of SQLite will also ** be larger than the release from which it is derived. Either Y will ** be held constant and Z will be incremented or else Y will be incremented ** and Z will be reset to zero. ** ** Since version 3.6.18, SQLite source code has been stored in the ** Fossil configuration management ** system. ^The SQLITE_SOURCE_ID macro evaluates to ** a string which identifies a particular check-in of SQLite ** within its configuration management system. ^The SQLITE_SOURCE_ID ** string contains the date and time of the check-in (UTC) and an SHA1 ** hash of the entire source tree. ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ #define SQLITE_VERSION "3.7.14.1" #define SQLITE_VERSION_NUMBER 3007014 #define SQLITE_SOURCE_ID "2012-10-04 19:37:12 091570e46d04e84b67228e0bdbcd6e1fb60c6bdb" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version, sqlite3_sourceid ** ** These interfaces provide the same information as the [SQLITE_VERSION], ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros ** but are associated with the library instead of the header file. ^(Cautious ** programmers might include assert() statements in their application to ** verify that values returned by these interfaces match the macros in ** the header, and thus insure that the application is ** compiled with matching library and header files. ** **
    ** assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
    ** assert( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)==0 );
    ** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
    ** 
    )^ ** ** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION] ** macro. ^The sqlite3_libversion() function returns a pointer to the ** to the sqlite3_version[] string constant. The sqlite3_libversion() ** function is provided for use in DLLs since DLL users usually do not have ** direct access to string constants within the DLL. ^The ** sqlite3_libversion_number() function returns an integer equal to ** [SQLITE_VERSION_NUMBER]. ^The sqlite3_sourceid() function returns ** a pointer to a string constant whose value is the same as the ** [SQLITE_SOURCE_ID] C preprocessor macro. ** ** See also: [sqlite_version()] and [sqlite_source_id()]. */ SQLITE_API const char sqlite3_version[] = SQLITE_VERSION; SQLITE_API const char *sqlite3_libversion(void); SQLITE_API const char *sqlite3_sourceid(void); SQLITE_API int sqlite3_libversion_number(void); /* ** CAPI3REF: Run-Time Library Compilation Options Diagnostics ** ** ^The sqlite3_compileoption_used() function returns 0 or 1 ** indicating whether the specified option was defined at ** compile time. ^The SQLITE_ prefix may be omitted from the ** option name passed to sqlite3_compileoption_used(). ** ** ^The sqlite3_compileoption_get() function allows iterating ** over the list of options that were defined at compile time by ** returning the N-th compile time option string. ^If N is out of range, ** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ ** prefix is omitted from any strings returned by ** sqlite3_compileoption_get(). ** ** ^Support for the diagnostic functions sqlite3_compileoption_used() ** and sqlite3_compileoption_get() may be omitted by specifying the ** [SQLITE_OMIT_COMPILEOPTION_DIAGS] option at compile time. ** ** See also: SQL functions [sqlite_compileoption_used()] and ** [sqlite_compileoption_get()] and the [compile_options pragma]. */ #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS SQLITE_API int sqlite3_compileoption_used(const char *zOptName); SQLITE_API const char *sqlite3_compileoption_get(int N); #endif /* ** CAPI3REF: Test To See If The Library Is Threadsafe ** ** ^The sqlite3_threadsafe() function returns zero if and only if ** SQLite was compiled with mutexing code omitted due to the ** [SQLITE_THREADSAFE] compile-time option being set to 0. ** ** SQLite can be compiled with or without mutexes. When ** the [SQLITE_THREADSAFE] C preprocessor macro is 1 or 2, mutexes ** are enabled and SQLite is threadsafe. When the ** [SQLITE_THREADSAFE] macro is 0, ** the mutexes are omitted. Without the mutexes, it is not safe ** to use SQLite concurrently from more than one thread. ** ** Enabling mutexes incurs a measurable performance penalty. ** So if speed is of utmost importance, it makes sense to disable ** the mutexes. But for maximum safety, mutexes should be enabled. ** ^The default behavior is for mutexes to be enabled. ** ** This interface can be used by an application to make sure that the ** version of SQLite that it is linking against was compiled with ** the desired setting of the [SQLITE_THREADSAFE] macro. ** ** This interface only reports on the compile-time mutex setting ** of the [SQLITE_THREADSAFE] flag. If SQLite is compiled with ** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but ** can be fully or partially disabled using a call to [sqlite3_config()] ** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD], ** or [SQLITE_CONFIG_MUTEX]. ^(The return value of the ** sqlite3_threadsafe() function shows only the compile-time setting of ** thread safety, not any run-time changes to that setting made by ** sqlite3_config(). In other words, the return value from sqlite3_threadsafe() ** is unchanged by calls to sqlite3_config().)^ ** ** See the [threading mode] documentation for additional information. */ SQLITE_API int sqlite3_threadsafe(void); /* ** CAPI3REF: Database Connection Handle ** KEYWORDS: {database connection} {database connections} ** ** Each open SQLite database is represented by a pointer to an instance of ** the opaque structure named "sqlite3". It is useful to think of an sqlite3 ** pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and ** [sqlite3_open_v2()] interfaces are its constructors, and [sqlite3_close()] ** and [sqlite3_close_v2()] are its destructors. There are many other ** interfaces (such as ** [sqlite3_prepare_v2()], [sqlite3_create_function()], and ** [sqlite3_busy_timeout()] to name but three) that are methods on an ** sqlite3 object. */ typedef struct sqlite3 sqlite3; /* ** CAPI3REF: 64-Bit Integer Types ** KEYWORDS: sqlite_int64 sqlite_uint64 ** ** Because there is no cross-platform way to specify 64-bit integer types ** SQLite includes typedefs for 64-bit signed and unsigned integers. ** ** The sqlite3_int64 and sqlite3_uint64 are the preferred type definitions. ** The sqlite_int64 and sqlite_uint64 types are supported for backwards ** compatibility only. ** ** ^The sqlite3_int64 and sqlite_int64 types can store integer values ** between -9223372036854775808 and +9223372036854775807 inclusive. ^The ** sqlite3_uint64 and sqlite_uint64 types can store integer values ** between 0 and +18446744073709551615 inclusive. */ #ifdef SQLITE_INT64_TYPE typedef SQLITE_INT64_TYPE sqlite_int64; typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; #elif defined(_MSC_VER) || defined(__BORLANDC__) typedef __int64 sqlite_int64; typedef unsigned __int64 sqlite_uint64; #else typedef long long int sqlite_int64; typedef unsigned long long int sqlite_uint64; #endif typedef sqlite_int64 sqlite3_int64; typedef sqlite_uint64 sqlite3_uint64; /* ** If compiling for a processor that lacks floating point support, ** substitute integer for floating-point. */ #ifdef SQLITE_OMIT_FLOATING_POINT # define double sqlite3_int64 #endif /* ** CAPI3REF: Closing A Database Connection ** ** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors ** for the [sqlite3] object. ** ^Calls to sqlite3_close() and sqlite3_close_v2() return SQLITE_OK if ** the [sqlite3] object is successfully destroyed and all associated ** resources are deallocated. ** ** ^If the database connection is associated with unfinalized prepared ** statements or unfinished sqlite3_backup objects then sqlite3_close() ** will leave the database connection open and return [SQLITE_BUSY]. ** ^If sqlite3_close_v2() is called with unfinalized prepared statements ** and unfinished sqlite3_backups, then the database connection becomes ** an unusable "zombie" which will automatically be deallocated when the ** last prepared statement is finalized or the last sqlite3_backup is ** finished. The sqlite3_close_v2() interface is intended for use with ** host languages that are garbage collected, and where the order in which ** destructors are called is arbitrary. ** ** Applications should [sqlite3_finalize | finalize] all [prepared statements], ** [sqlite3_blob_close | close] all [BLOB handles], and ** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated ** with the [sqlite3] object prior to attempting to close the object. ^If ** sqlite3_close() is called on a [database connection] that still has ** outstanding [prepared statements], [BLOB handles], and/or ** [sqlite3_backup] objects then it returns SQLITE_OK but the deallocation ** of resources is deferred until all [prepared statements], [BLOB handles], ** and [sqlite3_backup] objects are also destroyed. ** ** ^If an [sqlite3] object is destroyed while a transaction is open, ** the transaction is automatically rolled back. ** ** The C parameter to [sqlite3_close(C)] and [sqlite3_close_v2(C)] ** must be either a NULL ** pointer or an [sqlite3] object pointer obtained ** from [sqlite3_open()], [sqlite3_open16()], or ** [sqlite3_open_v2()], and not previously closed. ** ^Calling sqlite3_close() or sqlite3_close_v2() with a NULL pointer ** argument is a harmless no-op. */ SQLITE_API int sqlite3_close(sqlite3*); SQLITE_API int sqlite3_close_v2(sqlite3*); /* ** The type for a callback function. ** This is legacy and deprecated. It is included for historical ** compatibility and is not documented. */ typedef int (*sqlite3_callback)(void*,int,char**, char**); /* ** CAPI3REF: One-Step Query Execution Interface ** ** The sqlite3_exec() interface is a convenience wrapper around ** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()], ** that allows an application to run multiple statements of SQL ** without having to use a lot of C code. ** ** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded, ** semicolon-separate SQL statements passed into its 2nd argument, ** in the context of the [database connection] passed in as its 1st ** argument. ^If the callback function of the 3rd argument to ** sqlite3_exec() is not NULL, then it is invoked for each result row ** coming out of the evaluated SQL statements. ^The 4th argument to ** sqlite3_exec() is relayed through to the 1st argument of each ** callback invocation. ^If the callback pointer to sqlite3_exec() ** is NULL, then no callback is ever invoked and result rows are ** ignored. ** ** ^If an error occurs while evaluating the SQL statements passed into ** sqlite3_exec(), then execution of the current statement stops and ** subsequent statements are skipped. ^If the 5th parameter to sqlite3_exec() ** is not NULL then any error message is written into memory obtained ** from [sqlite3_malloc()] and passed back through the 5th parameter. ** To avoid memory leaks, the application should invoke [sqlite3_free()] ** on error message strings returned through the 5th parameter of ** of sqlite3_exec() after the error message string is no longer needed. ** ^If the 5th parameter to sqlite3_exec() is not NULL and no errors ** occur, then sqlite3_exec() sets the pointer in its 5th parameter to ** NULL before returning. ** ** ^If an sqlite3_exec() callback returns non-zero, the sqlite3_exec() ** routine returns SQLITE_ABORT without invoking the callback again and ** without running any subsequent SQL statements. ** ** ^The 2nd argument to the sqlite3_exec() callback function is the ** number of columns in the result. ^The 3rd argument to the sqlite3_exec() ** callback is an array of pointers to strings obtained as if from ** [sqlite3_column_text()], one for each column. ^If an element of a ** result row is NULL then the corresponding string pointer for the ** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the ** sqlite3_exec() callback is an array of pointers to strings where each ** entry represents the name of corresponding result column as obtained ** from [sqlite3_column_name()]. ** ** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer ** to an empty string, or a pointer that contains only whitespace and/or ** SQL comments, then no SQL statements are evaluated and the database ** is not changed. ** ** Restrictions: ** **
      **
    • The application must insure that the 1st parameter to sqlite3_exec() ** is a valid and open [database connection]. **
    • The application must not close [database connection] specified by ** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running. **
    • The application must not modify the SQL statement text passed into ** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running. **
    */ SQLITE_API int sqlite3_exec( sqlite3*, /* An open database */ const char *sql, /* SQL to be evaluated */ int (*callback)(void*,int,char**,char**), /* Callback function */ void *, /* 1st argument to callback */ char **errmsg /* Error msg written here */ ); /* ** CAPI3REF: Result Codes ** KEYWORDS: SQLITE_OK {error code} {error codes} ** KEYWORDS: {result code} {result codes} ** ** Many SQLite functions return an integer result code from the set shown ** here in order to indicate success or failure. ** ** New error codes may be added in future versions of SQLite. ** ** See also: [SQLITE_IOERR_READ | extended result codes], ** [sqlite3_vtab_on_conflict()] [SQLITE_ROLLBACK | result codes]. */ #define SQLITE_OK 0 /* Successful result */ /* beginning-of-error-codes */ #define SQLITE_ERROR 1 /* SQL error or missing database */ #define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ #define SQLITE_PERM 3 /* Access permission denied */ #define SQLITE_ABORT 4 /* Callback routine requested an abort */ #define SQLITE_BUSY 5 /* The database file is locked */ #define SQLITE_LOCKED 6 /* A table in the database is locked */ #define SQLITE_NOMEM 7 /* A malloc() failed */ #define SQLITE_READONLY 8 /* Attempt to write a readonly database */ #define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ #define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ #define SQLITE_CORRUPT 11 /* The database disk image is malformed */ #define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */ #define SQLITE_FULL 13 /* Insertion failed because database is full */ #define SQLITE_CANTOPEN 14 /* Unable to open the database file */ #define SQLITE_PROTOCOL 15 /* Database lock protocol error */ #define SQLITE_EMPTY 16 /* Database is empty */ #define SQLITE_SCHEMA 17 /* The database schema changed */ #define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ #define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ #define SQLITE_MISMATCH 20 /* Data type mismatch */ #define SQLITE_MISUSE 21 /* Library used incorrectly */ #define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ #define SQLITE_AUTH 23 /* Authorization denied */ #define SQLITE_FORMAT 24 /* Auxiliary database format error */ #define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ #define SQLITE_NOTADB 26 /* File opened that is not a database file */ #define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ #define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ /* end-of-error-codes */ /* ** CAPI3REF: Extended Result Codes ** KEYWORDS: {extended error code} {extended error codes} ** KEYWORDS: {extended result code} {extended result codes} ** ** In its default configuration, SQLite API routines return one of 26 integer ** [SQLITE_OK | result codes]. However, experience has shown that many of ** these result codes are too coarse-grained. They do not provide as ** much information about problems as programmers might like. In an effort to ** address this, newer versions of SQLite (version 3.3.8 and later) include ** support for additional result codes that provide more detailed information ** about errors. The extended result codes are enabled or disabled ** on a per database connection basis using the ** [sqlite3_extended_result_codes()] API. ** ** Some of the available extended result codes are listed here. ** One may expect the number of extended result codes will be expand ** over time. Software that uses extended result codes should expect ** to see new result codes in future releases of SQLite. ** ** The SQLITE_OK result code will never be extended. It will always ** be exactly zero. */ #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) #define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) #define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) #define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6<<8)) #define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7<<8)) #define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8<<8)) #define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9<<8)) #define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10<<8)) #define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11<<8)) #define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12<<8)) #define SQLITE_IOERR_ACCESS (SQLITE_IOERR | (13<<8)) #define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14<<8)) #define SQLITE_IOERR_LOCK (SQLITE_IOERR | (15<<8)) #define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16<<8)) #define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17<<8)) #define SQLITE_IOERR_SHMOPEN (SQLITE_IOERR | (18<<8)) #define SQLITE_IOERR_SHMSIZE (SQLITE_IOERR | (19<<8)) #define SQLITE_IOERR_SHMLOCK (SQLITE_IOERR | (20<<8)) #define SQLITE_IOERR_SHMMAP (SQLITE_IOERR | (21<<8)) #define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) #define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8)) #define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8)) #define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) #define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) #define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8)) #define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2<<8)) /* ** CAPI3REF: Flags For File Open Operations ** ** These bit values are intended for use in the ** 3rd parameter to the [sqlite3_open_v2()] interface and ** in the 4th parameter to the [sqlite3_vfs.xOpen] method. */ #define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_DELETEONCLOSE 0x00000008 /* VFS only */ #define SQLITE_OPEN_EXCLUSIVE 0x00000010 /* VFS only */ #define SQLITE_OPEN_AUTOPROXY 0x00000020 /* VFS only */ #define SQLITE_OPEN_URI 0x00000040 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */ #define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */ #define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */ #define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 /* VFS only */ #define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */ #define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */ #define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */ #define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ /* Reserved: 0x00F00000 */ /* ** CAPI3REF: Device Characteristics ** ** The xDeviceCharacteristics method of the [sqlite3_io_methods] ** object returns an integer which is a vector of these ** bit values expressing I/O characteristics of the mass storage ** device that holds the file that the [sqlite3_io_methods] ** refers to. ** ** The SQLITE_IOCAP_ATOMIC property means that all writes of ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values ** mean that writes of blocks that are nnn bytes in size and ** are aligned to an address which is an integer multiple of ** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means ** that when data is appended to a file, the data is appended ** first then the size of the file is extended, never the other ** way around. The SQLITE_IOCAP_SEQUENTIAL property means that ** information is written to disk in the same order as calls ** to xWrite(). The SQLITE_IOCAP_POWERSAFE_OVERWRITE property means that ** after reboot following a crash or power loss, the only bytes in a ** file that were written at the application level might have changed ** and that adjacent bytes, even bytes within the same sector are ** guaranteed to be unchanged. */ #define SQLITE_IOCAP_ATOMIC 0x00000001 #define SQLITE_IOCAP_ATOMIC512 0x00000002 #define SQLITE_IOCAP_ATOMIC1K 0x00000004 #define SQLITE_IOCAP_ATOMIC2K 0x00000008 #define SQLITE_IOCAP_ATOMIC4K 0x00000010 #define SQLITE_IOCAP_ATOMIC8K 0x00000020 #define SQLITE_IOCAP_ATOMIC16K 0x00000040 #define SQLITE_IOCAP_ATOMIC32K 0x00000080 #define SQLITE_IOCAP_ATOMIC64K 0x00000100 #define SQLITE_IOCAP_SAFE_APPEND 0x00000200 #define SQLITE_IOCAP_SEQUENTIAL 0x00000400 #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 /* ** CAPI3REF: File Locking Levels ** ** SQLite uses one of these integer values as the second ** argument to calls it makes to the xLock() and xUnlock() methods ** of an [sqlite3_io_methods] object. */ #define SQLITE_LOCK_NONE 0 #define SQLITE_LOCK_SHARED 1 #define SQLITE_LOCK_RESERVED 2 #define SQLITE_LOCK_PENDING 3 #define SQLITE_LOCK_EXCLUSIVE 4 /* ** CAPI3REF: Synchronization Type Flags ** ** When SQLite invokes the xSync() method of an ** [sqlite3_io_methods] object it uses a combination of ** these integer values as the second argument. ** ** When the SQLITE_SYNC_DATAONLY flag is used, it means that the ** sync operation only needs to flush data to mass storage. Inode ** information need not be flushed. If the lower four bits of the flag ** equal SQLITE_SYNC_NORMAL, that means to use normal fsync() semantics. ** If the lower four bits equal SQLITE_SYNC_FULL, that means ** to use Mac OS X style fullsync instead of fsync(). ** ** Do not confuse the SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL flags ** with the [PRAGMA synchronous]=NORMAL and [PRAGMA synchronous]=FULL ** settings. The [synchronous pragma] determines when calls to the ** xSync VFS method occur and applies uniformly across all platforms. ** The SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL flags determine how ** energetic or rigorous or forceful the sync operations are and ** only make a difference on Mac OSX for the default SQLite code. ** (Third-party VFS implementations might also make the distinction ** between SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL, but among the ** operating systems natively supported by SQLite, only Mac OSX ** cares about the difference.) */ #define SQLITE_SYNC_NORMAL 0x00002 #define SQLITE_SYNC_FULL 0x00003 #define SQLITE_SYNC_DATAONLY 0x00010 /* ** CAPI3REF: OS Interface Open File Handle ** ** An [sqlite3_file] object represents an open file in the ** [sqlite3_vfs | OS interface layer]. Individual OS interface ** implementations will ** want to subclass this object by appending additional fields ** for their own use. The pMethods entry is a pointer to an ** [sqlite3_io_methods] object that defines methods for performing ** I/O operations on the open file. */ typedef struct sqlite3_file sqlite3_file; struct sqlite3_file { const struct sqlite3_io_methods *pMethods; /* Methods for an open file */ }; /* ** CAPI3REF: OS Interface File Virtual Methods Object ** ** Every file opened by the [sqlite3_vfs.xOpen] method populates an ** [sqlite3_file] object (or, more commonly, a subclass of the ** [sqlite3_file] object) with a pointer to an instance of this object. ** This object defines the methods used to perform various operations ** against the open file represented by the [sqlite3_file] object. ** ** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element ** to a non-NULL pointer, then the sqlite3_io_methods.xClose method ** may be invoked even if the [sqlite3_vfs.xOpen] reported that it failed. The ** only way to prevent a call to xClose following a failed [sqlite3_vfs.xOpen] ** is for the [sqlite3_vfs.xOpen] to set the sqlite3_file.pMethods element ** to NULL. ** ** The flags argument to xSync may be one of [SQLITE_SYNC_NORMAL] or ** [SQLITE_SYNC_FULL]. The first choice is the normal fsync(). ** The second choice is a Mac OS X style fullsync. The [SQLITE_SYNC_DATAONLY] ** flag may be ORed in to indicate that only the data of the file ** and not its inode needs to be synced. ** ** The integer values to xLock() and xUnlock() are one of **
      **
    • [SQLITE_LOCK_NONE], **
    • [SQLITE_LOCK_SHARED], **
    • [SQLITE_LOCK_RESERVED], **
    • [SQLITE_LOCK_PENDING], or **
    • [SQLITE_LOCK_EXCLUSIVE]. **
    ** xLock() increases the lock. xUnlock() decreases the lock. ** The xCheckReservedLock() method checks whether any database connection, ** either in this process or in some other process, is holding a RESERVED, ** PENDING, or EXCLUSIVE lock on the file. It returns true ** if such a lock exists and false otherwise. ** ** The xFileControl() method is a generic interface that allows custom ** VFS implementations to directly control an open file using the ** [sqlite3_file_control()] interface. The second "op" argument is an ** integer opcode. The third argument is a generic pointer intended to ** point to a structure that may contain arguments or space in which to ** write return values. Potential uses for xFileControl() might be ** functions to enable blocking locks with timeouts, to change the ** locking strategy (for example to use dot-file locks), to inquire ** about the status of a lock, or to break stale locks. The SQLite ** core reserves all opcodes less than 100 for its own use. ** A [SQLITE_FCNTL_LOCKSTATE | list of opcodes] less than 100 is available. ** Applications that define a custom xFileControl method should use opcodes ** greater than 100 to avoid conflicts. VFS implementations should ** return [SQLITE_NOTFOUND] for file control opcodes that they do not ** recognize. ** ** The xSectorSize() method returns the sector size of the ** device that underlies the file. The sector size is the ** minimum write that can be performed without disturbing ** other bytes in the file. The xDeviceCharacteristics() ** method returns a bit vector describing behaviors of the ** underlying device: ** **
      **
    • [SQLITE_IOCAP_ATOMIC] **
    • [SQLITE_IOCAP_ATOMIC512] **
    • [SQLITE_IOCAP_ATOMIC1K] **
    • [SQLITE_IOCAP_ATOMIC2K] **
    • [SQLITE_IOCAP_ATOMIC4K] **
    • [SQLITE_IOCAP_ATOMIC8K] **
    • [SQLITE_IOCAP_ATOMIC16K] **
    • [SQLITE_IOCAP_ATOMIC32K] **
    • [SQLITE_IOCAP_ATOMIC64K] **
    • [SQLITE_IOCAP_SAFE_APPEND] **
    • [SQLITE_IOCAP_SEQUENTIAL] **
    ** ** The SQLITE_IOCAP_ATOMIC property means that all writes of ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values ** mean that writes of blocks that are nnn bytes in size and ** are aligned to an address which is an integer multiple of ** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means ** that when data is appended to a file, the data is appended ** first then the size of the file is extended, never the other ** way around. The SQLITE_IOCAP_SEQUENTIAL property means that ** information is written to disk in the same order as calls ** to xWrite(). ** ** If xRead() returns SQLITE_IOERR_SHORT_READ it must also fill ** in the unread portions of the buffer with zeros. A VFS that ** fails to zero-fill short reads might seem to work. However, ** failure to zero-fill short reads will eventually lead to ** database corruption. */ typedef struct sqlite3_io_methods sqlite3_io_methods; struct sqlite3_io_methods { int iVersion; int (*xClose)(sqlite3_file*); int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst); int (*xTruncate)(sqlite3_file*, sqlite3_int64 size); int (*xSync)(sqlite3_file*, int flags); int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize); int (*xLock)(sqlite3_file*, int); int (*xUnlock)(sqlite3_file*, int); int (*xCheckReservedLock)(sqlite3_file*, int *pResOut); int (*xFileControl)(sqlite3_file*, int op, void *pArg); int (*xSectorSize)(sqlite3_file*); int (*xDeviceCharacteristics)(sqlite3_file*); /* Methods above are valid for version 1 */ int (*xShmMap)(sqlite3_file*, int iPg, int pgsz, int, void volatile**); int (*xShmLock)(sqlite3_file*, int offset, int n, int flags); void (*xShmBarrier)(sqlite3_file*); int (*xShmUnmap)(sqlite3_file*, int deleteFlag); /* Methods above are valid for version 2 */ /* Additional methods may be added in future releases */ }; /* ** CAPI3REF: Standard File Control Opcodes ** ** These integer constants are opcodes for the xFileControl method ** of the [sqlite3_io_methods] object and for the [sqlite3_file_control()] ** interface. ** ** The [SQLITE_FCNTL_LOCKSTATE] opcode is used for debugging. This ** opcode causes the xFileControl method to write the current state of ** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED], ** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE]) ** into an integer that the pArg argument points to. This capability ** is used during testing and only needs to be supported when SQLITE_TEST ** is defined. **
      **
    • [[SQLITE_FCNTL_SIZE_HINT]] ** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS ** layer a hint of how large the database file will grow to be during the ** current transaction. This hint is not guaranteed to be accurate but it ** is often close. The underlying VFS might choose to preallocate database ** file space based on this hint in order to help writes to the database ** file run faster. ** **
    • [[SQLITE_FCNTL_CHUNK_SIZE]] ** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS ** extends and truncates the database file in chunks of a size specified ** by the user. The fourth argument to [sqlite3_file_control()] should ** point to an integer (type int) containing the new chunk-size to use ** for the nominated database. Allocating database file space in large ** chunks (say 1MB at a time), may reduce file-system fragmentation and ** improve performance on some systems. ** **
    • [[SQLITE_FCNTL_FILE_POINTER]] ** The [SQLITE_FCNTL_FILE_POINTER] opcode is used to obtain a pointer ** to the [sqlite3_file] object associated with a particular database ** connection. See the [sqlite3_file_control()] documentation for ** additional information. ** **
    • [[SQLITE_FCNTL_SYNC_OMITTED]] ** ^(The [SQLITE_FCNTL_SYNC_OMITTED] opcode is generated internally by ** SQLite and sent to all VFSes in place of a call to the xSync method ** when the database connection has [PRAGMA synchronous] set to OFF.)^ ** Some specialized VFSes need this signal in order to operate correctly ** when [PRAGMA synchronous | PRAGMA synchronous=OFF] is set, but most ** VFSes do not need this signal and should silently ignore this opcode. ** Applications should not call [sqlite3_file_control()] with this ** opcode as doing so may disrupt the operation of the specialized VFSes ** that do require it. ** **
    • [[SQLITE_FCNTL_WIN32_AV_RETRY]] ** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic ** retry counts and intervals for certain disk I/O operations for the ** windows [VFS] in order to provide robustness in the presence of ** anti-virus programs. By default, the windows VFS will retry file read, ** file write, and file delete operations up to 10 times, with a delay ** of 25 milliseconds before the first retry and with the delay increasing ** by an additional 25 milliseconds with each subsequent retry. This ** opcode allows these two values (10 retries and 25 milliseconds of delay) ** to be adjusted. The values are changed for all database connections ** within the same process. The argument is a pointer to an array of two ** integers where the first integer i the new retry count and the second ** integer is the delay. If either integer is negative, then the setting ** is not changed but instead the prior value of that setting is written ** into the array entry, allowing the current retry settings to be ** interrogated. The zDbName parameter is ignored. ** **
    • [[SQLITE_FCNTL_PERSIST_WAL]] ** ^The [SQLITE_FCNTL_PERSIST_WAL] opcode is used to set or query the ** persistent [WAL | Write Ahead Log] setting. By default, the auxiliary ** write ahead log and shared memory files used for transaction control ** are automatically deleted when the latest connection to the database ** closes. Setting persistent WAL mode causes those files to persist after ** close. Persisting the files is useful when other processes that do not ** have write permission on the directory containing the database file want ** to read the database file, as the WAL and shared memory files must exist ** in order for the database to be readable. The fourth parameter to ** [sqlite3_file_control()] for this opcode should be a pointer to an integer. ** That integer is 0 to disable persistent WAL mode or 1 to enable persistent ** WAL mode. If the integer is -1, then it is overwritten with the current ** WAL persistence setting. ** **
    • [[SQLITE_FCNTL_POWERSAFE_OVERWRITE]] ** ^The [SQLITE_FCNTL_POWERSAFE_OVERWRITE] opcode is used to set or query the ** persistent "powersafe-overwrite" or "PSOW" setting. The PSOW setting ** determines the [SQLITE_IOCAP_POWERSAFE_OVERWRITE] bit of the ** xDeviceCharacteristics methods. The fourth parameter to ** [sqlite3_file_control()] for this opcode should be a pointer to an integer. ** That integer is 0 to disable zero-damage mode or 1 to enable zero-damage ** mode. If the integer is -1, then it is overwritten with the current ** zero-damage mode setting. ** **
    • [[SQLITE_FCNTL_OVERWRITE]] ** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening ** a write transaction to indicate that, unless it is rolled back for some ** reason, the entire database file will be overwritten by the current ** transaction. This is used by VACUUM operations. ** **
    • [[SQLITE_FCNTL_VFSNAME]] ** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of ** all [VFSes] in the VFS stack. The names are of all VFS shims and the ** final bottom-level VFS are written into memory obtained from ** [sqlite3_malloc()] and the result is stored in the char* variable ** that the fourth parameter of [sqlite3_file_control()] points to. ** The caller is responsible for freeing the memory when done. As with ** all file-control actions, there is no guarantee that this will actually ** do anything. Callers should initialize the char* variable to a NULL ** pointer in case this file-control is not implemented. This file-control ** is intended for diagnostic use only. ** **
    • [[SQLITE_FCNTL_PRAGMA]] ** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] ** file control is sent to the open [sqlite3_file] object corresponding ** to the database file to which the pragma statement refers. ^The argument ** to the [SQLITE_FCNTL_PRAGMA] file control is an array of ** pointers to strings (char**) in which the second element of the array ** is the name of the pragma and the third element is the argument to the ** pragma or NULL if the pragma has no argument. ^The handler for an ** [SQLITE_FCNTL_PRAGMA] file control can optionally make the first element ** of the char** argument point to a string obtained from [sqlite3_mprintf()] ** or the equivalent and that string will become the result of the pragma or ** the error message if the pragma fails. ^If the ** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal ** [PRAGMA] processing continues. ^If the [SQLITE_FCNTL_PRAGMA] ** file control returns [SQLITE_OK], then the parser assumes that the ** VFS has handled the PRAGMA itself and the parser generates a no-op ** prepared statement. ^If the [SQLITE_FCNTL_PRAGMA] file control returns ** any result code other than [SQLITE_OK] or [SQLITE_NOTFOUND], that means ** that the VFS encountered an error while handling the [PRAGMA] and the ** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] ** file control occurs at the beginning of pragma statement analysis and so ** it is able to override built-in [PRAGMA] statements. **
    */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_GET_LOCKPROXYFILE 2 #define SQLITE_SET_LOCKPROXYFILE 3 #define SQLITE_LAST_ERRNO 4 #define SQLITE_FCNTL_SIZE_HINT 5 #define SQLITE_FCNTL_CHUNK_SIZE 6 #define SQLITE_FCNTL_FILE_POINTER 7 #define SQLITE_FCNTL_SYNC_OMITTED 8 #define SQLITE_FCNTL_WIN32_AV_RETRY 9 #define SQLITE_FCNTL_PERSIST_WAL 10 #define SQLITE_FCNTL_OVERWRITE 11 #define SQLITE_FCNTL_VFSNAME 12 #define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 #define SQLITE_FCNTL_PRAGMA 14 /* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an ** abstract type for a mutex object. The SQLite core never looks ** at the internal representation of an [sqlite3_mutex]. It only ** deals with pointers to the [sqlite3_mutex] object. ** ** Mutexes are created using [sqlite3_mutex_alloc()]. */ typedef struct sqlite3_mutex sqlite3_mutex; /* ** CAPI3REF: OS Interface Object ** ** An instance of the sqlite3_vfs object defines the interface between ** the SQLite core and the underlying operating system. The "vfs" ** in the name of the object stands for "virtual file system". See ** the [VFS | VFS documentation] for further information. ** ** The value of the iVersion field is initially 1 but may be larger in ** future versions of SQLite. Additional fields may be appended to this ** object when the iVersion value is increased. Note that the structure ** of the sqlite3_vfs object changes in the transaction between ** SQLite version 3.5.9 and 3.6.0 and yet the iVersion field was not ** modified. ** ** The szOsFile field is the size of the subclassed [sqlite3_file] ** structure used by this VFS. mxPathname is the maximum length of ** a pathname in this VFS. ** ** Registered sqlite3_vfs objects are kept on a linked list formed by ** the pNext pointer. The [sqlite3_vfs_register()] ** and [sqlite3_vfs_unregister()] interfaces manage this list ** in a thread-safe way. The [sqlite3_vfs_find()] interface ** searches the list. Neither the application code nor the VFS ** implementation should use the pNext pointer. ** ** The pNext field is the only field in the sqlite3_vfs ** structure that SQLite will ever modify. SQLite will only access ** or modify this field while holding a particular static mutex. ** The application should never modify anything within the sqlite3_vfs ** object once the object has been registered. ** ** The zName field holds the name of the VFS module. The name must ** be unique across all VFS modules. ** ** [[sqlite3_vfs.xOpen]] ** ^SQLite guarantees that the zFilename parameter to xOpen ** is either a NULL pointer or string obtained ** from xFullPathname() with an optional suffix added. ** ^If a suffix is added to the zFilename parameter, it will ** consist of a single "-" character followed by no more than ** 11 alphanumeric and/or "-" characters. ** ^SQLite further guarantees that ** the string will be valid and unchanged until xClose() is ** called. Because of the previous sentence, ** the [sqlite3_file] can safely store a pointer to the ** filename if it needs to remember the filename for some reason. ** If the zFilename parameter to xOpen is a NULL pointer then xOpen ** must invent its own temporary name for the file. ^Whenever the ** xFilename parameter is NULL it will also be the case that the ** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE]. ** ** The flags argument to xOpen() includes all bits set in ** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()] ** or [sqlite3_open16()] is used, then flags includes at least ** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. ** If xOpen() opens a file read-only then it sets *pOutFlags to ** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set. ** ** ^(SQLite will also add one of the following flags to the xOpen() ** call, depending on the object being opened: ** **
      **
    • [SQLITE_OPEN_MAIN_DB] **
    • [SQLITE_OPEN_MAIN_JOURNAL] **
    • [SQLITE_OPEN_TEMP_DB] **
    • [SQLITE_OPEN_TEMP_JOURNAL] **
    • [SQLITE_OPEN_TRANSIENT_DB] **
    • [SQLITE_OPEN_SUBJOURNAL] **
    • [SQLITE_OPEN_MASTER_JOURNAL] **
    • [SQLITE_OPEN_WAL] **
    )^ ** ** The file I/O implementation can use the object type flags to ** change the way it deals with files. For example, an application ** that does not care about crash recovery or rollback might make ** the open of a journal file a no-op. Writes to this journal would ** also be no-ops, and any attempt to read the journal would return ** SQLITE_IOERR. Or the implementation might recognize that a database ** file will be doing page-aligned sector reads and writes in a random ** order and set up its I/O subsystem accordingly. ** ** SQLite might also add one of the following flags to the xOpen method: ** **
      **
    • [SQLITE_OPEN_DELETEONCLOSE] **
    • [SQLITE_OPEN_EXCLUSIVE] **
    ** ** The [SQLITE_OPEN_DELETEONCLOSE] flag means the file should be ** deleted when it is closed. ^The [SQLITE_OPEN_DELETEONCLOSE] ** will be set for TEMP databases and their journals, transient ** databases, and subjournals. ** ** ^The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction ** with the [SQLITE_OPEN_CREATE] flag, which are both directly ** analogous to the O_EXCL and O_CREAT flags of the POSIX open() ** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the ** SQLITE_OPEN_CREATE, is used to indicate that file should always ** be created, and that it is an error if it already exists. ** It is not used to indicate the file should be opened ** for exclusive access. ** ** ^At least szOsFile bytes of memory are allocated by SQLite ** to hold the [sqlite3_file] structure passed as the third ** argument to xOpen. The xOpen method does not have to ** allocate the structure; it should just fill it in. Note that ** the xOpen method must set the sqlite3_file.pMethods to either ** a valid [sqlite3_io_methods] object or to NULL. xOpen must do ** this even if the open fails. SQLite expects that the sqlite3_file.pMethods ** element will be valid after xOpen returns regardless of the success ** or failure of the xOpen call. ** ** [[sqlite3_vfs.xAccess]] ** ^The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS] ** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to ** test whether a file is readable and writable, or [SQLITE_ACCESS_READ] ** to test whether a file is at least readable. The file can be a ** directory. ** ** ^SQLite will always allocate at least mxPathname+1 bytes for the ** output buffer xFullPathname. The exact size of the output buffer ** is also passed as a parameter to both methods. If the output buffer ** is not large enough, [SQLITE_CANTOPEN] should be returned. Since this is ** handled as a fatal error by SQLite, vfs implementations should endeavor ** to prevent this by setting mxPathname to a sufficiently large value. ** ** The xRandomness(), xSleep(), xCurrentTime(), and xCurrentTimeInt64() ** interfaces are not strictly a part of the filesystem, but they are ** included in the VFS structure for completeness. ** The xRandomness() function attempts to return nBytes bytes ** of good-quality randomness into zOut. The return value is ** the actual number of bytes of randomness obtained. ** The xSleep() method causes the calling thread to sleep for at ** least the number of microseconds given. ^The xCurrentTime() ** method returns a Julian Day Number for the current date and time as ** a floating point value. ** ^The xCurrentTimeInt64() method returns, as an integer, the Julian ** Day Number multiplied by 86400000 (the number of milliseconds in ** a 24-hour day). ** ^SQLite will use the xCurrentTimeInt64() method to get the current ** date and time if that method is available (if iVersion is 2 or ** greater and the function pointer is not NULL) and will fall back ** to xCurrentTime() if xCurrentTimeInt64() is unavailable. ** ** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces ** are not used by the SQLite core. These optional interfaces are provided ** by some VFSes to facilitate testing of the VFS code. By overriding ** system calls with functions under its control, a test program can ** simulate faults and error conditions that would otherwise be difficult ** or impossible to induce. The set of system calls that can be overridden ** varies from one VFS to another, and from one version of the same VFS to the ** next. Applications that use these interfaces must be prepared for any ** or all of these interfaces to be NULL or for their behavior to change ** from one release to the next. Applications must not attempt to access ** any of these methods if the iVersion of the VFS is less than 3. */ typedef struct sqlite3_vfs sqlite3_vfs; typedef void (*sqlite3_syscall_ptr)(void); struct sqlite3_vfs { int iVersion; /* Structure version number (currently 3) */ int szOsFile; /* Size of subclassed sqlite3_file */ int mxPathname; /* Maximum file pathname length */ sqlite3_vfs *pNext; /* Next registered VFS */ const char *zName; /* Name of this virtual file system */ void *pAppData; /* Pointer to application-specific data */ int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*, int flags, int *pOutFlags); int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir); int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut); int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut); void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename); void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg); void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void); void (*xDlClose)(sqlite3_vfs*, void*); int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut); int (*xSleep)(sqlite3_vfs*, int microseconds); int (*xCurrentTime)(sqlite3_vfs*, double*); int (*xGetLastError)(sqlite3_vfs*, int, char *); /* ** The methods above are in version 1 of the sqlite_vfs object ** definition. Those that follow are added in version 2 or later */ int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*); /* ** The methods above are in versions 1 and 2 of the sqlite_vfs object. ** Those below are for version 3 and greater. */ int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr); sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName); const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName); /* ** The methods above are in versions 1 through 3 of the sqlite_vfs object. ** New fields may be appended in figure versions. The iVersion ** value will increment whenever this happens. */ }; /* ** CAPI3REF: Flags for the xAccess VFS method ** ** These integer constants can be used as the third parameter to ** the xAccess method of an [sqlite3_vfs] object. They determine ** what kind of permissions the xAccess method is looking for. ** With SQLITE_ACCESS_EXISTS, the xAccess method ** simply checks whether the file exists. ** With SQLITE_ACCESS_READWRITE, the xAccess method ** checks whether the named directory is both readable and writable ** (in other words, if files can be added, removed, and renamed within ** the directory). ** The SQLITE_ACCESS_READWRITE constant is currently used only by the ** [temp_store_directory pragma], though this could change in a future ** release of SQLite. ** With SQLITE_ACCESS_READ, the xAccess method ** checks whether the file is readable. The SQLITE_ACCESS_READ constant is ** currently unused, though it might be used in a future release of ** SQLite. */ #define SQLITE_ACCESS_EXISTS 0 #define SQLITE_ACCESS_READWRITE 1 /* Used by PRAGMA temp_store_directory */ #define SQLITE_ACCESS_READ 2 /* Unused */ /* ** CAPI3REF: Flags for the xShmLock VFS method ** ** These integer constants define the various locking operations ** allowed by the xShmLock method of [sqlite3_io_methods]. The ** following are the only legal combinations of flags to the ** xShmLock method: ** **
      **
    • SQLITE_SHM_LOCK | SQLITE_SHM_SHARED **
    • SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE **
    • SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED **
    • SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE **
    ** ** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as ** was given no the corresponding lock. ** ** The xShmLock method can transition between unlocked and SHARED or ** between unlocked and EXCLUSIVE. It cannot transition between SHARED ** and EXCLUSIVE. */ #define SQLITE_SHM_UNLOCK 1 #define SQLITE_SHM_LOCK 2 #define SQLITE_SHM_SHARED 4 #define SQLITE_SHM_EXCLUSIVE 8 /* ** CAPI3REF: Maximum xShmLock index ** ** The xShmLock method on [sqlite3_io_methods] may use values ** between 0 and this upper bound as its "offset" argument. ** The SQLite core will never attempt to acquire or release a ** lock outside of this range */ #define SQLITE_SHM_NLOCK 8 /* ** CAPI3REF: Initialize The SQLite Library ** ** ^The sqlite3_initialize() routine initializes the ** SQLite library. ^The sqlite3_shutdown() routine ** deallocates any resources that were allocated by sqlite3_initialize(). ** These routines are designed to aid in process initialization and ** shutdown on embedded systems. Workstation applications using ** SQLite normally do not need to invoke either of these routines. ** ** A call to sqlite3_initialize() is an "effective" call if it is ** the first time sqlite3_initialize() is invoked during the lifetime of ** the process, or if it is the first time sqlite3_initialize() is invoked ** following a call to sqlite3_shutdown(). ^(Only an effective call ** of sqlite3_initialize() does any initialization. All other calls ** are harmless no-ops.)^ ** ** A call to sqlite3_shutdown() is an "effective" call if it is the first ** call to sqlite3_shutdown() since the last sqlite3_initialize(). ^(Only ** an effective call to sqlite3_shutdown() does any deinitialization. ** All other valid calls to sqlite3_shutdown() are harmless no-ops.)^ ** ** The sqlite3_initialize() interface is threadsafe, but sqlite3_shutdown() ** is not. The sqlite3_shutdown() interface must only be called from a ** single thread. All open [database connections] must be closed and all ** other SQLite resources must be deallocated prior to invoking ** sqlite3_shutdown(). ** ** Among other things, ^sqlite3_initialize() will invoke ** sqlite3_os_init(). Similarly, ^sqlite3_shutdown() ** will invoke sqlite3_os_end(). ** ** ^The sqlite3_initialize() routine returns [SQLITE_OK] on success. ** ^If for some reason, sqlite3_initialize() is unable to initialize ** the library (perhaps it is unable to allocate a needed resource such ** as a mutex) it returns an [error code] other than [SQLITE_OK]. ** ** ^The sqlite3_initialize() routine is called internally by many other ** SQLite interfaces so that an application usually does not need to ** invoke sqlite3_initialize() directly. For example, [sqlite3_open()] ** calls sqlite3_initialize() so the SQLite library will be automatically ** initialized when [sqlite3_open()] is called if it has not be initialized ** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT] ** compile-time option, then the automatic calls to sqlite3_initialize() ** are omitted and the application must call sqlite3_initialize() directly ** prior to using any other SQLite interface. For maximum portability, ** it is recommended that applications always invoke sqlite3_initialize() ** directly prior to using any other SQLite interface. Future releases ** of SQLite may require this. In other words, the behavior exhibited ** when SQLite is compiled with [SQLITE_OMIT_AUTOINIT] might become the ** default behavior in some future release of SQLite. ** ** The sqlite3_os_init() routine does operating-system specific ** initialization of the SQLite library. The sqlite3_os_end() ** routine undoes the effect of sqlite3_os_init(). Typical tasks ** performed by these routines include allocation or deallocation ** of static resources, initialization of global variables, ** setting up a default [sqlite3_vfs] module, or setting up ** a default configuration using [sqlite3_config()]. ** ** The application should never invoke either sqlite3_os_init() ** or sqlite3_os_end() directly. The application should only invoke ** sqlite3_initialize() and sqlite3_shutdown(). The sqlite3_os_init() ** interface is called automatically by sqlite3_initialize() and ** sqlite3_os_end() is called by sqlite3_shutdown(). Appropriate ** implementations for sqlite3_os_init() and sqlite3_os_end() ** are built into SQLite when it is compiled for Unix, Windows, or OS/2. ** When [custom builds | built for other platforms] ** (using the [SQLITE_OS_OTHER=1] compile-time ** option) the application must supply a suitable implementation for ** sqlite3_os_init() and sqlite3_os_end(). An application-supplied ** implementation of sqlite3_os_init() or sqlite3_os_end() ** must return [SQLITE_OK] on success and some other [error code] upon ** failure. */ SQLITE_API int sqlite3_initialize(void); SQLITE_API int sqlite3_shutdown(void); SQLITE_API int sqlite3_os_init(void); SQLITE_API int sqlite3_os_end(void); /* ** CAPI3REF: Configuring The SQLite Library ** ** The sqlite3_config() interface is used to make global configuration ** changes to SQLite in order to tune SQLite to the specific needs of ** the application. The default configuration is recommended for most ** applications and so this routine is usually not necessary. It is ** provided to support rare applications with unusual needs. ** ** The sqlite3_config() interface is not threadsafe. The application ** must insure that no other SQLite interfaces are invoked by other ** threads while sqlite3_config() is running. Furthermore, sqlite3_config() ** may only be invoked prior to library initialization using ** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. ** ^If sqlite3_config() is called after [sqlite3_initialize()] and before ** [sqlite3_shutdown()] then it will return SQLITE_MISUSE. ** Note, however, that ^sqlite3_config() can be called as part of the ** implementation of an application-defined [sqlite3_os_init()]. ** ** The first argument to sqlite3_config() is an integer ** [configuration option] that determines ** what property of SQLite is to be configured. Subsequent arguments ** vary depending on the [configuration option] ** in the first argument. ** ** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. ** ^If the option is unknown or SQLite is unable to set the option ** then this routine returns a non-zero [error code]. */ SQLITE_API int sqlite3_config(int, ...); /* ** CAPI3REF: Configure database connections ** ** The sqlite3_db_config() interface is used to make configuration ** changes to a [database connection]. The interface is similar to ** [sqlite3_config()] except that the changes apply to a single ** [database connection] (specified in the first argument). ** ** The second argument to sqlite3_db_config(D,V,...) is the ** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code ** that indicates what aspect of the [database connection] is being configured. ** Subsequent arguments vary depending on the configuration verb. ** ** ^Calls to sqlite3_db_config() return SQLITE_OK if and only if ** the call is considered successful. */ SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...); /* ** CAPI3REF: Memory Allocation Routines ** ** An instance of this object defines the interface between SQLite ** and low-level memory allocation routines. ** ** This object is used in only one place in the SQLite interface. ** A pointer to an instance of this object is the argument to ** [sqlite3_config()] when the configuration option is ** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. ** By creating an instance of this object ** and passing it to [sqlite3_config]([SQLITE_CONFIG_MALLOC]) ** during configuration, an application can specify an alternative ** memory allocation subsystem for SQLite to use for all of its ** dynamic memory needs. ** ** Note that SQLite comes with several [built-in memory allocators] ** that are perfectly adequate for the overwhelming majority of applications ** and that this object is only useful to a tiny minority of applications ** with specialized memory allocation requirements. This object is ** also used during testing of SQLite in order to specify an alternative ** memory allocator that simulates memory out-of-memory conditions in ** order to verify that SQLite recovers gracefully from such ** conditions. ** ** The xMalloc, xRealloc, and xFree methods must work like the ** malloc(), realloc() and free() functions from the standard C library. ** ^SQLite guarantees that the second argument to ** xRealloc is always a value returned by a prior call to xRoundup. ** ** xSize should return the allocated size of a memory allocation ** previously obtained from xMalloc or xRealloc. The allocated size ** is always at least as big as the requested size but may be larger. ** ** The xRoundup method returns what would be the allocated size of ** a memory allocation given a particular requested size. Most memory ** allocators round up memory allocations at least to the next multiple ** of 8. Some allocators round up to a larger multiple or to a power of 2. ** Every memory allocation request coming in through [sqlite3_malloc()] ** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, ** that causes the corresponding memory allocation to fail. ** ** The xInit method initializes the memory allocator. (For example, ** it might allocate any require mutexes or initialize internal data ** structures. The xShutdown method is invoked (indirectly) by ** [sqlite3_shutdown()] and should deallocate any resources acquired ** by xInit. The pAppData pointer is used as the only parameter to ** xInit and xShutdown. ** ** SQLite holds the [SQLITE_MUTEX_STATIC_MASTER] mutex when it invokes ** the xInit method, so the xInit method need not be threadsafe. The ** xShutdown method is only called from [sqlite3_shutdown()] so it does ** not need to be threadsafe either. For all other methods, SQLite ** holds the [SQLITE_MUTEX_STATIC_MEM] mutex as long as the ** [SQLITE_CONFIG_MEMSTATUS] configuration option is turned on (which ** it is by default) and so the methods are automatically serialized. ** However, if [SQLITE_CONFIG_MEMSTATUS] is disabled, then the other ** methods must be threadsafe or else make their own arrangements for ** serialization. ** ** SQLite will never invoke xInit() more than once without an intervening ** call to xShutdown(). */ typedef struct sqlite3_mem_methods sqlite3_mem_methods; struct sqlite3_mem_methods { void *(*xMalloc)(int); /* Memory allocation function */ void (*xFree)(void*); /* Free a prior allocation */ void *(*xRealloc)(void*,int); /* Resize an allocation */ int (*xSize)(void*); /* Return the size of an allocation */ int (*xRoundup)(int); /* Round up request size to allocation size */ int (*xInit)(void*); /* Initialize the memory allocator */ void (*xShutdown)(void*); /* Deinitialize the memory allocator */ void *pAppData; /* Argument to xInit() and xShutdown() */ }; /* ** CAPI3REF: Configuration Options ** KEYWORDS: {configuration option} ** ** These constants are the available integer configuration options that ** can be passed as the first argument to the [sqlite3_config()] interface. ** ** New configuration options may be added in future releases of SQLite. ** Existing configuration options might be discontinued. Applications ** should check the return code from [sqlite3_config()] to make sure that ** the call worked. The [sqlite3_config()] interface will return a ** non-zero [error code] if a discontinued or unsupported configuration option ** is invoked. ** **
    ** [[SQLITE_CONFIG_SINGLETHREAD]]
    SQLITE_CONFIG_SINGLETHREAD
    **
    There are no arguments to this option. ^This option sets the ** [threading mode] to Single-thread. In other words, it disables ** all mutexing and puts SQLite into a mode where it can only be used ** by a single thread. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** it is not possible to change the [threading mode] from its default ** value of Single-thread and so [sqlite3_config()] will return ** [SQLITE_ERROR] if called with the SQLITE_CONFIG_SINGLETHREAD ** configuration option.
    ** ** [[SQLITE_CONFIG_MULTITHREAD]]
    SQLITE_CONFIG_MULTITHREAD
    **
    There are no arguments to this option. ^This option sets the ** [threading mode] to Multi-thread. In other words, it disables ** mutexing on [database connection] and [prepared statement] objects. ** The application is responsible for serializing access to ** [database connections] and [prepared statements]. But other mutexes ** are enabled so that SQLite will be safe to use in a multi-threaded ** environment as long as no two threads attempt to use the same ** [database connection] at the same time. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** it is not possible to set the Multi-thread [threading mode] and ** [sqlite3_config()] will return [SQLITE_ERROR] if called with the ** SQLITE_CONFIG_MULTITHREAD configuration option.
    ** ** [[SQLITE_CONFIG_SERIALIZED]]
    SQLITE_CONFIG_SERIALIZED
    **
    There are no arguments to this option. ^This option sets the ** [threading mode] to Serialized. In other words, this option enables ** all mutexes including the recursive ** mutexes on [database connection] and [prepared statement] objects. ** In this mode (which is the default when SQLite is compiled with ** [SQLITE_THREADSAFE=1]) the SQLite library will itself serialize access ** to [database connections] and [prepared statements] so that the ** application is free to use the same [database connection] or the ** same [prepared statement] in different threads at the same time. ** ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** it is not possible to set the Serialized [threading mode] and ** [sqlite3_config()] will return [SQLITE_ERROR] if called with the ** SQLITE_CONFIG_SERIALIZED configuration option.
    ** ** [[SQLITE_CONFIG_MALLOC]]
    SQLITE_CONFIG_MALLOC
    **
    ^(This option takes a single argument which is a pointer to an ** instance of the [sqlite3_mem_methods] structure. The argument specifies ** alternative low-level memory allocation routines to be used in place of ** the memory allocation routines built into SQLite.)^ ^SQLite makes ** its own private copy of the content of the [sqlite3_mem_methods] structure ** before the [sqlite3_config()] call returns.
    ** ** [[SQLITE_CONFIG_GETMALLOC]]
    SQLITE_CONFIG_GETMALLOC
    **
    ^(This option takes a single argument which is a pointer to an ** instance of the [sqlite3_mem_methods] structure. The [sqlite3_mem_methods] ** structure is filled with the currently defined memory allocation routines.)^ ** This option can be used to overload the default memory allocation ** routines with a wrapper that simulations memory allocation failure or ** tracks memory usage, for example.
    ** ** [[SQLITE_CONFIG_MEMSTATUS]]
    SQLITE_CONFIG_MEMSTATUS
    **
    ^This option takes single argument of type int, interpreted as a ** boolean, which enables or disables the collection of memory allocation ** statistics. ^(When memory allocation statistics are disabled, the ** following SQLite interfaces become non-operational: **
      **
    • [sqlite3_memory_used()] **
    • [sqlite3_memory_highwater()] **
    • [sqlite3_soft_heap_limit64()] **
    • [sqlite3_status()] **
    )^ ** ^Memory allocation statistics are enabled by default unless SQLite is ** compiled with [SQLITE_DEFAULT_MEMSTATUS]=0 in which case memory ** allocation statistics are disabled by default. **
    ** ** [[SQLITE_CONFIG_SCRATCH]]
    SQLITE_CONFIG_SCRATCH
    **
    ^This option specifies a static memory buffer that SQLite can use for ** scratch memory. There are three arguments: A pointer an 8-byte ** aligned memory buffer from which the scratch allocations will be ** drawn, the size of each scratch allocation (sz), ** and the maximum number of scratch allocations (N). The sz ** argument must be a multiple of 16. ** The first argument must be a pointer to an 8-byte aligned buffer ** of at least sz*N bytes of memory. ** ^SQLite will use no more than two scratch buffers per thread. So ** N should be set to twice the expected maximum number of threads. ** ^SQLite will never require a scratch buffer that is more than 6 ** times the database page size. ^If SQLite needs needs additional ** scratch memory beyond what is provided by this configuration option, then ** [sqlite3_malloc()] will be used to obtain the memory needed.
    ** ** [[SQLITE_CONFIG_PAGECACHE]]
    SQLITE_CONFIG_PAGECACHE
    **
    ^This option specifies a static memory buffer that SQLite can use for ** the database page cache with the default page cache implementation. ** This configuration should not be used if an application-define page ** cache implementation is loaded using the SQLITE_CONFIG_PCACHE2 option. ** There are three arguments to this option: A pointer to 8-byte aligned ** memory, the size of each page buffer (sz), and the number of pages (N). ** The sz argument should be the size of the largest database page ** (a power of two between 512 and 32768) plus a little extra for each ** page header. ^The page header size is 20 to 40 bytes depending on ** the host architecture. ^It is harmless, apart from the wasted memory, ** to make sz a little too large. The first ** argument should point to an allocation of at least sz*N bytes of memory. ** ^SQLite will use the memory provided by the first argument to satisfy its ** memory needs for the first N pages that it adds to cache. ^If additional ** page cache memory is needed beyond what is provided by this option, then ** SQLite goes to [sqlite3_malloc()] for the additional storage space. ** The pointer in the first argument must ** be aligned to an 8-byte boundary or subsequent behavior of SQLite ** will be undefined.
    ** ** [[SQLITE_CONFIG_HEAP]]
    SQLITE_CONFIG_HEAP
    **
    ^This option specifies a static memory buffer that SQLite will use ** for all of its dynamic memory allocation needs beyond those provided ** for by [SQLITE_CONFIG_SCRATCH] and [SQLITE_CONFIG_PAGECACHE]. ** There are three arguments: An 8-byte aligned pointer to the memory, ** the number of bytes in the memory buffer, and the minimum allocation size. ** ^If the first pointer (the memory pointer) is NULL, then SQLite reverts ** to using its default memory allocator (the system malloc() implementation), ** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the ** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or ** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory ** allocator is engaged to handle all of SQLites memory allocation needs. ** The first pointer (the memory pointer) must be aligned to an 8-byte ** boundary or subsequent behavior of SQLite will be undefined. ** The minimum allocation size is capped at 2**12. Reasonable values ** for the minimum allocation size are 2**5 through 2**8.
    ** ** [[SQLITE_CONFIG_MUTEX]]
    SQLITE_CONFIG_MUTEX
    **
    ^(This option takes a single argument which is a pointer to an ** instance of the [sqlite3_mutex_methods] structure. The argument specifies ** alternative low-level mutex routines to be used in place ** the mutex routines built into SQLite.)^ ^SQLite makes a copy of the ** content of the [sqlite3_mutex_methods] structure before the call to ** [sqlite3_config()] returns. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** the entire mutexing subsystem is omitted from the build and hence calls to ** [sqlite3_config()] with the SQLITE_CONFIG_MUTEX configuration option will ** return [SQLITE_ERROR].
    ** ** [[SQLITE_CONFIG_GETMUTEX]]
    SQLITE_CONFIG_GETMUTEX
    **
    ^(This option takes a single argument which is a pointer to an ** instance of the [sqlite3_mutex_methods] structure. The ** [sqlite3_mutex_methods] ** structure is filled with the currently defined mutex routines.)^ ** This option can be used to overload the default mutex allocation ** routines with a wrapper used to track mutex usage for performance ** profiling or testing, for example. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** the entire mutexing subsystem is omitted from the build and hence calls to ** [sqlite3_config()] with the SQLITE_CONFIG_GETMUTEX configuration option will ** return [SQLITE_ERROR].
    ** ** [[SQLITE_CONFIG_LOOKASIDE]]
    SQLITE_CONFIG_LOOKASIDE
    **
    ^(This option takes two arguments that determine the default ** memory allocation for the lookaside memory allocator on each ** [database connection]. The first argument is the ** size of each lookaside buffer slot and the second is the number of ** slots allocated to each database connection.)^ ^(This option sets the ** default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] ** verb to [sqlite3_db_config()] can be used to change the lookaside ** configuration on individual connections.)^
    ** ** [[SQLITE_CONFIG_PCACHE2]]
    SQLITE_CONFIG_PCACHE2
    **
    ^(This option takes a single argument which is a pointer to ** an [sqlite3_pcache_methods2] object. This object specifies the interface ** to a custom page cache implementation.)^ ^SQLite makes a copy of the ** object and uses it for page cache memory allocations.
    ** ** [[SQLITE_CONFIG_GETPCACHE2]]
    SQLITE_CONFIG_GETPCACHE2
    **
    ^(This option takes a single argument which is a pointer to an ** [sqlite3_pcache_methods2] object. SQLite copies of the current ** page cache implementation into that object.)^
    ** ** [[SQLITE_CONFIG_LOG]]
    SQLITE_CONFIG_LOG
    **
    ^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a ** function with a call signature of void(*)(void*,int,const char*), ** and a pointer to void. ^If the function pointer is not NULL, it is ** invoked by [sqlite3_log()] to process each logging event. ^If the ** function pointer is NULL, the [sqlite3_log()] interface becomes a no-op. ** ^The void pointer that is the second argument to SQLITE_CONFIG_LOG is ** passed through as the first parameter to the application-defined logger ** function whenever that function is invoked. ^The second parameter to ** the logger function is a copy of the first parameter to the corresponding ** [sqlite3_log()] call and is intended to be a [result code] or an ** [extended result code]. ^The third parameter passed to the logger is ** log message after formatting via [sqlite3_snprintf()]. ** The SQLite logging interface is not reentrant; the logger function ** supplied by the application must not invoke any SQLite interface. ** In a multi-threaded application, the application-defined logger ** function must be threadsafe.
    ** ** [[SQLITE_CONFIG_URI]]
    SQLITE_CONFIG_URI **
    This option takes a single argument of type int. If non-zero, then ** URI handling is globally enabled. If the parameter is zero, then URI handling ** is globally disabled. If URI handling is globally enabled, all filenames ** passed to [sqlite3_open()], [sqlite3_open_v2()], [sqlite3_open16()] or ** specified as part of [ATTACH] commands are interpreted as URIs, regardless ** of whether or not the [SQLITE_OPEN_URI] flag is set when the database ** connection is opened. If it is globally disabled, filenames are ** only interpreted as URIs if the SQLITE_OPEN_URI flag is set when the ** database connection is opened. By default, URI handling is globally ** disabled. The default value may be changed by compiling with the ** [SQLITE_USE_URI] symbol defined. ** ** [[SQLITE_CONFIG_PCACHE]] [[SQLITE_CONFIG_GETPCACHE]] **
    SQLITE_CONFIG_PCACHE and SQLITE_CONFIG_GETPCACHE **
    These options are obsolete and should not be used by new code. ** They are retained for backwards compatibility but are now no-ops. **
    */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ #define SQLITE_CONFIG_SERIALIZED 3 /* nil */ #define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ #define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ #define SQLITE_CONFIG_SCRATCH 6 /* void*, int sz, int N */ #define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ #define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ #define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ #define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ #define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ /* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ #define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ #define SQLITE_CONFIG_PCACHE 14 /* no-op */ #define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ #define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ #define SQLITE_CONFIG_URI 17 /* int */ #define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ /* ** CAPI3REF: Database Connection Configuration Options ** ** These constants are the available integer configuration options that ** can be passed as the second argument to the [sqlite3_db_config()] interface. ** ** New configuration options may be added in future releases of SQLite. ** Existing configuration options might be discontinued. Applications ** should check the return code from [sqlite3_db_config()] to make sure that ** the call worked. ^The [sqlite3_db_config()] interface will return a ** non-zero [error code] if a discontinued or unsupported configuration option ** is invoked. ** **
    **
    SQLITE_DBCONFIG_LOOKASIDE
    **
    ^This option takes three additional arguments that determine the ** [lookaside memory allocator] configuration for the [database connection]. ** ^The first argument (the third parameter to [sqlite3_db_config()] is a ** pointer to a memory buffer to use for lookaside memory. ** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb ** may be NULL in which case SQLite will allocate the ** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the ** size of each lookaside buffer slot. ^The third argument is the number of ** slots. The size of the buffer in the first argument must be greater than ** or equal to the product of the second and third arguments. The buffer ** must be aligned to an 8-byte boundary. ^If the second argument to ** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally ** rounded down to the next smaller multiple of 8. ^(The lookaside memory ** configuration for a database connection can only be changed when that ** connection is not currently using lookaside memory, or in other words ** when the "current value" returned by ** [sqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero. ** Any attempt to change the lookaside memory configuration when lookaside ** memory is in use leaves the configuration unchanged and returns ** [SQLITE_BUSY].)^
    ** **
    SQLITE_DBCONFIG_ENABLE_FKEY
    **
    ^This option is used to enable or disable the enforcement of ** [foreign key constraints]. There should be two additional arguments. ** The first argument is an integer which is 0 to disable FK enforcement, ** positive to enable FK enforcement or negative to leave FK enforcement ** unchanged. The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether FK enforcement is off or on ** following this call. The second parameter may be a NULL pointer, in ** which case the FK enforcement setting is not reported back.
    ** **
    SQLITE_DBCONFIG_ENABLE_TRIGGER
    **
    ^This option is used to enable or disable [CREATE TRIGGER | triggers]. ** There should be two additional arguments. ** The first argument is an integer which is 0 to disable triggers, ** positive to enable triggers or negative to leave the setting unchanged. ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether triggers are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in ** which case the trigger setting is not reported back.
    ** **
    */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ #define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes ** ** ^The sqlite3_extended_result_codes() routine enables or disables the ** [extended result codes] feature of SQLite. ^The extended result ** codes are disabled by default for historical compatibility. */ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); /* ** CAPI3REF: Last Insert Rowid ** ** ^Each entry in an SQLite table has a unique 64-bit signed ** integer key called the [ROWID | "rowid"]. ^The rowid is always available ** as an undeclared column named ROWID, OID, or _ROWID_ as long as those ** names are not also used by explicitly declared columns. ^If ** the table has a column of type [INTEGER PRIMARY KEY] then that column ** is another alias for the rowid. ** ** ^This routine returns the [rowid] of the most recent ** successful [INSERT] into the database from the [database connection] ** in the first argument. ^As of SQLite version 3.7.7, this routines ** records the last insert rowid of both ordinary tables and [virtual tables]. ** ^If no successful [INSERT]s ** have ever occurred on that database connection, zero is returned. ** ** ^(If an [INSERT] occurs within a trigger or within a [virtual table] ** method, then this routine will return the [rowid] of the inserted ** row as long as the trigger or virtual table method is running. ** But once the trigger or virtual table method ends, the value returned ** by this routine reverts to what it was before the trigger or virtual ** table method began.)^ ** ** ^An [INSERT] that fails due to a constraint violation is not a ** successful [INSERT] and does not change the value returned by this ** routine. ^Thus INSERT OR FAIL, INSERT OR IGNORE, INSERT OR ROLLBACK, ** and INSERT OR ABORT make no changes to the return value of this ** routine when their insertion fails. ^(When INSERT OR REPLACE ** encounters a constraint violation, it does not fail. The ** INSERT continues to completion after deleting rows that caused ** the constraint problem so INSERT OR REPLACE will always change ** the return value of this interface.)^ ** ** ^For the purposes of this routine, an [INSERT] is considered to ** be successful even if it is subsequently rolled back. ** ** This function is accessible to SQL statements via the ** [last_insert_rowid() SQL function]. ** ** If a separate thread performs a new [INSERT] on the same ** database connection while the [sqlite3_last_insert_rowid()] ** function is running and thus changes the last insert [rowid], ** then the value returned by [sqlite3_last_insert_rowid()] is ** unpredictable and might not equal either the old or the new ** last insert [rowid]. */ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); /* ** CAPI3REF: Count The Number Of Rows Modified ** ** ^This function returns the number of database rows that were changed ** or inserted or deleted by the most recently completed SQL statement ** on the [database connection] specified by the first parameter. ** ^(Only changes that are directly specified by the [INSERT], [UPDATE], ** or [DELETE] statement are counted. Auxiliary changes caused by ** triggers or [foreign key actions] are not counted.)^ Use the ** [sqlite3_total_changes()] function to find the total number of changes ** including changes caused by triggers and foreign key actions. ** ** ^Changes to a view that are simulated by an [INSTEAD OF trigger] ** are not counted. Only real table changes are counted. ** ** ^(A "row change" is a change to a single row of a single table ** caused by an INSERT, DELETE, or UPDATE statement. Rows that ** are changed as side effects of [REPLACE] constraint resolution, ** rollback, ABORT processing, [DROP TABLE], or by any other ** mechanisms do not count as direct row changes.)^ ** ** A "trigger context" is a scope of execution that begins and ** ends with the script of a [CREATE TRIGGER | trigger]. ** Most SQL statements are ** evaluated outside of any trigger. This is the "top level" ** trigger context. If a trigger fires from the top level, a ** new trigger context is entered for the duration of that one ** trigger. Subtriggers create subcontexts for their duration. ** ** ^Calling [sqlite3_exec()] or [sqlite3_step()] recursively does ** not create a new trigger context. ** ** ^This function returns the number of direct row changes in the ** most recent INSERT, UPDATE, or DELETE statement within the same ** trigger context. ** ** ^Thus, when called from the top level, this function returns the ** number of changes in the most recent INSERT, UPDATE, or DELETE ** that also occurred at the top level. ^(Within the body of a trigger, ** the sqlite3_changes() interface can be called to find the number of ** changes in the most recently completed INSERT, UPDATE, or DELETE ** statement within the body of the same trigger. ** However, the number returned does not include changes ** caused by subtriggers since those have their own context.)^ ** ** See also the [sqlite3_total_changes()] interface, the ** [count_changes pragma], and the [changes() SQL function]. ** ** If a separate thread makes changes on the same database connection ** while [sqlite3_changes()] is running then the value returned ** is unpredictable and not meaningful. */ SQLITE_API int sqlite3_changes(sqlite3*); /* ** CAPI3REF: Total Number Of Rows Modified ** ** ^This function returns the number of row changes caused by [INSERT], ** [UPDATE] or [DELETE] statements since the [database connection] was opened. ** ^(The count returned by sqlite3_total_changes() includes all changes ** from all [CREATE TRIGGER | trigger] contexts and changes made by ** [foreign key actions]. However, ** the count does not include changes used to implement [REPLACE] constraints, ** do rollbacks or ABORT processing, or [DROP TABLE] processing. The ** count does not include rows of views that fire an [INSTEAD OF trigger], ** though if the INSTEAD OF trigger makes changes of its own, those changes ** are counted.)^ ** ^The sqlite3_total_changes() function counts the changes as soon as ** the statement that makes them is completed (when the statement handle ** is passed to [sqlite3_reset()] or [sqlite3_finalize()]). ** ** See also the [sqlite3_changes()] interface, the ** [count_changes pragma], and the [total_changes() SQL function]. ** ** If a separate thread makes changes on the same database connection ** while [sqlite3_total_changes()] is running then the value ** returned is unpredictable and not meaningful. */ SQLITE_API int sqlite3_total_changes(sqlite3*); /* ** CAPI3REF: Interrupt A Long-Running Query ** ** ^This function causes any pending database operation to abort and ** return at its earliest opportunity. This routine is typically ** called in response to a user action such as pressing "Cancel" ** or Ctrl-C where the user wants a long query operation to halt ** immediately. ** ** ^It is safe to call this routine from a thread different from the ** thread that is currently running the database operation. But it ** is not safe to call this routine with a [database connection] that ** is closed or might close before sqlite3_interrupt() returns. ** ** ^If an SQL operation is very nearly finished at the time when ** sqlite3_interrupt() is called, then it might not have an opportunity ** to be interrupted and might continue to completion. ** ** ^An SQL operation that is interrupted will return [SQLITE_INTERRUPT]. ** ^If the interrupted SQL operation is an INSERT, UPDATE, or DELETE ** that is inside an explicit transaction, then the entire transaction ** will be rolled back automatically. ** ** ^The sqlite3_interrupt(D) call is in effect until all currently running ** SQL statements on [database connection] D complete. ^Any new SQL statements ** that are started after the sqlite3_interrupt() call and before the ** running statements reaches zero are interrupted as if they had been ** running prior to the sqlite3_interrupt() call. ^New SQL statements ** that are started after the running statement count reaches zero are ** not effected by the sqlite3_interrupt(). ** ^A call to sqlite3_interrupt(D) that occurs when there are no running ** SQL statements is a no-op and has no effect on SQL statements ** that are started after the sqlite3_interrupt() call returns. ** ** If the database connection closes while [sqlite3_interrupt()] ** is running then bad things will likely happen. */ SQLITE_API void sqlite3_interrupt(sqlite3*); /* ** CAPI3REF: Determine If An SQL Statement Is Complete ** ** These routines are useful during command-line input to determine if the ** currently entered text seems to form a complete SQL statement or ** if additional input is needed before sending the text into ** SQLite for parsing. ^These routines return 1 if the input string ** appears to be a complete SQL statement. ^A statement is judged to be ** complete if it ends with a semicolon token and is not a prefix of a ** well-formed CREATE TRIGGER statement. ^Semicolons that are embedded within ** string literals or quoted identifier names or comments are not ** independent tokens (they are part of the token in which they are ** embedded) and thus do not count as a statement terminator. ^Whitespace ** and comments that follow the final semicolon are ignored. ** ** ^These routines return 0 if the statement is incomplete. ^If a ** memory allocation fails, then SQLITE_NOMEM is returned. ** ** ^These routines do not parse the SQL statements thus ** will not detect syntactically incorrect SQL. ** ** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior ** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked ** automatically by sqlite3_complete16(). If that initialization fails, ** then the return value from sqlite3_complete16() will be non-zero ** regardless of whether or not the input SQL is complete.)^ ** ** The input to [sqlite3_complete()] must be a zero-terminated ** UTF-8 string. ** ** The input to [sqlite3_complete16()] must be a zero-terminated ** UTF-16 string in native byte order. */ SQLITE_API int sqlite3_complete(const char *sql); SQLITE_API int sqlite3_complete16(const void *sql); /* ** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors ** ** ^This routine sets a callback function that might be invoked whenever ** an attempt is made to open a database table that another thread ** or process has locked. ** ** ^If the busy callback is NULL, then [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] ** is returned immediately upon encountering the lock. ^If the busy callback ** is not NULL, then the callback might be invoked with two arguments. ** ** ^The first argument to the busy handler is a copy of the void* pointer which ** is the third argument to sqlite3_busy_handler(). ^The second argument to ** the busy handler callback is the number of times that the busy handler has ** been invoked for this locking event. ^If the ** busy callback returns 0, then no additional attempts are made to ** access the database and [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] is returned. ** ^If the callback returns non-zero, then another attempt ** is made to open the database for reading and the cycle repeats. ** ** The presence of a busy handler does not guarantee that it will be invoked ** when there is lock contention. ^If SQLite determines that invoking the busy ** handler could result in a deadlock, it will go ahead and return [SQLITE_BUSY] ** or [SQLITE_IOERR_BLOCKED] instead of invoking the busy handler. ** Consider a scenario where one process is holding a read lock that ** it is trying to promote to a reserved lock and ** a second process is holding a reserved lock that it is trying ** to promote to an exclusive lock. The first process cannot proceed ** because it is blocked by the second and the second process cannot ** proceed because it is blocked by the first. If both processes ** invoke the busy handlers, neither will make any progress. Therefore, ** SQLite returns [SQLITE_BUSY] for the first process, hoping that this ** will induce the first process to release its read lock and allow ** the second process to proceed. ** ** ^The default busy callback is NULL. ** ** ^The [SQLITE_BUSY] error is converted to [SQLITE_IOERR_BLOCKED] ** when SQLite is in the middle of a large transaction where all the ** changes will not fit into the in-memory cache. SQLite will ** already hold a RESERVED lock on the database file, but it needs ** to promote this lock to EXCLUSIVE so that it can spill cache ** pages into the database file without harm to concurrent ** readers. ^If it is unable to promote the lock, then the in-memory ** cache will be left in an inconsistent state and so the error ** code is promoted from the relatively benign [SQLITE_BUSY] to ** the more severe [SQLITE_IOERR_BLOCKED]. ^This error code promotion ** forces an automatic rollback of the changes. See the ** ** CorruptionFollowingBusyError wiki page for a discussion of why ** this is important. ** ** ^(There can only be a single busy handler defined for each ** [database connection]. Setting a new busy handler clears any ** previously set handler.)^ ^Note that calling [sqlite3_busy_timeout()] ** will also set or clear the busy handler. ** ** The busy callback should not take any actions which modify the ** database connection that invoked the busy handler. Any such actions ** result in undefined behavior. ** ** A busy handler must not close the database connection ** or [prepared statement] that invoked the busy handler. */ SQLITE_API int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*); /* ** CAPI3REF: Set A Busy Timeout ** ** ^This routine sets a [sqlite3_busy_handler | busy handler] that sleeps ** for a specified amount of time when a table is locked. ^The handler ** will sleep multiple times until at least "ms" milliseconds of sleeping ** have accumulated. ^After at least "ms" milliseconds of sleeping, ** the handler returns 0 which causes [sqlite3_step()] to return ** [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED]. ** ** ^Calling this routine with an argument less than or equal to zero ** turns off all busy handlers. ** ** ^(There can only be a single busy handler for a particular ** [database connection] any any given moment. If another busy handler ** was defined (using [sqlite3_busy_handler()]) prior to calling ** this routine, that other busy handler is cleared.)^ */ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); /* ** CAPI3REF: Convenience Routines For Running Queries ** ** This is a legacy interface that is preserved for backwards compatibility. ** Use of this interface is not recommended. ** ** Definition: A result table is memory data structure created by the ** [sqlite3_get_table()] interface. A result table records the ** complete query results from one or more queries. ** ** The table conceptually has a number of rows and columns. But ** these numbers are not part of the result table itself. These ** numbers are obtained separately. Let N be the number of rows ** and M be the number of columns. ** ** A result table is an array of pointers to zero-terminated UTF-8 strings. ** There are (N+1)*M elements in the array. The first M pointers point ** to zero-terminated strings that contain the names of the columns. ** The remaining entries all point to query results. NULL values result ** in NULL pointers. All other values are in their UTF-8 zero-terminated ** string representation as returned by [sqlite3_column_text()]. ** ** A result table might consist of one or more memory allocations. ** It is not safe to pass a result table directly to [sqlite3_free()]. ** A result table should be deallocated using [sqlite3_free_table()]. ** ** ^(As an example of the result table format, suppose a query result ** is as follows: ** **
    **        Name        | Age
    **        -----------------------
    **        Alice       | 43
    **        Bob         | 28
    **        Cindy       | 21
    ** 
    ** ** There are two column (M==2) and three rows (N==3). Thus the ** result table has 8 entries. Suppose the result table is stored ** in an array names azResult. Then azResult holds this content: ** **
    **        azResult[0] = "Name";
    **        azResult[1] = "Age";
    **        azResult[2] = "Alice";
    **        azResult[3] = "43";
    **        azResult[4] = "Bob";
    **        azResult[5] = "28";
    **        azResult[6] = "Cindy";
    **        azResult[7] = "21";
    ** 
    )^ ** ** ^The sqlite3_get_table() function evaluates one or more ** semicolon-separated SQL statements in the zero-terminated UTF-8 ** string of its 2nd parameter and returns a result table to the ** pointer given in its 3rd parameter. ** ** After the application has finished with the result from sqlite3_get_table(), ** it must pass the result table pointer to sqlite3_free_table() in order to ** release the memory that was malloced. Because of the way the ** [sqlite3_malloc()] happens within sqlite3_get_table(), the calling ** function must not try to call [sqlite3_free()] directly. Only ** [sqlite3_free_table()] is able to release the memory properly and safely. ** ** The sqlite3_get_table() interface is implemented as a wrapper around ** [sqlite3_exec()]. The sqlite3_get_table() routine does not have access ** to any internal data structures of SQLite. It uses only the public ** interface defined here. As a consequence, errors that occur in the ** wrapper layer outside of the internal [sqlite3_exec()] call are not ** reflected in subsequent calls to [sqlite3_errcode()] or ** [sqlite3_errmsg()]. */ SQLITE_API int sqlite3_get_table( sqlite3 *db, /* An open database */ const char *zSql, /* SQL to be evaluated */ char ***pazResult, /* Results of the query */ int *pnRow, /* Number of result rows written here */ int *pnColumn, /* Number of result columns written here */ char **pzErrmsg /* Error msg written here */ ); SQLITE_API void sqlite3_free_table(char **result); /* ** CAPI3REF: Formatted String Printing Functions ** ** These routines are work-alikes of the "printf()" family of functions ** from the standard C library. ** ** ^The sqlite3_mprintf() and sqlite3_vmprintf() routines write their ** results into memory obtained from [sqlite3_malloc()]. ** The strings returned by these two routines should be ** released by [sqlite3_free()]. ^Both routines return a ** NULL pointer if [sqlite3_malloc()] is unable to allocate enough ** memory to hold the resulting string. ** ** ^(The sqlite3_snprintf() routine is similar to "snprintf()" from ** the standard C library. The result is written into the ** buffer supplied as the second parameter whose size is given by ** the first parameter. Note that the order of the ** first two parameters is reversed from snprintf().)^ This is an ** historical accident that cannot be fixed without breaking ** backwards compatibility. ^(Note also that sqlite3_snprintf() ** returns a pointer to its buffer instead of the number of ** characters actually written into the buffer.)^ We admit that ** the number of characters written would be a more useful return ** value but we cannot change the implementation of sqlite3_snprintf() ** now without breaking compatibility. ** ** ^As long as the buffer size is greater than zero, sqlite3_snprintf() ** guarantees that the buffer is always zero-terminated. ^The first ** parameter "n" is the total size of the buffer, including space for ** the zero terminator. So the longest string that can be completely ** written will be n-1 characters. ** ** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf(). ** ** These routines all implement some additional formatting ** options that are useful for constructing SQL statements. ** All of the usual printf() formatting options apply. In addition, there ** is are "%q", "%Q", and "%z" options. ** ** ^(The %q option works like %s in that it substitutes a nul-terminated ** string from the argument list. But %q also doubles every '\'' character. ** %q is designed for use inside a string literal.)^ By doubling each '\'' ** character it escapes that character and allows it to be inserted into ** the string. ** ** For example, assume the string variable zText contains text as follows: ** **
    **  char *zText = "It's a happy day!";
    ** 
    ** ** One can use this text in an SQL statement as follows: ** **
    **  char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES('%q')", zText);
    **  sqlite3_exec(db, zSQL, 0, 0, 0);
    **  sqlite3_free(zSQL);
    ** 
    ** ** Because the %q format string is used, the '\'' character in zText ** is escaped and the SQL generated is as follows: ** **
    **  INSERT INTO table1 VALUES('It''s a happy day!')
    ** 
    ** ** This is correct. Had we used %s instead of %q, the generated SQL ** would have looked like this: ** **
    **  INSERT INTO table1 VALUES('It's a happy day!');
    ** 
    ** ** This second example is an SQL syntax error. As a general rule you should ** always use %q instead of %s when inserting text into a string literal. ** ** ^(The %Q option works like %q except it also adds single quotes around ** the outside of the total string. Additionally, if the parameter in the ** argument list is a NULL pointer, %Q substitutes the text "NULL" (without ** single quotes).)^ So, for example, one could say: ** **
    **  char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES(%Q)", zText);
    **  sqlite3_exec(db, zSQL, 0, 0, 0);
    **  sqlite3_free(zSQL);
    ** 
    ** ** The code above will render a correct SQL statement in the zSQL ** variable even if the zText variable is a NULL pointer. ** ** ^(The "%z" formatting option works like "%s" but with the ** addition that after the string has been read and copied into ** the result, [sqlite3_free()] is called on the input string.)^ */ SQLITE_API char *sqlite3_mprintf(const char*,...); SQLITE_API char *sqlite3_vmprintf(const char*, va_list); SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...); SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); /* ** CAPI3REF: Memory Allocation Subsystem ** ** The SQLite core uses these three routines for all of its own ** internal memory allocation needs. "Core" in the previous sentence ** does not include operating-system specific VFS implementation. The ** Windows VFS uses native malloc() and free() for some operations. ** ** ^The sqlite3_malloc() routine returns a pointer to a block ** of memory at least N bytes in length, where N is the parameter. ** ^If sqlite3_malloc() is unable to obtain sufficient free ** memory, it returns a NULL pointer. ^If the parameter N to ** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns ** a NULL pointer. ** ** ^Calling sqlite3_free() with a pointer previously returned ** by sqlite3_malloc() or sqlite3_realloc() releases that memory so ** that it might be reused. ^The sqlite3_free() routine is ** a no-op if is called with a NULL pointer. Passing a NULL pointer ** to sqlite3_free() is harmless. After being freed, memory ** should neither be read nor written. Even reading previously freed ** memory might result in a segmentation fault or other severe error. ** Memory corruption, a segmentation fault, or other severe error ** might result if sqlite3_free() is called with a non-NULL pointer that ** was not obtained from sqlite3_malloc() or sqlite3_realloc(). ** ** ^(The sqlite3_realloc() interface attempts to resize a ** prior memory allocation to be at least N bytes, where N is the ** second parameter. The memory allocation to be resized is the first ** parameter.)^ ^ If the first parameter to sqlite3_realloc() ** is a NULL pointer then its behavior is identical to calling ** sqlite3_malloc(N) where N is the second parameter to sqlite3_realloc(). ** ^If the second parameter to sqlite3_realloc() is zero or ** negative then the behavior is exactly the same as calling ** sqlite3_free(P) where P is the first parameter to sqlite3_realloc(). ** ^sqlite3_realloc() returns a pointer to a memory allocation ** of at least N bytes in size or NULL if sufficient memory is unavailable. ** ^If M is the size of the prior allocation, then min(N,M) bytes ** of the prior allocation are copied into the beginning of buffer returned ** by sqlite3_realloc() and the prior allocation is freed. ** ^If sqlite3_realloc() returns NULL, then the prior allocation ** is not freed. ** ** ^The memory returned by sqlite3_malloc() and sqlite3_realloc() ** is always aligned to at least an 8 byte boundary, or to a ** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time ** option is used. ** ** In SQLite version 3.5.0 and 3.5.1, it was possible to define ** the SQLITE_OMIT_MEMORY_ALLOCATION which would cause the built-in ** implementation of these routines to be omitted. That capability ** is no longer provided. Only built-in memory allocators can be used. ** ** Prior to SQLite version 3.7.10, the Windows OS interface layer called ** the system malloc() and free() directly when converting ** filenames between the UTF-8 encoding used by SQLite ** and whatever filename encoding is used by the particular Windows ** installation. Memory allocation errors were detected, but ** they were reported back as [SQLITE_CANTOPEN] or ** [SQLITE_IOERR] rather than [SQLITE_NOMEM]. ** ** The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()] ** must be either NULL or else pointers obtained from a prior ** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that have ** not yet been released. ** ** The application must not read or write any part of ** a block of memory after it has been released using ** [sqlite3_free()] or [sqlite3_realloc()]. */ SQLITE_API void *sqlite3_malloc(int); SQLITE_API void *sqlite3_realloc(void*, int); SQLITE_API void sqlite3_free(void*); /* ** CAPI3REF: Memory Allocator Statistics ** ** SQLite provides these two interfaces for reporting on the status ** of the [sqlite3_malloc()], [sqlite3_free()], and [sqlite3_realloc()] ** routines, which form the built-in memory allocation subsystem. ** ** ^The [sqlite3_memory_used()] routine returns the number of bytes ** of memory currently outstanding (malloced but not freed). ** ^The [sqlite3_memory_highwater()] routine returns the maximum ** value of [sqlite3_memory_used()] since the high-water mark ** was last reset. ^The values returned by [sqlite3_memory_used()] and ** [sqlite3_memory_highwater()] include any overhead ** added by SQLite in its implementation of [sqlite3_malloc()], ** but not overhead added by the any underlying system library ** routines that [sqlite3_malloc()] may call. ** ** ^The memory high-water mark is reset to the current value of ** [sqlite3_memory_used()] if and only if the parameter to ** [sqlite3_memory_highwater()] is true. ^The value returned ** by [sqlite3_memory_highwater(1)] is the high-water mark ** prior to the reset. */ SQLITE_API sqlite3_int64 sqlite3_memory_used(void); SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag); /* ** CAPI3REF: Pseudo-Random Number Generator ** ** SQLite contains a high-quality pseudo-random number generator (PRNG) used to ** select random [ROWID | ROWIDs] when inserting new records into a table that ** already uses the largest possible [ROWID]. The PRNG is also used for ** the build-in random() and randomblob() SQL functions. This interface allows ** applications to access the same PRNG for other purposes. ** ** ^A call to this routine stores N bytes of randomness into buffer P. ** ** ^The first time this routine is invoked (either internally or by ** the application) the PRNG is seeded using randomness obtained ** from the xRandomness method of the default [sqlite3_vfs] object. ** ^On all subsequent invocations, the pseudo-randomness is generated ** internally and without recourse to the [sqlite3_vfs] xRandomness ** method. */ SQLITE_API void sqlite3_randomness(int N, void *P); /* ** CAPI3REF: Compile-Time Authorization Callbacks ** ** ^This routine registers an authorizer callback with a particular ** [database connection], supplied in the first argument. ** ^The authorizer callback is invoked as SQL statements are being compiled ** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()], ** [sqlite3_prepare16()] and [sqlite3_prepare16_v2()]. ^At various ** points during the compilation process, as logic is being created ** to perform various actions, the authorizer callback is invoked to ** see if those actions are allowed. ^The authorizer callback should ** return [SQLITE_OK] to allow the action, [SQLITE_IGNORE] to disallow the ** specific action but allow the SQL statement to continue to be ** compiled, or [SQLITE_DENY] to cause the entire SQL statement to be ** rejected with an error. ^If the authorizer callback returns ** any value other than [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY] ** then the [sqlite3_prepare_v2()] or equivalent call that triggered ** the authorizer will fail with an error message. ** ** When the callback returns [SQLITE_OK], that means the operation ** requested is ok. ^When the callback returns [SQLITE_DENY], the ** [sqlite3_prepare_v2()] or equivalent call that triggered the ** authorizer will fail with an error message explaining that ** access is denied. ** ** ^The first parameter to the authorizer callback is a copy of the third ** parameter to the sqlite3_set_authorizer() interface. ^The second parameter ** to the callback is an integer [SQLITE_COPY | action code] that specifies ** the particular action to be authorized. ^The third through sixth parameters ** to the callback are zero-terminated strings that contain additional ** details about the action to be authorized. ** ** ^If the action code is [SQLITE_READ] ** and the callback returns [SQLITE_IGNORE] then the ** [prepared statement] statement is constructed to substitute ** a NULL value in place of the table column that would have ** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE] ** return can be used to deny an untrusted user access to individual ** columns of a table. ** ^If the action code is [SQLITE_DELETE] and the callback returns ** [SQLITE_IGNORE] then the [DELETE] operation proceeds but the ** [truncate optimization] is disabled and all rows are deleted individually. ** ** An authorizer is used when [sqlite3_prepare | preparing] ** SQL statements from an untrusted source, to ensure that the SQL statements ** do not try to access data they are not allowed to see, or that they do not ** try to execute malicious statements that damage the database. For ** example, an application may allow a user to enter arbitrary ** SQL queries for evaluation by a database. But the application does ** not want the user to be able to make arbitrary changes to the ** database. An authorizer could then be put in place while the ** user-entered SQL is being [sqlite3_prepare | prepared] that ** disallows everything except [SELECT] statements. ** ** Applications that need to process SQL from untrusted sources ** might also consider lowering resource limits using [sqlite3_limit()] ** and limiting database size using the [max_page_count] [PRAGMA] ** in addition to using an authorizer. ** ** ^(Only a single authorizer can be in place on a database connection ** at a time. Each call to sqlite3_set_authorizer overrides the ** previous call.)^ ^Disable the authorizer by installing a NULL callback. ** The authorizer is disabled by default. ** ** The authorizer callback must not do anything that will modify ** the database connection that invoked the authorizer callback. ** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their ** database connections for the meaning of "modify" in this paragraph. ** ** ^When [sqlite3_prepare_v2()] is used to prepare a statement, the ** statement might be re-prepared during [sqlite3_step()] due to a ** schema change. Hence, the application should ensure that the ** correct authorizer callback remains in place during the [sqlite3_step()]. ** ** ^Note that the authorizer callback is invoked only during ** [sqlite3_prepare()] or its variants. Authorization is not ** performed during statement evaluation in [sqlite3_step()], unless ** as stated in the previous paragraph, sqlite3_step() invokes ** sqlite3_prepare_v2() to reprepare a statement after a schema change. */ SQLITE_API int sqlite3_set_authorizer( sqlite3*, int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), void *pUserData ); /* ** CAPI3REF: Authorizer Return Codes ** ** The [sqlite3_set_authorizer | authorizer callback function] must ** return either [SQLITE_OK] or one of these two constants in order ** to signal SQLite whether or not the action is permitted. See the ** [sqlite3_set_authorizer | authorizer documentation] for additional ** information. ** ** Note that SQLITE_IGNORE is also used as a [SQLITE_ROLLBACK | return code] ** from the [sqlite3_vtab_on_conflict()] interface. */ #define SQLITE_DENY 1 /* Abort the SQL statement with an error */ #define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ /* ** CAPI3REF: Authorizer Action Codes ** ** The [sqlite3_set_authorizer()] interface registers a callback function ** that is invoked to authorize certain SQL statement actions. The ** second parameter to the callback is an integer code that specifies ** what action is being authorized. These are the integer action codes that ** the authorizer callback may be passed. ** ** These action code values signify what kind of operation is to be ** authorized. The 3rd and 4th parameters to the authorization ** callback function will be parameters or NULL depending on which of these ** codes is used as the second parameter. ^(The 5th parameter to the ** authorizer callback is the name of the database ("main", "temp", ** etc.) if applicable.)^ ^The 6th parameter to the authorizer callback ** is the name of the inner-most trigger or view that is responsible for ** the access attempt or NULL if this access attempt is directly from ** top-level SQL code. */ /******************************************* 3rd ************ 4th ***********/ #define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */ #define SQLITE_CREATE_TABLE 2 /* Table Name NULL */ #define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */ #define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */ #define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */ #define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */ #define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */ #define SQLITE_CREATE_VIEW 8 /* View Name NULL */ #define SQLITE_DELETE 9 /* Table Name NULL */ #define SQLITE_DROP_INDEX 10 /* Index Name Table Name */ #define SQLITE_DROP_TABLE 11 /* Table Name NULL */ #define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */ #define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */ #define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */ #define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */ #define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */ #define SQLITE_DROP_VIEW 17 /* View Name NULL */ #define SQLITE_INSERT 18 /* Table Name NULL */ #define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */ #define SQLITE_READ 20 /* Table Name Column Name */ #define SQLITE_SELECT 21 /* NULL NULL */ #define SQLITE_TRANSACTION 22 /* Operation NULL */ #define SQLITE_UPDATE 23 /* Table Name Column Name */ #define SQLITE_ATTACH 24 /* Filename NULL */ #define SQLITE_DETACH 25 /* Database Name NULL */ #define SQLITE_ALTER_TABLE 26 /* Database Name Table Name */ #define SQLITE_REINDEX 27 /* Index Name NULL */ #define SQLITE_ANALYZE 28 /* Table Name NULL */ #define SQLITE_CREATE_VTABLE 29 /* Table Name Module Name */ #define SQLITE_DROP_VTABLE 30 /* Table Name Module Name */ #define SQLITE_FUNCTION 31 /* NULL Function Name */ #define SQLITE_SAVEPOINT 32 /* Operation Savepoint Name */ #define SQLITE_COPY 0 /* No longer used */ /* ** CAPI3REF: Tracing And Profiling Functions ** ** These routines register callback functions that can be used for ** tracing and profiling the execution of SQL statements. ** ** ^The callback function registered by sqlite3_trace() is invoked at ** various times when an SQL statement is being run by [sqlite3_step()]. ** ^The sqlite3_trace() callback is invoked with a UTF-8 rendering of the ** SQL statement text as the statement first begins executing. ** ^(Additional sqlite3_trace() callbacks might occur ** as each triggered subprogram is entered. The callbacks for triggers ** contain a UTF-8 SQL comment that identifies the trigger.)^ ** ** ^The callback function registered by sqlite3_profile() is invoked ** as each SQL statement finishes. ^The profile callback contains ** the original statement text and an estimate of wall-clock time ** of how long that statement took to run. ^The profile callback ** time is in units of nanoseconds, however the current implementation ** is only capable of millisecond resolution so the six least significant ** digits in the time are meaningless. Future versions of SQLite ** might provide greater resolution on the profiler callback. The ** sqlite3_profile() function is considered experimental and is ** subject to change in future versions of SQLite. */ SQLITE_API void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*); SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_profile(sqlite3*, void(*xProfile)(void*,const char*,sqlite3_uint64), void*); /* ** CAPI3REF: Query Progress Callbacks ** ** ^The sqlite3_progress_handler(D,N,X,P) interface causes the callback ** function X to be invoked periodically during long running calls to ** [sqlite3_exec()], [sqlite3_step()] and [sqlite3_get_table()] for ** database connection D. An example use for this ** interface is to keep a GUI updated during a large query. ** ** ^The parameter P is passed through as the only parameter to the ** callback function X. ^The parameter N is the number of ** [virtual machine instructions] that are evaluated between successive ** invocations of the callback X. ** ** ^Only a single progress handler may be defined at one time per ** [database connection]; setting a new progress handler cancels the ** old one. ^Setting parameter X to NULL disables the progress handler. ** ^The progress handler is also disabled by setting N to a value less ** than 1. ** ** ^If the progress callback returns non-zero, the operation is ** interrupted. This feature can be used to implement a ** "Cancel" button on a GUI progress dialog box. ** ** The progress handler callback must not do anything that will modify ** the database connection that invoked the progress handler. ** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their ** database connections for the meaning of "modify" in this paragraph. ** */ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); /* ** CAPI3REF: Opening A New Database Connection ** ** ^These routines open an SQLite database file as specified by the ** filename argument. ^The filename argument is interpreted as UTF-8 for ** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte ** order for sqlite3_open16(). ^(A [database connection] handle is usually ** returned in *ppDb, even if an error occurs. The only exception is that ** if SQLite is unable to allocate memory to hold the [sqlite3] object, ** a NULL will be written into *ppDb instead of a pointer to the [sqlite3] ** object.)^ ^(If the database is opened (and/or created) successfully, then ** [SQLITE_OK] is returned. Otherwise an [error code] is returned.)^ ^The ** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain ** an English language description of the error following a failure of any ** of the sqlite3_open() routines. ** ** ^The default encoding for the database will be UTF-8 if ** sqlite3_open() or sqlite3_open_v2() is called and ** UTF-16 in the native byte order if sqlite3_open16() is used. ** ** Whether or not an error occurs when it is opened, resources ** associated with the [database connection] handle should be released by ** passing it to [sqlite3_close()] when it is no longer required. ** ** The sqlite3_open_v2() interface works like sqlite3_open() ** except that it accepts two additional parameters for additional control ** over the new database connection. ^(The flags parameter to ** sqlite3_open_v2() can take one of ** the following three values, optionally combined with the ** [SQLITE_OPEN_NOMUTEX], [SQLITE_OPEN_FULLMUTEX], [SQLITE_OPEN_SHAREDCACHE], ** [SQLITE_OPEN_PRIVATECACHE], and/or [SQLITE_OPEN_URI] flags:)^ ** **
    ** ^(
    [SQLITE_OPEN_READONLY]
    **
    The database is opened in read-only mode. If the database does not ** already exist, an error is returned.
    )^ ** ** ^(
    [SQLITE_OPEN_READWRITE]
    **
    The database is opened for reading and writing if possible, or reading ** only if the file is write protected by the operating system. In either ** case the database must already exist, otherwise an error is returned.
    )^ ** ** ^(
    [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]
    **
    The database is opened for reading and writing, and is created if ** it does not already exist. This is the behavior that is always used for ** sqlite3_open() and sqlite3_open16().
    )^ **
    ** ** If the 3rd parameter to sqlite3_open_v2() is not one of the ** combinations shown above optionally combined with other ** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits] ** then the behavior is undefined. ** ** ^If the [SQLITE_OPEN_NOMUTEX] flag is set, then the database connection ** opens in the multi-thread [threading mode] as long as the single-thread ** mode has not been set at compile-time or start-time. ^If the ** [SQLITE_OPEN_FULLMUTEX] flag is set then the database connection opens ** in the serialized [threading mode] unless single-thread was ** previously selected at compile-time or start-time. ** ^The [SQLITE_OPEN_SHAREDCACHE] flag causes the database connection to be ** eligible to use [shared cache mode], regardless of whether or not shared ** cache is enabled using [sqlite3_enable_shared_cache()]. ^The ** [SQLITE_OPEN_PRIVATECACHE] flag causes the database connection to not ** participate in [shared cache mode] even if it is enabled. ** ** ^The fourth parameter to sqlite3_open_v2() is the name of the ** [sqlite3_vfs] object that defines the operating system interface that ** the new database connection should use. ^If the fourth parameter is ** a NULL pointer then the default [sqlite3_vfs] object is used. ** ** ^If the filename is ":memory:", then a private, temporary in-memory database ** is created for the connection. ^This in-memory database will vanish when ** the database connection is closed. Future versions of SQLite might ** make use of additional special filenames that begin with the ":" character. ** It is recommended that when a database filename actually does begin with ** a ":" character you should prefix the filename with a pathname such as ** "./" to avoid ambiguity. ** ** ^If the filename is an empty string, then a private, temporary ** on-disk database will be created. ^This private database will be ** automatically deleted as soon as the database connection is closed. ** ** [[URI filenames in sqlite3_open()]]

    URI Filenames

    ** ** ^If [URI filename] interpretation is enabled, and the filename argument ** begins with "file:", then the filename is interpreted as a URI. ^URI ** filename interpretation is enabled if the [SQLITE_OPEN_URI] flag is ** set in the fourth argument to sqlite3_open_v2(), or if it has ** been enabled globally using the [SQLITE_CONFIG_URI] option with the ** [sqlite3_config()] method or by the [SQLITE_USE_URI] compile-time option. ** As of SQLite version 3.7.7, URI filename interpretation is turned off ** by default, but future releases of SQLite might enable URI filename ** interpretation by default. See "[URI filenames]" for additional ** information. ** ** URI filenames are parsed according to RFC 3986. ^If the URI contains an ** authority, then it must be either an empty string or the string ** "localhost". ^If the authority is not an empty string or "localhost", an ** error is returned to the caller. ^The fragment component of a URI, if ** present, is ignored. ** ** ^SQLite uses the path component of the URI as the name of the disk file ** which contains the database. ^If the path begins with a '/' character, ** then it is interpreted as an absolute path. ^If the path does not begin ** with a '/' (meaning that the authority section is omitted from the URI) ** then the path is interpreted as a relative path. ** ^On windows, the first component of an absolute path ** is a drive specification (e.g. "C:"). ** ** [[core URI query parameters]] ** The query component of a URI may contain parameters that are interpreted ** either by SQLite itself, or by a [VFS | custom VFS implementation]. ** SQLite interprets the following three query parameters: ** **
      **
    • vfs: ^The "vfs" parameter may be used to specify the name of ** a VFS object that provides the operating system interface that should ** be used to access the database file on disk. ^If this option is set to ** an empty string the default VFS object is used. ^Specifying an unknown ** VFS is an error. ^If sqlite3_open_v2() is used and the vfs option is ** present, then the VFS specified by the option takes precedence over ** the value passed as the fourth parameter to sqlite3_open_v2(). ** **
    • mode: ^(The mode parameter may be set to either "ro", "rw", ** "rwc", or "memory". Attempting to set it to any other value is ** an error)^. ** ^If "ro" is specified, then the database is opened for read-only ** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the ** third argument to sqlite3_prepare_v2(). ^If the mode option is set to ** "rw", then the database is opened for read-write (but not create) ** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had ** been set. ^Value "rwc" is equivalent to setting both ** SQLITE_OPEN_READWRITE and SQLITE_OPEN_CREATE. ^If the mode option is ** set to "memory" then a pure [in-memory database] that never reads ** or writes from disk is used. ^It is an error to specify a value for ** the mode parameter that is less restrictive than that specified by ** the flags passed in the third parameter to sqlite3_open_v2(). ** **
    • cache: ^The cache parameter may be set to either "shared" or ** "private". ^Setting it to "shared" is equivalent to setting the ** SQLITE_OPEN_SHAREDCACHE bit in the flags argument passed to ** sqlite3_open_v2(). ^Setting the cache parameter to "private" is ** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit. ** ^If sqlite3_open_v2() is used and the "cache" parameter is present in ** a URI filename, its value overrides any behaviour requested by setting ** SQLITE_OPEN_PRIVATECACHE or SQLITE_OPEN_SHAREDCACHE flag. **
    ** ** ^Specifying an unknown parameter in the query component of a URI is not an ** error. Future versions of SQLite might understand additional query ** parameters. See "[query parameters with special meaning to SQLite]" for ** additional information. ** ** [[URI filename examples]]

    URI filename examples

    ** ** **
    URI filenames Results **
    file:data.db ** Open the file "data.db" in the current directory. **
    file:/home/fred/data.db
    ** file:///home/fred/data.db
    ** file://localhost/home/fred/data.db
    ** Open the database file "/home/fred/data.db". **
    file://darkstar/home/fred/data.db ** An error. "darkstar" is not a recognized authority. **
    ** file:///C:/Documents%20and%20Settings/fred/Desktop/data.db ** Windows only: Open the file "data.db" on fred's desktop on drive ** C:. Note that the %20 escaping in this example is not strictly ** necessary - space characters can be used literally ** in URI filenames. **
    file:data.db?mode=ro&cache=private ** Open file "data.db" in the current directory for read-only access. ** Regardless of whether or not shared-cache mode is enabled by ** default, use a private cache. **
    file:/home/fred/data.db?vfs=unix-nolock ** Open file "/home/fred/data.db". Use the special VFS "unix-nolock". **
    file:data.db?mode=readonly ** An error. "readonly" is not a valid option for the "mode" parameter. **
    ** ** ^URI hexadecimal escape sequences (%HH) are supported within the path and ** query components of a URI. A hexadecimal escape sequence consists of a ** percent sign - "%" - followed by exactly two hexadecimal digits ** specifying an octet value. ^Before the path or query components of a ** URI filename are interpreted, they are encoded using UTF-8 and all ** hexadecimal escape sequences replaced by a single byte containing the ** corresponding octet. If this process generates an invalid UTF-8 encoding, ** the results are undefined. ** ** Note to Windows users: The encoding used for the filename argument ** of sqlite3_open() and sqlite3_open_v2() must be UTF-8, not whatever ** codepage is currently defined. Filenames containing international ** characters must be converted to UTF-8 prior to passing them into ** sqlite3_open() or sqlite3_open_v2(). ** ** Note to Windows Runtime users: The temporary directory must be set ** prior to calling sqlite3_open() or sqlite3_open_v2(). Otherwise, various ** features that require the use of temporary files may fail. ** ** See also: [sqlite3_temp_directory] */ SQLITE_API int sqlite3_open( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb /* OUT: SQLite db handle */ ); SQLITE_API int sqlite3_open16( const void *filename, /* Database filename (UTF-16) */ sqlite3 **ppDb /* OUT: SQLite db handle */ ); SQLITE_API int sqlite3_open_v2( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb, /* OUT: SQLite db handle */ int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ); /* ** CAPI3REF: Obtain Values For URI Parameters ** ** These are utility routines, useful to VFS implementations, that check ** to see if a database file was a URI that contained a specific query ** parameter, and if so obtains the value of that query parameter. ** ** If F is the database filename pointer passed into the xOpen() method of ** a VFS implementation when the flags parameter to xOpen() has one or ** more of the [SQLITE_OPEN_URI] or [SQLITE_OPEN_MAIN_DB] bits set and ** P is the name of the query parameter, then ** sqlite3_uri_parameter(F,P) returns the value of the P ** parameter if it exists or a NULL pointer if P does not appear as a ** query parameter on F. If P is a query parameter of F ** has no explicit value, then sqlite3_uri_parameter(F,P) returns ** a pointer to an empty string. ** ** The sqlite3_uri_boolean(F,P,B) routine assumes that P is a boolean ** parameter and returns true (1) or false (0) according to the value ** of P. The sqlite3_uri_boolean(F,P,B) routine returns true (1) if the ** value of query parameter P is one of "yes", "true", or "on" in any ** case or if the value begins with a non-zero number. The ** sqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of ** query parameter P is one of "no", "false", or "off" in any case or ** if the value begins with a numeric zero. If P is not a query ** parameter on F or if the value of P is does not match any of the ** above, then sqlite3_uri_boolean(F,P,B) returns (B!=0). ** ** The sqlite3_uri_int64(F,P,D) routine converts the value of P into a ** 64-bit signed integer and returns that integer, or D if P does not ** exist. If the value of P is something other than an integer, then ** zero is returned. ** ** If F is a NULL pointer, then sqlite3_uri_parameter(F,P) returns NULL and ** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and ** is not a database file pathname pointer that SQLite passed into the xOpen ** VFS method, then the behavior of this routine is undefined and probably ** undesirable. */ SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam); SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault); SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64); /* ** CAPI3REF: Error Codes And Messages ** ** ^The sqlite3_errcode() interface returns the numeric [result code] or ** [extended result code] for the most recent failed sqlite3_* API call ** associated with a [database connection]. If a prior API call failed ** but the most recent API call succeeded, the return value from ** sqlite3_errcode() is undefined. ^The sqlite3_extended_errcode() ** interface is the same except that it always returns the ** [extended result code] even when extended result codes are ** disabled. ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language ** text that describes the error, as either UTF-8 or UTF-16 respectively. ** ^(Memory to hold the error message string is managed internally. ** The application does not need to worry about freeing the result. ** However, the error string might be overwritten or deallocated by ** subsequent calls to other SQLite interface functions.)^ ** ** When the serialized [threading mode] is in use, it might be the ** case that a second error occurs on a separate thread in between ** the time of the first error and the call to these interfaces. ** When that happens, the second error will be reported since these ** interfaces always report the most recent result. To avoid ** this, each thread can obtain exclusive use of the [database connection] D ** by invoking [sqlite3_mutex_enter]([sqlite3_db_mutex](D)) before beginning ** to use D and invoking [sqlite3_mutex_leave]([sqlite3_db_mutex](D)) after ** all calls to the interfaces listed here are completed. ** ** If an interface fails with SQLITE_MISUSE, that means the interface ** was invoked incorrectly by the application. In that case, the ** error code and message may or may not be set. */ SQLITE_API int sqlite3_errcode(sqlite3 *db); SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); SQLITE_API const char *sqlite3_errmsg(sqlite3*); SQLITE_API const void *sqlite3_errmsg16(sqlite3*); /* ** CAPI3REF: SQL Statement Object ** KEYWORDS: {prepared statement} {prepared statements} ** ** An instance of this object represents a single SQL statement. ** This object is variously known as a "prepared statement" or a ** "compiled SQL statement" or simply as a "statement". ** ** The life of a statement object goes something like this: ** **
      **
    1. Create the object using [sqlite3_prepare_v2()] or a related ** function. **
    2. Bind values to [host parameters] using the sqlite3_bind_*() ** interfaces. **
    3. Run the SQL by calling [sqlite3_step()] one or more times. **
    4. Reset the statement using [sqlite3_reset()] then go back ** to step 2. Do this zero or more times. **
    5. Destroy the object using [sqlite3_finalize()]. **
    ** ** Refer to documentation on individual methods above for additional ** information. */ typedef struct sqlite3_stmt sqlite3_stmt; /* ** CAPI3REF: Run-time Limits ** ** ^(This interface allows the size of various constructs to be limited ** on a connection by connection basis. The first parameter is the ** [database connection] whose limit is to be set or queried. The ** second parameter is one of the [limit categories] that define a ** class of constructs to be size limited. The third parameter is the ** new limit for that construct.)^ ** ** ^If the new limit is a negative number, the limit is unchanged. ** ^(For each limit category SQLITE_LIMIT_NAME there is a ** [limits | hard upper bound] ** set at compile-time by a C preprocessor macro called ** [limits | SQLITE_MAX_NAME]. ** (The "_LIMIT_" in the name is changed to "_MAX_".))^ ** ^Attempts to increase a limit above its hard upper bound are ** silently truncated to the hard upper bound. ** ** ^Regardless of whether or not the limit was changed, the ** [sqlite3_limit()] interface returns the prior value of the limit. ** ^Hence, to find the current value of a limit without changing it, ** simply invoke this interface with the third parameter set to -1. ** ** Run-time limits are intended for use in applications that manage ** both their own internal database and also databases that are controlled ** by untrusted external sources. An example application might be a ** web browser that has its own databases for storing history and ** separate databases controlled by JavaScript applications downloaded ** off the Internet. The internal databases can be given the ** large, default limits. Databases managed by external sources can ** be given much smaller limits designed to prevent a denial of service ** attack. Developers might also want to use the [sqlite3_set_authorizer()] ** interface to further control untrusted SQL. The size of the database ** created by an untrusted script can be contained using the ** [max_page_count] [PRAGMA]. ** ** New run-time limit categories may be added in future releases. */ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); /* ** CAPI3REF: Run-Time Limit Categories ** KEYWORDS: {limit category} {*limit categories} ** ** These constants define various performance limits ** that can be lowered at run-time using [sqlite3_limit()]. ** The synopsis of the meanings of the various limits is shown below. ** Additional information is available at [limits | Limits in SQLite]. ** **
    ** [[SQLITE_LIMIT_LENGTH]] ^(
    SQLITE_LIMIT_LENGTH
    **
    The maximum size of any string or BLOB or table row, in bytes.
    )^ ** ** [[SQLITE_LIMIT_SQL_LENGTH]] ^(
    SQLITE_LIMIT_SQL_LENGTH
    **
    The maximum length of an SQL statement, in bytes.
    )^ ** ** [[SQLITE_LIMIT_COLUMN]] ^(
    SQLITE_LIMIT_COLUMN
    **
    The maximum number of columns in a table definition or in the ** result set of a [SELECT] or the maximum number of columns in an index ** or in an ORDER BY or GROUP BY clause.
    )^ ** ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(
    SQLITE_LIMIT_EXPR_DEPTH
    **
    The maximum depth of the parse tree on any expression.
    )^ ** ** [[SQLITE_LIMIT_COMPOUND_SELECT]] ^(
    SQLITE_LIMIT_COMPOUND_SELECT
    **
    The maximum number of terms in a compound SELECT statement.
    )^ ** ** [[SQLITE_LIMIT_VDBE_OP]] ^(
    SQLITE_LIMIT_VDBE_OP
    **
    The maximum number of instructions in a virtual machine program ** used to implement an SQL statement. This limit is not currently ** enforced, though that might be added in some future release of ** SQLite.
    )^ ** ** [[SQLITE_LIMIT_FUNCTION_ARG]] ^(
    SQLITE_LIMIT_FUNCTION_ARG
    **
    The maximum number of arguments on a function.
    )^ ** ** [[SQLITE_LIMIT_ATTACHED]] ^(
    SQLITE_LIMIT_ATTACHED
    **
    The maximum number of [ATTACH | attached databases].)^
    ** ** [[SQLITE_LIMIT_LIKE_PATTERN_LENGTH]] ** ^(
    SQLITE_LIMIT_LIKE_PATTERN_LENGTH
    **
    The maximum length of the pattern argument to the [LIKE] or ** [GLOB] operators.
    )^ ** ** [[SQLITE_LIMIT_VARIABLE_NUMBER]] ** ^(
    SQLITE_LIMIT_VARIABLE_NUMBER
    **
    The maximum index number of any [parameter] in an SQL statement.)^ ** ** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(
    SQLITE_LIMIT_TRIGGER_DEPTH
    **
    The maximum depth of recursion for triggers.
    )^ **
    */ #define SQLITE_LIMIT_LENGTH 0 #define SQLITE_LIMIT_SQL_LENGTH 1 #define SQLITE_LIMIT_COLUMN 2 #define SQLITE_LIMIT_EXPR_DEPTH 3 #define SQLITE_LIMIT_COMPOUND_SELECT 4 #define SQLITE_LIMIT_VDBE_OP 5 #define SQLITE_LIMIT_FUNCTION_ARG 6 #define SQLITE_LIMIT_ATTACHED 7 #define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 #define SQLITE_LIMIT_VARIABLE_NUMBER 9 #define SQLITE_LIMIT_TRIGGER_DEPTH 10 /* ** CAPI3REF: Compiling An SQL Statement ** KEYWORDS: {SQL statement compiler} ** ** To execute an SQL query, it must first be compiled into a byte-code ** program using one of these routines. ** ** The first argument, "db", is a [database connection] obtained from a ** prior successful call to [sqlite3_open()], [sqlite3_open_v2()] or ** [sqlite3_open16()]. The database connection must not have been closed. ** ** The second argument, "zSql", is the statement to be compiled, encoded ** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2() ** interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2() ** use UTF-16. ** ** ^If the nByte argument is less than zero, then zSql is read up to the ** first zero terminator. ^If nByte is non-negative, then it is the maximum ** number of bytes read from zSql. ^When nByte is non-negative, the ** zSql string ends at either the first '\000' or '\u0000' character or ** the nByte-th byte, whichever comes first. If the caller knows ** that the supplied string is nul-terminated, then there is a small ** performance advantage to be gained by passing an nByte parameter that ** is equal to the number of bytes in the input string including ** the nul-terminator bytes as this saves SQLite from having to ** make a copy of the input string. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte ** past the end of the first SQL statement in zSql. These routines only ** compile the first statement in zSql, so *pzTail is left pointing to ** what remains uncompiled. ** ** ^*ppStmt is left pointing to a compiled [prepared statement] that can be ** executed using [sqlite3_step()]. ^If there is an error, *ppStmt is set ** to NULL. ^If the input text contains no SQL (if the input is an empty ** string or a comment) then *ppStmt is set to NULL. ** The calling procedure is responsible for deleting the compiled ** SQL statement using [sqlite3_finalize()] after it has finished with it. ** ppStmt may not be NULL. ** ** ^On success, the sqlite3_prepare() family of routines return [SQLITE_OK]; ** otherwise an [error code] is returned. ** ** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are ** recommended for all new programs. The two older interfaces are retained ** for backwards compatibility, but their use is discouraged. ** ^In the "v2" interfaces, the prepared statement ** that is returned (the [sqlite3_stmt] object) contains a copy of the ** original SQL text. This causes the [sqlite3_step()] interface to ** behave differently in three ways: ** **
      **
    1. ** ^If the database schema changes, instead of returning [SQLITE_SCHEMA] as it ** always used to do, [sqlite3_step()] will automatically recompile the SQL ** statement and try to run it again. **
    2. ** **
    3. ** ^When an error occurs, [sqlite3_step()] will return one of the detailed ** [error codes] or [extended error codes]. ^The legacy behavior was that ** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code ** and the application would have to make a second call to [sqlite3_reset()] ** in order to find the underlying cause of the problem. With the "v2" prepare ** interfaces, the underlying reason for the error is returned immediately. **
    4. ** **
    5. ** ^If the specific value bound to [parameter | host parameter] in the ** WHERE clause might influence the choice of query plan for a statement, ** then the statement will be automatically recompiled, as if there had been ** a schema change, on the first [sqlite3_step()] call following any change ** to the [sqlite3_bind_text | bindings] of that [parameter]. ** ^The specific value of WHERE-clause [parameter] might influence the ** choice of query plan if the parameter is the left-hand side of a [LIKE] ** or [GLOB] operator or if the parameter is compared to an indexed column ** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. ** the **
    6. **
    */ SQLITE_API int sqlite3_prepare( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ ); SQLITE_API int sqlite3_prepare_v2( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ ); SQLITE_API int sqlite3_prepare16( sqlite3 *db, /* Database handle */ const void *zSql, /* SQL statement, UTF-16 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const void **pzTail /* OUT: Pointer to unused portion of zSql */ ); SQLITE_API int sqlite3_prepare16_v2( sqlite3 *db, /* Database handle */ const void *zSql, /* SQL statement, UTF-16 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const void **pzTail /* OUT: Pointer to unused portion of zSql */ ); /* ** CAPI3REF: Retrieving Statement SQL ** ** ^This interface can be used to retrieve a saved copy of the original ** SQL text used to create a [prepared statement] if that statement was ** compiled using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()]. */ SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); /* ** CAPI3REF: Determine If An SQL Statement Writes The Database ** ** ^The sqlite3_stmt_readonly(X) interface returns true (non-zero) if ** and only if the [prepared statement] X makes no direct changes to ** the content of the database file. ** ** Note that [application-defined SQL functions] or ** [virtual tables] might change the database indirectly as a side effect. ** ^(For example, if an application defines a function "eval()" that ** calls [sqlite3_exec()], then the following SQL statement would ** change the database file through side-effects: ** **
    **    SELECT eval('DELETE FROM t1') FROM t2;
    ** 
    ** ** But because the [SELECT] statement does not change the database file ** directly, sqlite3_stmt_readonly() would still return true.)^ ** ** ^Transaction control statements such as [BEGIN], [COMMIT], [ROLLBACK], ** [SAVEPOINT], and [RELEASE] cause sqlite3_stmt_readonly() to return true, ** since the statements themselves do not actually modify the database but ** rather they control the timing of when other statements modify the ** database. ^The [ATTACH] and [DETACH] statements also cause ** sqlite3_stmt_readonly() to return true since, while those statements ** change the configuration of a database connection, they do not make ** changes to the content of the database files on disk. */ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); /* ** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** ** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the ** [prepared statement] S has been stepped at least once using ** [sqlite3_step(S)] but has not run to completion and/or has not ** been reset using [sqlite3_reset(S)]. ^The sqlite3_stmt_busy(S) ** interface returns false if S is a NULL pointer. If S is not a ** NULL pointer and is not a pointer to a valid [prepared statement] ** object, then the behavior is undefined and probably undesirable. ** ** This interface can be used in combination [sqlite3_next_stmt()] ** to locate all prepared statements associated with a database ** connection that are in need of being reset. This can be used, ** for example, in diagnostic routines to search for prepared ** statements that are holding a transaction open. */ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); /* ** CAPI3REF: Dynamically Typed Value Object ** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value} ** ** SQLite uses the sqlite3_value object to represent all values ** that can be stored in a database table. SQLite uses dynamic typing ** for the values it stores. ^Values stored in sqlite3_value objects ** can be integers, floating point values, strings, BLOBs, or NULL. ** ** An sqlite3_value object may be either "protected" or "unprotected". ** Some interfaces require a protected sqlite3_value. Other interfaces ** will accept either a protected or an unprotected sqlite3_value. ** Every interface that accepts sqlite3_value arguments specifies ** whether or not it requires a protected sqlite3_value. ** ** The terms "protected" and "unprotected" refer to whether or not ** a mutex is held. An internal mutex is held for a protected ** sqlite3_value object but no mutex is held for an unprotected ** sqlite3_value object. If SQLite is compiled to be single-threaded ** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0) ** or if SQLite is run in one of reduced mutex modes ** [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD] ** then there is no distinction between protected and unprotected ** sqlite3_value objects and they can be used interchangeably. However, ** for maximum code portability it is recommended that applications ** still make the distinction between protected and unprotected ** sqlite3_value objects even when not strictly required. ** ** ^The sqlite3_value objects that are passed as parameters into the ** implementation of [application-defined SQL functions] are protected. ** ^The sqlite3_value object returned by ** [sqlite3_column_value()] is unprotected. ** Unprotected sqlite3_value objects may only be used with ** [sqlite3_result_value()] and [sqlite3_bind_value()]. ** The [sqlite3_value_blob | sqlite3_value_type()] family of ** interfaces require protected sqlite3_value objects. */ typedef struct Mem sqlite3_value; /* ** CAPI3REF: SQL Function Context Object ** ** The context in which an SQL function executes is stored in an ** sqlite3_context object. ^A pointer to an sqlite3_context object ** is always first parameter to [application-defined SQL functions]. ** The application-defined SQL function implementation will pass this ** pointer through into calls to [sqlite3_result_int | sqlite3_result()], ** [sqlite3_aggregate_context()], [sqlite3_user_data()], ** [sqlite3_context_db_handle()], [sqlite3_get_auxdata()], ** and/or [sqlite3_set_auxdata()]. */ typedef struct sqlite3_context sqlite3_context; /* ** CAPI3REF: Binding Values To Prepared Statements ** KEYWORDS: {host parameter} {host parameters} {host parameter name} ** KEYWORDS: {SQL parameter} {SQL parameters} {parameter binding} ** ** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants, ** literals may be replaced by a [parameter] that matches one of following ** templates: ** **
      **
    • ? **
    • ?NNN **
    • :VVV **
    • @VVV **
    • $VVV **
    ** ** In the templates above, NNN represents an integer literal, ** and VVV represents an alphanumeric identifier.)^ ^The values of these ** parameters (also called "host parameter names" or "SQL parameters") ** can be set using the sqlite3_bind_*() routines defined here. ** ** ^The first argument to the sqlite3_bind_*() routines is always ** a pointer to the [sqlite3_stmt] object returned from ** [sqlite3_prepare_v2()] or its variants. ** ** ^The second argument is the index of the SQL parameter to be set. ** ^The leftmost SQL parameter has an index of 1. ^When the same named ** SQL parameter is used more than once, second and subsequent ** occurrences have the same index as the first occurrence. ** ^The index for named parameters can be looked up using the ** [sqlite3_bind_parameter_index()] API if desired. ^The index ** for "?NNN" parameters is the value of NNN. ** ^The NNN value must be between 1 and the [sqlite3_limit()] ** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 999). ** ** ^The third argument is the value to bind to the parameter. ** ** ^(In those routines that have a fourth argument, its value is the ** number of bytes in the parameter. To be clear: the value is the ** number of bytes in the value, not the number of characters.)^ ** ^If the fourth parameter to sqlite3_bind_text() or sqlite3_bind_text16() ** is negative, then the length of the string is ** the number of bytes up to the first zero terminator. ** If the fourth parameter to sqlite3_bind_blob() is negative, then ** the behavior is undefined. ** If a non-negative fourth parameter is provided to sqlite3_bind_text() ** or sqlite3_bind_text16() then that parameter must be the byte offset ** where the NUL terminator would occur assuming the string were NUL ** terminated. If any NUL characters occur at byte offsets less than ** the value of the fourth parameter then the resulting string value will ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. ** ** ^The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and ** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or ** string after SQLite has finished with it. ^The destructor is called ** to dispose of the BLOB or string even if the call to sqlite3_bind_blob(), ** sqlite3_bind_text(), or sqlite3_bind_text16() fails. ** ^If the fifth argument is ** the special value [SQLITE_STATIC], then SQLite assumes that the ** information is in static, unmanaged space and does not need to be freed. ** ^If the fifth argument has the value [SQLITE_TRANSIENT], then ** SQLite makes its own private copy of the data immediately, before ** the sqlite3_bind_*() routine returns. ** ** ^The sqlite3_bind_zeroblob() routine binds a BLOB of length N that ** is filled with zeroes. ^A zeroblob uses a fixed amount of memory ** (just an integer to hold its size) while it is being processed. ** Zeroblobs are intended to serve as placeholders for BLOBs whose ** content is later written using ** [sqlite3_blob_open | incremental BLOB I/O] routines. ** ^A negative value for the zeroblob results in a zero-length BLOB. ** ** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer ** for the [prepared statement] or with a prepared statement for which ** [sqlite3_step()] has been called more recently than [sqlite3_reset()], ** then the call will return [SQLITE_MISUSE]. If any sqlite3_bind_() ** routine is passed a [prepared statement] that has been finalized, the ** result is undefined and probably harmful. ** ** ^Bindings are not cleared by the [sqlite3_reset()] routine. ** ^Unbound parameters are interpreted as NULL. ** ** ^The sqlite3_bind_* routines return [SQLITE_OK] on success or an ** [error code] if anything goes wrong. ** ^[SQLITE_RANGE] is returned if the parameter ** index is out of range. ^[SQLITE_NOMEM] is returned if malloc() fails. ** ** See also: [sqlite3_bind_parameter_count()], ** [sqlite3_bind_parameter_name()], and [sqlite3_bind_parameter_index()]. */ SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double); SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int); SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int); SQLITE_API int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); /* ** CAPI3REF: Number Of SQL Parameters ** ** ^This routine can be used to find the number of [SQL parameters] ** in a [prepared statement]. SQL parameters are tokens of the ** form "?", "?NNN", ":AAA", "$AAA", or "@AAA" that serve as ** placeholders for values that are [sqlite3_bind_blob | bound] ** to the parameters at a later time. ** ** ^(This routine actually returns the index of the largest (rightmost) ** parameter. For all forms except ?NNN, this will correspond to the ** number of unique parameters. If parameters of the ?NNN form are used, ** there may be gaps in the list.)^ ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_name()], and ** [sqlite3_bind_parameter_index()]. */ SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); /* ** CAPI3REF: Name Of A Host Parameter ** ** ^The sqlite3_bind_parameter_name(P,N) interface returns ** the name of the N-th [SQL parameter] in the [prepared statement] P. ** ^(SQL parameters of the form "?NNN" or ":AAA" or "@AAA" or "$AAA" ** have a name which is the string "?NNN" or ":AAA" or "@AAA" or "$AAA" ** respectively. ** In other words, the initial ":" or "$" or "@" or "?" ** is included as part of the name.)^ ** ^Parameters of the form "?" without a following integer have no name ** and are referred to as "nameless" or "anonymous parameters". ** ** ^The first host parameter has an index of 1, not 0. ** ** ^If the value N is out of range or if the N-th parameter is ** nameless, then NULL is returned. ^The returned string is ** always in UTF-8 encoding even if the named parameter was ** originally specified as UTF-16 in [sqlite3_prepare16()] or ** [sqlite3_prepare16_v2()]. ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_count()], and ** [sqlite3_bind_parameter_index()]. */ SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); /* ** CAPI3REF: Index Of A Parameter With A Given Name ** ** ^Return the index of an SQL parameter given its name. ^The ** index value returned is suitable for use as the second ** parameter to [sqlite3_bind_blob|sqlite3_bind()]. ^A zero ** is returned if no matching parameter is found. ^The parameter ** name must be given in UTF-8 even if the original statement ** was prepared from UTF-16 text using [sqlite3_prepare16_v2()]. ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_count()], and ** [sqlite3_bind_parameter_index()]. */ SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); /* ** CAPI3REF: Reset All Bindings On A Prepared Statement ** ** ^Contrary to the intuition of many, [sqlite3_reset()] does not reset ** the [sqlite3_bind_blob | bindings] on a [prepared statement]. ** ^Use this routine to reset all host parameters to NULL. */ SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); /* ** CAPI3REF: Number Of Columns In A Result Set ** ** ^Return the number of columns in the result set returned by the ** [prepared statement]. ^This routine returns 0 if pStmt is an SQL ** statement that does not return data (for example an [UPDATE]). ** ** See also: [sqlite3_data_count()] */ SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt); /* ** CAPI3REF: Column Names In A Result Set ** ** ^These routines return the name assigned to a particular column ** in the result set of a [SELECT] statement. ^The sqlite3_column_name() ** interface returns a pointer to a zero-terminated UTF-8 string ** and sqlite3_column_name16() returns a pointer to a zero-terminated ** UTF-16 string. ^The first parameter is the [prepared statement] ** that implements the [SELECT] statement. ^The second parameter is the ** column number. ^The leftmost column is number 0. ** ** ^The returned string pointer is valid until either the [prepared statement] ** is destroyed by [sqlite3_finalize()] or until the statement is automatically ** reprepared by the first call to [sqlite3_step()] for a particular run ** or until the next call to ** sqlite3_column_name() or sqlite3_column_name16() on the same column. ** ** ^If sqlite3_malloc() fails during the processing of either routine ** (for example during a conversion from UTF-8 to UTF-16) then a ** NULL pointer is returned. ** ** ^The name of a result column is the value of the "AS" clause for ** that column, if there is an AS clause. If there is no AS clause ** then the name of the column is unspecified and may change from ** one release of SQLite to the next. */ SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N); SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); /* ** CAPI3REF: Source Of Data In A Query Result ** ** ^These routines provide a means to determine the database, table, and ** table column that is the origin of a particular result column in ** [SELECT] statement. ** ^The name of the database or table or column can be returned as ** either a UTF-8 or UTF-16 string. ^The _database_ routines return ** the database name, the _table_ routines return the table name, and ** the origin_ routines return the column name. ** ^The returned string is valid until the [prepared statement] is destroyed ** using [sqlite3_finalize()] or until the statement is automatically ** reprepared by the first call to [sqlite3_step()] for a particular run ** or until the same information is requested ** again in a different encoding. ** ** ^The names returned are the original un-aliased names of the ** database, table, and column. ** ** ^The first argument to these interfaces is a [prepared statement]. ** ^These functions return information about the Nth result column returned by ** the statement, where N is the second function argument. ** ^The left-most column is column 0 for these routines. ** ** ^If the Nth column returned by the statement is an expression or ** subquery and is not a column value, then all of these functions return ** NULL. ^These routine might also return NULL if a memory allocation error ** occurs. ^Otherwise, they return the name of the attached database, table, ** or column that query result column was extracted from. ** ** ^As with all other SQLite APIs, those whose names end with "16" return ** UTF-16 encoded strings and the other functions return UTF-8. ** ** ^These APIs are only available if the library was compiled with the ** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol. ** ** If two or more threads call one or more of these routines against the same ** prepared statement and column at the same time then the results are ** undefined. ** ** If two or more threads call one or more ** [sqlite3_column_database_name | column metadata interfaces] ** for the same [prepared statement] and result column ** at the same time then the results are undefined. */ SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int); SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int); SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int); SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int); SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int); SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int); /* ** CAPI3REF: Declared Datatype Of A Query Result ** ** ^(The first parameter is a [prepared statement]. ** If this statement is a [SELECT] statement and the Nth column of the ** returned result set of that [SELECT] is a table column (not an ** expression or subquery) then the declared type of the table ** column is returned.)^ ^If the Nth column of the result set is an ** expression or subquery, then a NULL pointer is returned. ** ^The returned string is always UTF-8 encoded. ** ** ^(For example, given the database schema: ** ** CREATE TABLE t1(c1 VARIANT); ** ** and the following statement to be compiled: ** ** SELECT c1 + 1, c1 FROM t1; ** ** this routine would return the string "VARIANT" for the second result ** column (i==1), and a NULL pointer for the first result column (i==0).)^ ** ** ^SQLite uses dynamic run-time typing. ^So just because a column ** is declared to contain a particular type does not mean that the ** data stored in that column is of the declared type. SQLite is ** strongly typed, but the typing is dynamic not static. ^Type ** is associated with individual values, not with the containers ** used to hold those values. */ SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int); SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); /* ** CAPI3REF: Evaluate An SQL Statement ** ** After a [prepared statement] has been prepared using either ** [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] or one of the legacy ** interfaces [sqlite3_prepare()] or [sqlite3_prepare16()], this function ** must be called one or more times to evaluate the statement. ** ** The details of the behavior of the sqlite3_step() interface depend ** on whether the statement was prepared using the newer "v2" interface ** [sqlite3_prepare_v2()] and [sqlite3_prepare16_v2()] or the older legacy ** interface [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the ** new "v2" interface is recommended for new applications but the legacy ** interface will continue to be supported. ** ** ^In the legacy interface, the return value will be either [SQLITE_BUSY], ** [SQLITE_DONE], [SQLITE_ROW], [SQLITE_ERROR], or [SQLITE_MISUSE]. ** ^With the "v2" interface, any of the other [result codes] or ** [extended result codes] might be returned as well. ** ** ^[SQLITE_BUSY] means that the database engine was unable to acquire the ** database locks it needs to do its job. ^If the statement is a [COMMIT] ** or occurs outside of an explicit transaction, then you can retry the ** statement. If the statement is not a [COMMIT] and occurs within an ** explicit transaction then you should rollback the transaction before ** continuing. ** ** ^[SQLITE_DONE] means that the statement has finished executing ** successfully. sqlite3_step() should not be called again on this virtual ** machine without first calling [sqlite3_reset()] to reset the virtual ** machine back to its initial state. ** ** ^If the SQL statement being executed returns any data, then [SQLITE_ROW] ** is returned each time a new row of data is ready for processing by the ** caller. The values may be accessed using the [column access functions]. ** sqlite3_step() is called again to retrieve the next row of data. ** ** ^[SQLITE_ERROR] means that a run-time error (such as a constraint ** violation) has occurred. sqlite3_step() should not be called again on ** the VM. More information may be found by calling [sqlite3_errmsg()]. ** ^With the legacy interface, a more specific error code (for example, ** [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth) ** can be obtained by calling [sqlite3_reset()] on the ** [prepared statement]. ^In the "v2" interface, ** the more specific error code is returned directly by sqlite3_step(). ** ** [SQLITE_MISUSE] means that the this routine was called inappropriately. ** Perhaps it was called on a [prepared statement] that has ** already been [sqlite3_finalize | finalized] or on one that had ** previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could ** be the case that the same database connection is being used by two or ** more threads at the same moment in time. ** ** For all versions of SQLite up to and including 3.6.23.1, a call to ** [sqlite3_reset()] was required after sqlite3_step() returned anything ** other than [SQLITE_ROW] before any subsequent invocation of ** sqlite3_step(). Failure to reset the prepared statement using ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from ** sqlite3_step(). But after version 3.6.23.1, sqlite3_step() began ** calling [sqlite3_reset()] automatically in this circumstance rather ** than returning [SQLITE_MISUSE]. This is not considered a compatibility ** break because any application that ever receives an SQLITE_MISUSE error ** is broken by definition. The [SQLITE_OMIT_AUTORESET] compile-time option ** can be used to restore the legacy behavior. ** ** Goofy Interface Alert: In the legacy interface, the sqlite3_step() ** API always returns a generic error code, [SQLITE_ERROR], following any ** error other than [SQLITE_BUSY] and [SQLITE_MISUSE]. You must call ** [sqlite3_reset()] or [sqlite3_finalize()] in order to find one of the ** specific [error codes] that better describes the error. ** We admit that this is a goofy design. The problem has been fixed ** with the "v2" interface. If you prepare all of your SQL statements ** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead ** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces, ** then the more specific [error codes] are returned directly ** by sqlite3_step(). The use of the "v2" interface is recommended. */ SQLITE_API int sqlite3_step(sqlite3_stmt*); /* ** CAPI3REF: Number of columns in a result set ** ** ^The sqlite3_data_count(P) interface returns the number of columns in the ** current row of the result set of [prepared statement] P. ** ^If prepared statement P does not have results ready to return ** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of ** interfaces) then sqlite3_data_count(P) returns 0. ** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. ** ^The sqlite3_data_count(P) routine returns 0 if the previous call to ** [sqlite3_step](P) returned [SQLITE_DONE]. ^The sqlite3_data_count(P) ** will return non-zero if previous call to [sqlite3_step](P) returned ** [SQLITE_ROW], except in the case of the [PRAGMA incremental_vacuum] ** where it always returns zero since each step of that multi-step ** pragma returns 0 columns of data. ** ** See also: [sqlite3_column_count()] */ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); /* ** CAPI3REF: Fundamental Datatypes ** KEYWORDS: SQLITE_TEXT ** ** ^(Every value in SQLite has one of five fundamental datatypes: ** **
      **
    • 64-bit signed integer **
    • 64-bit IEEE floating point number **
    • string **
    • BLOB **
    • NULL **
    )^ ** ** These constants are codes for each of those types. ** ** Note that the SQLITE_TEXT constant was also used in SQLite version 2 ** for a completely different meaning. Software that links against both ** SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT, not ** SQLITE_TEXT. */ #define SQLITE_INTEGER 1 #define SQLITE_FLOAT 2 #define SQLITE_BLOB 4 #define SQLITE_NULL 5 #ifdef SQLITE_TEXT # undef SQLITE_TEXT #else # define SQLITE_TEXT 3 #endif #define SQLITE3_TEXT 3 /* ** CAPI3REF: Result Values From A Query ** KEYWORDS: {column access functions} ** ** These routines form the "result set" interface. ** ** ^These routines return information about a single column of the current ** result row of a query. ^In every case the first argument is a pointer ** to the [prepared statement] that is being evaluated (the [sqlite3_stmt*] ** that was returned from [sqlite3_prepare_v2()] or one of its variants) ** and the second argument is the index of the column for which information ** should be returned. ^The leftmost column of the result set has the index 0. ** ^The number of columns in the result can be determined using ** [sqlite3_column_count()]. ** ** If the SQL statement does not currently point to a valid row, or if the ** column index is out of range, the result is undefined. ** These routines may only be called when the most recent call to ** [sqlite3_step()] has returned [SQLITE_ROW] and neither ** [sqlite3_reset()] nor [sqlite3_finalize()] have been called subsequently. ** If any of these routines are called after [sqlite3_reset()] or ** [sqlite3_finalize()] or after [sqlite3_step()] has returned ** something other than [SQLITE_ROW], the results are undefined. ** If [sqlite3_step()] or [sqlite3_reset()] or [sqlite3_finalize()] ** are called from a different thread while any of these routines ** are pending, then the results are undefined. ** ** ^The sqlite3_column_type() routine returns the ** [SQLITE_INTEGER | datatype code] for the initial data type ** of the result column. ^The returned value is one of [SQLITE_INTEGER], ** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. The value ** returned by sqlite3_column_type() is only meaningful if no type ** conversions have occurred as described below. After a type conversion, ** the value returned by sqlite3_column_type() is undefined. Future ** versions of SQLite may change the behavior of sqlite3_column_type() ** following a type conversion. ** ** ^If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes() ** routine returns the number of bytes in that BLOB or string. ** ^If the result is a UTF-16 string, then sqlite3_column_bytes() converts ** the string to UTF-8 and then returns the number of bytes. ** ^If the result is a numeric value then sqlite3_column_bytes() uses ** [sqlite3_snprintf()] to convert that value to a UTF-8 string and returns ** the number of bytes in that string. ** ^If the result is NULL, then sqlite3_column_bytes() returns zero. ** ** ^If the result is a BLOB or UTF-16 string then the sqlite3_column_bytes16() ** routine returns the number of bytes in that BLOB or string. ** ^If the result is a UTF-8 string, then sqlite3_column_bytes16() converts ** the string to UTF-16 and then returns the number of bytes. ** ^If the result is a numeric value then sqlite3_column_bytes16() uses ** [sqlite3_snprintf()] to convert that value to a UTF-16 string and returns ** the number of bytes in that string. ** ^If the result is NULL, then sqlite3_column_bytes16() returns zero. ** ** ^The values returned by [sqlite3_column_bytes()] and ** [sqlite3_column_bytes16()] do not include the zero terminators at the end ** of the string. ^For clarity: the values returned by ** [sqlite3_column_bytes()] and [sqlite3_column_bytes16()] are the number of ** bytes in the string, not the number of characters. ** ** ^Strings returned by sqlite3_column_text() and sqlite3_column_text16(), ** even empty strings, are always zero-terminated. ^The return ** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer. ** ** ^The object returned by [sqlite3_column_value()] is an ** [unprotected sqlite3_value] object. An unprotected sqlite3_value object ** may only be used with [sqlite3_bind_value()] and [sqlite3_result_value()]. ** If the [unprotected sqlite3_value] object returned by ** [sqlite3_column_value()] is used in any other way, including calls ** to routines like [sqlite3_value_int()], [sqlite3_value_text()], ** or [sqlite3_value_bytes()], then the behavior is undefined. ** ** These routines attempt to convert the value where appropriate. ^For ** example, if the internal representation is FLOAT and a text result ** is requested, [sqlite3_snprintf()] is used internally to perform the ** conversion automatically. ^(The following table details the conversions ** that are applied: ** **
    ** **
    Internal
    Type
    Requested
    Type
    Conversion ** **
    NULL INTEGER Result is 0 **
    NULL FLOAT Result is 0.0 **
    NULL TEXT Result is NULL pointer **
    NULL BLOB Result is NULL pointer **
    INTEGER FLOAT Convert from integer to float **
    INTEGER TEXT ASCII rendering of the integer **
    INTEGER BLOB Same as INTEGER->TEXT **
    FLOAT INTEGER Convert from float to integer **
    FLOAT TEXT ASCII rendering of the float **
    FLOAT BLOB Same as FLOAT->TEXT **
    TEXT INTEGER Use atoi() **
    TEXT FLOAT Use atof() **
    TEXT BLOB No change **
    BLOB INTEGER Convert to TEXT then use atoi() **
    BLOB FLOAT Convert to TEXT then use atof() **
    BLOB TEXT Add a zero terminator if needed **
    **
    )^ ** ** The table above makes reference to standard C library functions atoi() ** and atof(). SQLite does not really use these functions. It has its ** own equivalent internal routines. The atoi() and atof() names are ** used in the table for brevity and because they are familiar to most ** C programmers. ** ** Note that when type conversions occur, pointers returned by prior ** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or ** sqlite3_column_text16() may be invalidated. ** Type conversions and pointer invalidations might occur ** in the following cases: ** **
      **
    • The initial content is a BLOB and sqlite3_column_text() or ** sqlite3_column_text16() is called. A zero-terminator might ** need to be added to the string.
    • **
    • The initial content is UTF-8 text and sqlite3_column_bytes16() or ** sqlite3_column_text16() is called. The content must be converted ** to UTF-16.
    • **
    • The initial content is UTF-16 text and sqlite3_column_bytes() or ** sqlite3_column_text() is called. The content must be converted ** to UTF-8.
    • **
    ** ** ^Conversions between UTF-16be and UTF-16le are always done in place and do ** not invalidate a prior pointer, though of course the content of the buffer ** that the prior pointer references will have been modified. Other kinds ** of conversion are done in place when it is possible, but sometimes they ** are not possible and in those cases prior pointers are invalidated. ** ** The safest and easiest to remember policy is to invoke these routines ** in one of the following ways: ** **
      **
    • sqlite3_column_text() followed by sqlite3_column_bytes()
    • **
    • sqlite3_column_blob() followed by sqlite3_column_bytes()
    • **
    • sqlite3_column_text16() followed by sqlite3_column_bytes16()
    • **
    ** ** In other words, you should call sqlite3_column_text(), ** sqlite3_column_blob(), or sqlite3_column_text16() first to force the result ** into the desired format, then invoke sqlite3_column_bytes() or ** sqlite3_column_bytes16() to find the size of the result. Do not mix calls ** to sqlite3_column_text() or sqlite3_column_blob() with calls to ** sqlite3_column_bytes16(), and do not mix calls to sqlite3_column_text16() ** with calls to sqlite3_column_bytes(). ** ** ^The pointers returned are valid until a type conversion occurs as ** described above, or until [sqlite3_step()] or [sqlite3_reset()] or ** [sqlite3_finalize()] is called. ^The memory space used to hold strings ** and BLOBs is freed automatically. Do not pass the pointers returned ** [sqlite3_column_blob()], [sqlite3_column_text()], etc. into ** [sqlite3_free()]. ** ** ^(If a memory allocation error occurs during the evaluation of any ** of these routines, a default value is returned. The default value ** is either the integer 0, the floating point number 0.0, or a NULL ** pointer. Subsequent calls to [sqlite3_errcode()] will return ** [SQLITE_NOMEM].)^ */ SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); /* ** CAPI3REF: Destroy A Prepared Statement Object ** ** ^The sqlite3_finalize() function is called to delete a [prepared statement]. ** ^If the most recent evaluation of the statement encountered no errors ** or if the statement is never been evaluated, then sqlite3_finalize() returns ** SQLITE_OK. ^If the most recent evaluation of statement S failed, then ** sqlite3_finalize(S) returns the appropriate [error code] or ** [extended error code]. ** ** ^The sqlite3_finalize(S) routine can be called at any point during ** the life cycle of [prepared statement] S: ** before statement S is ever evaluated, after ** one or more calls to [sqlite3_reset()], or after any call ** to [sqlite3_step()] regardless of whether or not the statement has ** completed execution. ** ** ^Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op. ** ** The application must finalize every [prepared statement] in order to avoid ** resource leaks. It is a grievous error for the application to try to use ** a prepared statement after it has been finalized. Any use of a prepared ** statement after it has been finalized can result in undefined and ** undesirable behavior such as segfaults and heap corruption. */ SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); /* ** CAPI3REF: Reset A Prepared Statement Object ** ** The sqlite3_reset() function is called to reset a [prepared statement] ** object back to its initial state, ready to be re-executed. ** ^Any SQL statement variables that had values bound to them using ** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values. ** Use [sqlite3_clear_bindings()] to reset the bindings. ** ** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S ** back to the beginning of its program. ** ** ^If the most recent call to [sqlite3_step(S)] for the ** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE], ** or if [sqlite3_step(S)] has never before been called on S, ** then [sqlite3_reset(S)] returns [SQLITE_OK]. ** ** ^If the most recent call to [sqlite3_step(S)] for the ** [prepared statement] S indicated an error, then ** [sqlite3_reset(S)] returns an appropriate [error code]. ** ** ^The [sqlite3_reset(S)] interface does not change the values ** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. */ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); /* ** CAPI3REF: Create Or Redefine SQL Functions ** KEYWORDS: {function creation routines} ** KEYWORDS: {application-defined SQL function} ** KEYWORDS: {application-defined SQL functions} ** ** ^These functions (collectively known as "function creation routines") ** are used to add SQL functions or aggregates or to redefine the behavior ** of existing SQL functions or aggregates. The only differences between ** these routines are the text encoding expected for ** the second parameter (the name of the function being created) ** and the presence or absence of a destructor callback for ** the application data pointer. ** ** ^The first parameter is the [database connection] to which the SQL ** function is to be added. ^If an application uses more than one database ** connection then application-defined SQL functions must be added ** to each database connection separately. ** ** ^The second parameter is the name of the SQL function to be created or ** redefined. ^The length of the name is limited to 255 bytes in a UTF-8 ** representation, exclusive of the zero-terminator. ^Note that the name ** length limit is in UTF-8 bytes, not characters nor UTF-16 bytes. ** ^Any attempt to create a function with a longer name ** will result in [SQLITE_MISUSE] being returned. ** ** ^The third parameter (nArg) ** is the number of arguments that the SQL function or ** aggregate takes. ^If this parameter is -1, then the SQL function or ** aggregate may take any number of arguments between 0 and the limit ** set by [sqlite3_limit]([SQLITE_LIMIT_FUNCTION_ARG]). If the third ** parameter is less than -1 or greater than 127 then the behavior is ** undefined. ** ** ^The fourth parameter, eTextRep, specifies what ** [SQLITE_UTF8 | text encoding] this SQL function prefers for ** its parameters. Every SQL function implementation must be able to work ** with UTF-8, UTF-16le, or UTF-16be. But some implementations may be ** more efficient with one encoding than another. ^An application may ** invoke sqlite3_create_function() or sqlite3_create_function16() multiple ** times with the same function but with different values of eTextRep. ** ^When multiple implementations of the same function are available, SQLite ** will pick the one that involves the least amount of data conversion. ** If there is only a single implementation which does not care what text ** encoding is used, then the fourth argument should be [SQLITE_ANY]. ** ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ ** ** ^The sixth, seventh and eighth parameters, xFunc, xStep and xFinal, are ** pointers to C-language functions that implement the SQL function or ** aggregate. ^A scalar SQL function requires an implementation of the xFunc ** callback only; NULL pointers must be passed as the xStep and xFinal ** parameters. ^An aggregate SQL function requires an implementation of xStep ** and xFinal and NULL pointer must be passed for xFunc. ^To delete an existing ** SQL function or aggregate, pass NULL pointers for all three function ** callbacks. ** ** ^(If the ninth parameter to sqlite3_create_function_v2() is not NULL, ** then it is destructor for the application data pointer. ** The destructor is invoked when the function is deleted, either by being ** overloaded or when the database connection closes.)^ ** ^The destructor is also invoked if the call to ** sqlite3_create_function_v2() fails. ** ^When the destructor callback of the tenth parameter is invoked, it ** is passed a single argument which is a copy of the application data ** pointer which was the fifth parameter to sqlite3_create_function_v2(). ** ** ^It is permitted to register multiple implementations of the same ** functions with the same name but with either differing numbers of ** arguments or differing preferred text encodings. ^SQLite will use ** the implementation that most closely matches the way in which the ** SQL function is used. ^A function implementation with a non-negative ** nArg parameter is a better match than a function implementation with ** a negative nArg. ^A function where the preferred text encoding ** matches the database encoding is a better ** match than a function where the encoding is different. ** ^A function where the encoding difference is between UTF16le and UTF16be ** is a closer match than a function where the encoding difference is ** between UTF8 and UTF16. ** ** ^Built-in functions may be overloaded by new application-defined functions. ** ** ^An application-defined function is permitted to call other ** SQLite interfaces. However, such calls must not ** close the database connection nor finalize or reset the prepared ** statement in which the function is running. */ SQLITE_API int sqlite3_create_function( sqlite3 *db, const char *zFunctionName, int nArg, int eTextRep, void *pApp, void (*xFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ); SQLITE_API int sqlite3_create_function16( sqlite3 *db, const void *zFunctionName, int nArg, int eTextRep, void *pApp, void (*xFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ); SQLITE_API int sqlite3_create_function_v2( sqlite3 *db, const char *zFunctionName, int nArg, int eTextRep, void *pApp, void (*xFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*), void(*xDestroy)(void*) ); /* ** CAPI3REF: Text Encodings ** ** These constant define integer codes that represent the various ** text encodings supported by SQLite. */ #define SQLITE_UTF8 1 #define SQLITE_UTF16LE 2 #define SQLITE_UTF16BE 3 #define SQLITE_UTF16 4 /* Use native byte order */ #define SQLITE_ANY 5 /* sqlite3_create_function only */ #define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ /* ** CAPI3REF: Deprecated Functions ** DEPRECATED ** ** These functions are [deprecated]. In order to maintain ** backwards compatibility with older code, these functions continue ** to be supported. However, new applications should avoid ** the use of these functions. To help encourage people to avoid ** using these functions, we are not going to tell you what they do. */ #ifndef SQLITE_OMIT_DEPRECATED SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*); SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*); SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void); SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void); SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),void*,sqlite3_int64); #endif /* ** CAPI3REF: Obtaining SQL Function Parameter Values ** ** The C-language implementation of SQL functions and aggregates uses ** this set of interface routines to access the parameter values on ** the function or aggregate. ** ** The xFunc (for scalar functions) or xStep (for aggregates) parameters ** to [sqlite3_create_function()] and [sqlite3_create_function16()] ** define callbacks that implement the SQL functions and aggregates. ** The 3rd parameter to these callbacks is an array of pointers to ** [protected sqlite3_value] objects. There is one [sqlite3_value] object for ** each parameter to the SQL function. These routines are used to ** extract values from the [sqlite3_value] objects. ** ** These routines work only with [protected sqlite3_value] objects. ** Any attempt to use these routines on an [unprotected sqlite3_value] ** object results in undefined behavior. ** ** ^These routines work just like the corresponding [column access functions] ** except that these routines take a single [protected sqlite3_value] object ** pointer instead of a [sqlite3_stmt*] pointer and an integer column number. ** ** ^The sqlite3_value_text16() interface extracts a UTF-16 string ** in the native byte-order of the host machine. ^The ** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces ** extract UTF-16 strings as big-endian and little-endian respectively. ** ** ^(The sqlite3_value_numeric_type() interface attempts to apply ** numeric affinity to the value. This means that an attempt is ** made to convert the value to an integer or floating point. If ** such a conversion is possible without loss of information (in other ** words, if the value is a string that looks like a number) ** then the conversion is performed. Otherwise no conversion occurs. ** The [SQLITE_INTEGER | datatype] after conversion is returned.)^ ** ** Please pay particular attention to the fact that the pointer returned ** from [sqlite3_value_blob()], [sqlite3_value_text()], or ** [sqlite3_value_text16()] can be invalidated by a subsequent call to ** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()], ** or [sqlite3_value_text16()]. ** ** These routines must be called from the same thread as ** the SQL function that supplied the [sqlite3_value*] parameters. */ SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); SQLITE_API int sqlite3_value_bytes(sqlite3_value*); SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); SQLITE_API double sqlite3_value_double(sqlite3_value*); SQLITE_API int sqlite3_value_int(sqlite3_value*); SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); SQLITE_API int sqlite3_value_type(sqlite3_value*); SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); /* ** CAPI3REF: Obtain Aggregate Function Context ** ** Implementations of aggregate SQL functions use this ** routine to allocate memory for storing their state. ** ** ^The first time the sqlite3_aggregate_context(C,N) routine is called ** for a particular aggregate function, SQLite ** allocates N of memory, zeroes out that memory, and returns a pointer ** to the new memory. ^On second and subsequent calls to ** sqlite3_aggregate_context() for the same aggregate function instance, ** the same buffer is returned. Sqlite3_aggregate_context() is normally ** called once for each invocation of the xStep callback and then one ** last time when the xFinal callback is invoked. ^(When no rows match ** an aggregate query, the xStep() callback of the aggregate function ** implementation is never called and xFinal() is called exactly once. ** In those cases, sqlite3_aggregate_context() might be called for the ** first time from within xFinal().)^ ** ** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer if N is ** less than or equal to zero or if a memory allocate error occurs. ** ** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is ** determined by the N parameter on first successful call. Changing the ** value of N in subsequent call to sqlite3_aggregate_context() within ** the same aggregate function instance will not resize the memory ** allocation.)^ ** ** ^SQLite automatically frees the memory allocated by ** sqlite3_aggregate_context() when the aggregate query concludes. ** ** The first parameter must be a copy of the ** [sqlite3_context | SQL function context] that is the first parameter ** to the xStep or xFinal callback routine that implements the aggregate ** function. ** ** This routine must be called from the same thread in which ** the aggregate SQL function is running. */ SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); /* ** CAPI3REF: User Data For Functions ** ** ^The sqlite3_user_data() interface returns a copy of ** the pointer that was the pUserData parameter (the 5th parameter) ** of the [sqlite3_create_function()] ** and [sqlite3_create_function16()] routines that originally ** registered the application defined function. ** ** This routine must be called from the same thread in which ** the application-defined function is running. */ SQLITE_API void *sqlite3_user_data(sqlite3_context*); /* ** CAPI3REF: Database Connection For Functions ** ** ^The sqlite3_context_db_handle() interface returns a copy of ** the pointer to the [database connection] (the 1st parameter) ** of the [sqlite3_create_function()] ** and [sqlite3_create_function16()] routines that originally ** registered the application defined function. */ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); /* ** CAPI3REF: Function Auxiliary Data ** ** The following two functions may be used by scalar SQL functions to ** associate metadata with argument values. If the same value is passed to ** multiple invocations of the same SQL function during query execution, under ** some circumstances the associated metadata may be preserved. This may ** be used, for example, to add a regular-expression matching scalar ** function. The compiled version of the regular expression is stored as ** metadata associated with the SQL value passed as the regular expression ** pattern. The compiled regular expression can be reused on multiple ** invocations of the same function so that the original pattern string ** does not need to be recompiled on each invocation. ** ** ^The sqlite3_get_auxdata() interface returns a pointer to the metadata ** associated by the sqlite3_set_auxdata() function with the Nth argument ** value to the application-defined function. ^If no metadata has been ever ** been set for the Nth argument of the function, or if the corresponding ** function parameter has changed since the meta-data was set, ** then sqlite3_get_auxdata() returns a NULL pointer. ** ** ^The sqlite3_set_auxdata() interface saves the metadata ** pointed to by its 3rd parameter as the metadata for the N-th ** argument of the application-defined function. Subsequent ** calls to sqlite3_get_auxdata() might return this data, if it has ** not been destroyed. ** ^If it is not NULL, SQLite will invoke the destructor ** function given by the 4th parameter to sqlite3_set_auxdata() on ** the metadata when the corresponding function parameter changes ** or when the SQL statement completes, whichever comes first. ** ** SQLite is free to call the destructor and drop metadata on any ** parameter of any function at any time. ^The only guarantee is that ** the destructor will be called before the metadata is dropped. ** ** ^(In practice, metadata is preserved between function calls for ** expressions that are constant at compile time. This includes literal ** values and [parameters].)^ ** ** These routines must be called from the same thread in which ** the SQL function is running. */ SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N); SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); /* ** CAPI3REF: Constants Defining Special Destructor Behavior ** ** These are special values for the destructor that is passed in as the ** final argument to routines like [sqlite3_result_blob()]. ^If the destructor ** argument is SQLITE_STATIC, it means that the content pointer is constant ** and will never change. It does not need to be destroyed. ^The ** SQLITE_TRANSIENT value means that the content will likely change in ** the near future and that SQLite should make its own private copy of ** the content before returning. ** ** The typedef is necessary to work around problems in certain ** C++ compilers. See ticket #2191. */ typedef void (*sqlite3_destructor_type)(void*); #define SQLITE_STATIC ((sqlite3_destructor_type)0) #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) /* ** CAPI3REF: Setting The Result Of An SQL Function ** ** These routines are used by the xFunc or xFinal callbacks that ** implement SQL functions and aggregates. See ** [sqlite3_create_function()] and [sqlite3_create_function16()] ** for additional information. ** ** These functions work very much like the [parameter binding] family of ** functions used to bind values to host parameters in prepared statements. ** Refer to the [SQL parameter] documentation for additional information. ** ** ^The sqlite3_result_blob() interface sets the result from ** an application-defined function to be the BLOB whose content is pointed ** to by the second parameter and which is N bytes long where N is the ** third parameter. ** ** ^The sqlite3_result_zeroblob() interfaces set the result of ** the application-defined function to be a BLOB containing all zero ** bytes and N bytes in size, where N is the value of the 2nd parameter. ** ** ^The sqlite3_result_double() interface sets the result from ** an application-defined function to be a floating point value specified ** by its 2nd argument. ** ** ^The sqlite3_result_error() and sqlite3_result_error16() functions ** cause the implemented SQL function to throw an exception. ** ^SQLite uses the string pointed to by the ** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() ** as the text of an error message. ^SQLite interprets the error ** message string from sqlite3_result_error() as UTF-8. ^SQLite ** interprets the string from sqlite3_result_error16() as UTF-16 in native ** byte order. ^If the third parameter to sqlite3_result_error() ** or sqlite3_result_error16() is negative then SQLite takes as the error ** message all text up through the first zero character. ** ^If the third parameter to sqlite3_result_error() or ** sqlite3_result_error16() is non-negative then SQLite takes that many ** bytes (not characters) from the 2nd parameter as the error message. ** ^The sqlite3_result_error() and sqlite3_result_error16() ** routines make a private copy of the error message text before ** they return. Hence, the calling function can deallocate or ** modify the text after they return without harm. ** ^The sqlite3_result_error_code() function changes the error code ** returned by SQLite as a result of an error in a function. ^By default, ** the error code is SQLITE_ERROR. ^A subsequent call to sqlite3_result_error() ** or sqlite3_result_error16() resets the error code to SQLITE_ERROR. ** ** ^The sqlite3_result_error_toobig() interface causes SQLite to throw an ** error indicating that a string or BLOB is too long to represent. ** ** ^The sqlite3_result_error_nomem() interface causes SQLite to throw an ** error indicating that a memory allocation failed. ** ** ^The sqlite3_result_int() interface sets the return value ** of the application-defined function to be the 32-bit signed integer ** value given in the 2nd argument. ** ^The sqlite3_result_int64() interface sets the return value ** of the application-defined function to be the 64-bit signed integer ** value given in the 2nd argument. ** ** ^The sqlite3_result_null() interface sets the return value ** of the application-defined function to be NULL. ** ** ^The sqlite3_result_text(), sqlite3_result_text16(), ** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces ** set the return value of the application-defined function to be ** a text string which is represented as UTF-8, UTF-16 native byte order, ** UTF-16 little endian, or UTF-16 big endian, respectively. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is negative, then SQLite takes result text from the 2nd parameter ** through the first zero character. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is non-negative, then as many bytes (not characters) of the text ** pointed to by the 2nd parameter are taken as the application-defined ** function result. If the 3rd parameter is non-negative, then it ** must be the byte offset into the string where the NUL terminator would ** appear if the string where NUL terminated. If any NUL characters occur ** in the string at a byte offset that is less than the value of the 3rd ** parameter, then the resulting string will contain embedded NULs and the ** result of expressions operating on strings with embedded NULs is undefined. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that ** function as the destructor on the text or BLOB result when it has ** finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces or to ** sqlite3_result_blob is the special constant SQLITE_STATIC, then SQLite ** assumes that the text or BLOB result is in constant space and does not ** copy the content of the parameter nor call a destructor on the content ** when it has finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT ** then SQLite makes a copy of the result into space obtained from ** from [sqlite3_malloc()] before it returns. ** ** ^The sqlite3_result_value() interface sets the result of ** the application-defined function to be a copy the ** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The ** sqlite3_result_value() interface makes a copy of the [sqlite3_value] ** so that the [sqlite3_value] specified in the parameter may change or ** be deallocated after sqlite3_result_value() returns without harm. ** ^A [protected sqlite3_value] object may always be used where an ** [unprotected sqlite3_value] object is required, so either ** kind of [sqlite3_value] object can be used with this interface. ** ** If these routines are called from within the different thread ** than the one containing the application-defined function that received ** the [sqlite3_context] pointer, the results are undefined. */ SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); SQLITE_API void sqlite3_result_double(sqlite3_context*, double); SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*); SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*); SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int); SQLITE_API void sqlite3_result_int(sqlite3_context*, int); SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); SQLITE_API void sqlite3_result_null(sqlite3_context*); SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); /* ** CAPI3REF: Define New Collating Sequences ** ** ^These functions add, remove, or modify a [collation] associated ** with the [database connection] specified as the first argument. ** ** ^The name of the collation is a UTF-8 string ** for sqlite3_create_collation() and sqlite3_create_collation_v2() ** and a UTF-16 string in native byte order for sqlite3_create_collation16(). ** ^Collation names that compare equal according to [sqlite3_strnicmp()] are ** considered to be the same name. ** ** ^(The third argument (eTextRep) must be one of the constants: **
      **
    • [SQLITE_UTF8], **
    • [SQLITE_UTF16LE], **
    • [SQLITE_UTF16BE], **
    • [SQLITE_UTF16], or **
    • [SQLITE_UTF16_ALIGNED]. **
    )^ ** ^The eTextRep argument determines the encoding of strings passed ** to the collating function callback, xCallback. ** ^The [SQLITE_UTF16] and [SQLITE_UTF16_ALIGNED] values for eTextRep ** force strings to be UTF16 with native byte order. ** ^The [SQLITE_UTF16_ALIGNED] value for eTextRep forces strings to begin ** on an even byte address. ** ** ^The fourth argument, pArg, is an application data pointer that is passed ** through as the first argument to the collating function callback. ** ** ^The fifth argument, xCallback, is a pointer to the collating function. ** ^Multiple collating functions can be registered using the same name but ** with different eTextRep parameters and SQLite will use whichever ** function requires the least amount of data transformation. ** ^If the xCallback argument is NULL then the collating function is ** deleted. ^When all collating functions having the same name are deleted, ** that collation is no longer usable. ** ** ^The collating function callback is invoked with a copy of the pArg ** application data pointer and with two strings in the encoding specified ** by the eTextRep argument. The collating function must return an ** integer that is negative, zero, or positive ** if the first string is less than, equal to, or greater than the second, ** respectively. A collating function must always return the same answer ** given the same inputs. If two or more collating functions are registered ** to the same collation name (using different eTextRep values) then all ** must give an equivalent answer when invoked with equivalent strings. ** The collating function must obey the following properties for all ** strings A, B, and C: ** **
      **
    1. If A==B then B==A. **
    2. If A==B and B==C then A==C. **
    3. If A<B THEN B>A. **
    4. If A<B and B<C then A<C. **
    ** ** If a collating function fails any of the above constraints and that ** collating function is registered and used, then the behavior of SQLite ** is undefined. ** ** ^The sqlite3_create_collation_v2() works like sqlite3_create_collation() ** with the addition that the xDestroy callback is invoked on pArg when ** the collating function is deleted. ** ^Collating functions are deleted when they are overridden by later ** calls to the collation creation functions or when the ** [database connection] is closed using [sqlite3_close()]. ** ** ^The xDestroy callback is not called if the ** sqlite3_create_collation_v2() function fails. Applications that invoke ** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should ** check the return code and dispose of the application data pointer ** themselves rather than expecting SQLite to deal with it for them. ** This is different from every other SQLite interface. The inconsistency ** is unfortunate but cannot be changed without breaking backwards ** compatibility. ** ** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. */ SQLITE_API int sqlite3_create_collation( sqlite3*, const char *zName, int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); SQLITE_API int sqlite3_create_collation_v2( sqlite3*, const char *zName, int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*), void(*xDestroy)(void*) ); SQLITE_API int sqlite3_create_collation16( sqlite3*, const void *zName, int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); /* ** CAPI3REF: Collation Needed Callbacks ** ** ^To avoid having to register all collation sequences before a database ** can be used, a single callback function may be registered with the ** [database connection] to be invoked whenever an undefined collation ** sequence is required. ** ** ^If the function is registered using the sqlite3_collation_needed() API, ** then it is passed the names of undefined collation sequences as strings ** encoded in UTF-8. ^If sqlite3_collation_needed16() is used, ** the names are passed as UTF-16 in machine native byte order. ** ^A call to either function replaces the existing collation-needed callback. ** ** ^(When the callback is invoked, the first argument passed is a copy ** of the second argument to sqlite3_collation_needed() or ** sqlite3_collation_needed16(). The second argument is the database ** connection. The third argument is one of [SQLITE_UTF8], [SQLITE_UTF16BE], ** or [SQLITE_UTF16LE], indicating the most desirable form of the collation ** sequence function required. The fourth parameter is the name of the ** required collation sequence.)^ ** ** The callback function should register the desired collation using ** [sqlite3_create_collation()], [sqlite3_create_collation16()], or ** [sqlite3_create_collation_v2()]. */ SQLITE_API int sqlite3_collation_needed( sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const char*) ); SQLITE_API int sqlite3_collation_needed16( sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const void*) ); #ifdef SQLITE_HAS_CODEC /* ** Specify the key for an encrypted database. This routine should be ** called right after sqlite3_open(). ** ** The code to implement this API is not available in the public release ** of SQLite. */ SQLITE_API int sqlite3_key( sqlite3 *db, /* Database to be rekeyed */ const void *pKey, int nKey /* The key */ ); /* ** Change the key on an open database. If the current database is not ** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the ** database is decrypted. ** ** The code to implement this API is not available in the public release ** of SQLite. */ SQLITE_API int sqlite3_rekey( sqlite3 *db, /* Database to be rekeyed */ const void *pKey, int nKey /* The new key */ ); /* ** Specify the activation key for a SEE database. Unless ** activated, none of the SEE routines will work. */ SQLITE_API void sqlite3_activate_see( const char *zPassPhrase /* Activation phrase */ ); #endif #ifdef SQLITE_ENABLE_CEROD /* ** Specify the activation key for a CEROD database. Unless ** activated, none of the CEROD routines will work. */ SQLITE_API void sqlite3_activate_cerod( const char *zPassPhrase /* Activation phrase */ ); #endif /* ** CAPI3REF: Suspend Execution For A Short Time ** ** The sqlite3_sleep() function causes the current thread to suspend execution ** for at least a number of milliseconds specified in its parameter. ** ** If the operating system does not support sleep requests with ** millisecond time resolution, then the time will be rounded up to ** the nearest second. The number of milliseconds of sleep actually ** requested from the operating system is returned. ** ** ^SQLite implements this interface by calling the xSleep() ** method of the default [sqlite3_vfs] object. If the xSleep() method ** of the default VFS is not implemented correctly, or not implemented at ** all, then the behavior of sqlite3_sleep() may deviate from the description ** in the previous paragraphs. */ SQLITE_API int sqlite3_sleep(int); /* ** CAPI3REF: Name Of The Folder Holding Temporary Files ** ** ^(If this global variable is made to point to a string which is ** the name of a folder (a.k.a. directory), then all temporary files ** created by SQLite when using a built-in [sqlite3_vfs | VFS] ** will be placed in that directory.)^ ^If this variable ** is a NULL pointer, then SQLite performs a search for an appropriate ** temporary file directory. ** ** It is not safe to read or modify this variable in more than one ** thread at a time. It is not safe to read or modify this variable ** if a [database connection] is being used at the same time in a separate ** thread. ** It is intended that this variable be set once ** as part of process initialization and before any SQLite interface ** routines have been called and that this variable remain unchanged ** thereafter. ** ** ^The [temp_store_directory pragma] may modify this variable and cause ** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, ** the [temp_store_directory pragma] always assumes that any string ** that this variable points to is held in memory obtained from ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be ** made NULL or made to point to memory obtained from [sqlite3_malloc] ** or else the use of the [temp_store_directory pragma] should be avoided. ** ** Note to Windows Runtime users: The temporary directory must be set ** prior to calling [sqlite3_open] or [sqlite3_open_v2]. Otherwise, various ** features that require the use of temporary files may fail. Here is an ** example of how to do this using C++ with the Windows Runtime: ** **
    ** LPCWSTR zPath = Windows::Storage::ApplicationData::Current->
    **       TemporaryFolder->Path->Data();
    ** char zPathBuf[MAX_PATH + 1];
    ** memset(zPathBuf, 0, sizeof(zPathBuf));
    ** WideCharToMultiByte(CP_UTF8, 0, zPath, -1, zPathBuf, sizeof(zPathBuf),
    **       NULL, NULL);
    ** sqlite3_temp_directory = sqlite3_mprintf("%s", zPathBuf);
    ** 
    */ SQLITE_API char *sqlite3_temp_directory; /* ** CAPI3REF: Name Of The Folder Holding Database Files ** ** ^(If this global variable is made to point to a string which is ** the name of a folder (a.k.a. directory), then all database files ** specified with a relative pathname and created or accessed by ** SQLite when using a built-in windows [sqlite3_vfs | VFS] will be assumed ** to be relative to that directory.)^ ^If this variable is a NULL ** pointer, then SQLite assumes that all database files specified ** with a relative pathname are relative to the current directory ** for the process. Only the windows VFS makes use of this global ** variable; it is ignored by the unix VFS. ** ** Changing the value of this variable while a database connection is ** open can result in a corrupt database. ** ** It is not safe to read or modify this variable in more than one ** thread at a time. It is not safe to read or modify this variable ** if a [database connection] is being used at the same time in a separate ** thread. ** It is intended that this variable be set once ** as part of process initialization and before any SQLite interface ** routines have been called and that this variable remain unchanged ** thereafter. ** ** ^The [data_store_directory pragma] may modify this variable and cause ** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, ** the [data_store_directory pragma] always assumes that any string ** that this variable points to is held in memory obtained from ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be ** made NULL or made to point to memory obtained from [sqlite3_malloc] ** or else the use of the [data_store_directory pragma] should be avoided. */ SQLITE_API char *sqlite3_data_directory; /* ** CAPI3REF: Test For Auto-Commit Mode ** KEYWORDS: {autocommit mode} ** ** ^The sqlite3_get_autocommit() interface returns non-zero or ** zero if the given database connection is or is not in autocommit mode, ** respectively. ^Autocommit mode is on by default. ** ^Autocommit mode is disabled by a [BEGIN] statement. ** ^Autocommit mode is re-enabled by a [COMMIT] or [ROLLBACK]. ** ** If certain kinds of errors occur on a statement within a multi-statement ** transaction (errors including [SQLITE_FULL], [SQLITE_IOERR], ** [SQLITE_NOMEM], [SQLITE_BUSY], and [SQLITE_INTERRUPT]) then the ** transaction might be rolled back automatically. The only way to ** find out whether SQLite automatically rolled back the transaction after ** an error is to use this function. ** ** If another thread changes the autocommit status of the database ** connection while this routine is running, then the return value ** is undefined. */ SQLITE_API int sqlite3_get_autocommit(sqlite3*); /* ** CAPI3REF: Find The Database Handle Of A Prepared Statement ** ** ^The sqlite3_db_handle interface returns the [database connection] handle ** to which a [prepared statement] belongs. ^The [database connection] ** returned by sqlite3_db_handle is the same [database connection] ** that was the first argument ** to the [sqlite3_prepare_v2()] call (or its variants) that was used to ** create the statement in the first place. */ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); /* ** CAPI3REF: Return The Filename For A Database Connection ** ** ^The sqlite3_db_filename(D,N) interface returns a pointer to a filename ** associated with database N of connection D. ^The main database file ** has the name "main". If there is no attached database N on the database ** connection D, or if database N is a temporary or in-memory database, then ** a NULL pointer is returned. ** ** ^The filename returned by this function is the output of the ** xFullPathname method of the [VFS]. ^In other words, the filename ** will be an absolute pathname, even if the filename used ** to open the database originally was a URI or relative pathname. */ SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Determine if a database is read-only ** ** ^The sqlite3_db_readonly(D,N) interface returns 1 if the database N ** of connection D is read-only, 0 if it is read/write, or -1 if N is not ** the name of a database on connection D. */ SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Find the next prepared statement ** ** ^This interface returns a pointer to the next [prepared statement] after ** pStmt associated with the [database connection] pDb. ^If pStmt is NULL ** then this interface returns a pointer to the first prepared statement ** associated with the database connection pDb. ^If no prepared statement ** satisfies the conditions of this routine, it returns NULL. ** ** The [database connection] pointer D in a call to ** [sqlite3_next_stmt(D,S)] must refer to an open database ** connection and in particular must not be a NULL pointer. */ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); /* ** CAPI3REF: Commit And Rollback Notification Callbacks ** ** ^The sqlite3_commit_hook() interface registers a callback ** function to be invoked whenever a transaction is [COMMIT | committed]. ** ^Any callback set by a previous call to sqlite3_commit_hook() ** for the same database connection is overridden. ** ^The sqlite3_rollback_hook() interface registers a callback ** function to be invoked whenever a transaction is [ROLLBACK | rolled back]. ** ^Any callback set by a previous call to sqlite3_rollback_hook() ** for the same database connection is overridden. ** ^The pArg argument is passed through to the callback. ** ^If the callback on a commit hook function returns non-zero, ** then the commit is converted into a rollback. ** ** ^The sqlite3_commit_hook(D,C,P) and sqlite3_rollback_hook(D,C,P) functions ** return the P argument from the previous call of the same function ** on the same [database connection] D, or NULL for ** the first call for each function on D. ** ** The commit and rollback hook callbacks are not reentrant. ** The callback implementation must not do anything that will modify ** the database connection that invoked the callback. Any actions ** to modify the database connection must be deferred until after the ** completion of the [sqlite3_step()] call that triggered the commit ** or rollback hook in the first place. ** Note that running any other SQL statements, including SELECT statements, ** or merely calling [sqlite3_prepare_v2()] and [sqlite3_step()] will modify ** the database connections for the meaning of "modify" in this paragraph. ** ** ^Registering a NULL function disables the callback. ** ** ^When the commit hook callback routine returns zero, the [COMMIT] ** operation is allowed to continue normally. ^If the commit hook ** returns non-zero, then the [COMMIT] is converted into a [ROLLBACK]. ** ^The rollback hook is invoked on a rollback that results from a commit ** hook returning non-zero, just as it would be with any other rollback. ** ** ^For the purposes of this API, a transaction is said to have been ** rolled back if an explicit "ROLLBACK" statement is executed, or ** an error or constraint causes an implicit rollback to occur. ** ^The rollback callback is not invoked if a transaction is ** automatically rolled back because the database connection is closed. ** ** See also the [sqlite3_update_hook()] interface. */ SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); /* ** CAPI3REF: Data Change Notification Callbacks ** ** ^The sqlite3_update_hook() interface registers a callback function ** with the [database connection] identified by the first argument ** to be invoked whenever a row is updated, inserted or deleted. ** ^Any callback set by a previous call to this function ** for the same database connection is overridden. ** ** ^The second argument is a pointer to the function to invoke when a ** row is updated, inserted or deleted. ** ^The first argument to the callback is a copy of the third argument ** to sqlite3_update_hook(). ** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], ** or [SQLITE_UPDATE], depending on the operation that caused the callback ** to be invoked. ** ^The third and fourth arguments to the callback contain pointers to the ** database and table name containing the affected row. ** ^The final callback parameter is the [rowid] of the row. ** ^In the case of an update, this is the [rowid] after the update takes place. ** ** ^(The update hook is not invoked when internal system tables are ** modified (i.e. sqlite_master and sqlite_sequence).)^ ** ** ^In the current implementation, the update hook ** is not invoked when duplication rows are deleted because of an ** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook ** invoked when rows are deleted using the [truncate optimization]. ** The exceptions defined in this paragraph might change in a future ** release of SQLite. ** ** The update hook implementation must not do anything that will modify ** the database connection that invoked the update hook. Any actions ** to modify the database connection must be deferred until after the ** completion of the [sqlite3_step()] call that triggered the update hook. ** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their ** database connections for the meaning of "modify" in this paragraph. ** ** ^The sqlite3_update_hook(D,C,P) function ** returns the P argument from the previous call ** on the same [database connection] D, or NULL for ** the first call on D. ** ** See also the [sqlite3_commit_hook()] and [sqlite3_rollback_hook()] ** interfaces. */ SQLITE_API void *sqlite3_update_hook( sqlite3*, void(*)(void *,int ,char const *,char const *,sqlite3_int64), void* ); /* ** CAPI3REF: Enable Or Disable Shared Pager Cache ** ** ^(This routine enables or disables the sharing of the database cache ** and schema data structures between [database connection | connections] ** to the same database. Sharing is enabled if the argument is true ** and disabled if the argument is false.)^ ** ** ^Cache sharing is enabled and disabled for an entire process. ** This is a change as of SQLite version 3.5.0. In prior versions of SQLite, ** sharing was enabled or disabled for each thread separately. ** ** ^(The cache sharing mode set by this interface effects all subsequent ** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()]. ** Existing database connections continue use the sharing mode ** that was in effect at the time they were opened.)^ ** ** ^(This routine returns [SQLITE_OK] if shared cache was enabled or disabled ** successfully. An [error code] is returned otherwise.)^ ** ** ^Shared cache is disabled by default. But this might change in ** future releases of SQLite. Applications that care about shared ** cache setting should set it explicitly. ** ** See Also: [SQLite Shared-Cache Mode] */ SQLITE_API int sqlite3_enable_shared_cache(int); /* ** CAPI3REF: Attempt To Free Heap Memory ** ** ^The sqlite3_release_memory() interface attempts to free N bytes ** of heap memory by deallocating non-essential memory allocations ** held by the database library. Memory used to cache database ** pages to improve performance is an example of non-essential memory. ** ^sqlite3_release_memory() returns the number of bytes actually freed, ** which might be more or less than the amount requested. ** ^The sqlite3_release_memory() routine is a no-op returning zero ** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT]. ** ** See also: [sqlite3_db_release_memory()] */ SQLITE_API int sqlite3_release_memory(int); /* ** CAPI3REF: Free Memory Used By A Database Connection ** ** ^The sqlite3_db_release_memory(D) interface attempts to free as much heap ** memory as possible from database connection D. Unlike the ** [sqlite3_release_memory()] interface, this interface is effect even ** when then [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is ** omitted. ** ** See also: [sqlite3_release_memory()] */ SQLITE_API int sqlite3_db_release_memory(sqlite3*); /* ** CAPI3REF: Impose A Limit On Heap Size ** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the ** soft limit on the amount of heap memory that may be allocated by SQLite. ** ^SQLite strives to keep heap memory utilization below the soft heap ** limit by reducing the number of pages held in the page cache ** as heap memory usages approaches the limit. ** ^The soft heap limit is "soft" because even though SQLite strives to stay ** below the limit, it will exceed the limit rather than generate ** an [SQLITE_NOMEM] error. In other words, the soft heap limit ** is advisory only. ** ** ^The return value from sqlite3_soft_heap_limit64() is the size of ** the soft heap limit prior to the call, or negative in the case of an ** error. ^If the argument N is negative ** then no change is made to the soft heap limit. Hence, the current ** size of the soft heap limit can be determined by invoking ** sqlite3_soft_heap_limit64() with a negative argument. ** ** ^If the argument N is zero then the soft heap limit is disabled. ** ** ^(The soft heap limit is not enforced in the current implementation ** if one or more of following conditions are true: ** **
      **
    • The soft heap limit is set to zero. **
    • Memory accounting is disabled using a combination of the ** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and ** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option. **
    • An alternative page cache implementation is specified using ** [sqlite3_config]([SQLITE_CONFIG_PCACHE2],...). **
    • The page cache allocates from its own memory pool supplied ** by [sqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than ** from the heap. **
    )^ ** ** Beginning with SQLite version 3.7.3, the soft heap limit is enforced ** regardless of whether or not the [SQLITE_ENABLE_MEMORY_MANAGEMENT] ** compile-time option is invoked. With [SQLITE_ENABLE_MEMORY_MANAGEMENT], ** the soft heap limit is enforced on every memory allocation. Without ** [SQLITE_ENABLE_MEMORY_MANAGEMENT], the soft heap limit is only enforced ** when memory is allocated by the page cache. Testing suggests that because ** the page cache is the predominate memory user in SQLite, most ** applications will achieve adequate soft heap limit enforcement without ** the use of [SQLITE_ENABLE_MEMORY_MANAGEMENT]. ** ** The circumstances under which SQLite will enforce the soft heap limit may ** changes in future releases of SQLite. */ SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); /* ** CAPI3REF: Deprecated Soft Heap Limit Interface ** DEPRECATED ** ** This is a deprecated version of the [sqlite3_soft_heap_limit64()] ** interface. This routine is provided for historical compatibility ** only. All new applications should use the ** [sqlite3_soft_heap_limit64()] interface rather than this one. */ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); /* ** CAPI3REF: Extract Metadata About A Column Of A Table ** ** ^This routine returns metadata about a specific column of a specific ** database table accessible using the [database connection] handle ** passed as the first function argument. ** ** ^The column is identified by the second, third and fourth parameters to ** this function. ^The second parameter is either the name of the database ** (i.e. "main", "temp", or an attached database) containing the specified ** table or NULL. ^If it is NULL, then all attached databases are searched ** for the table using the same algorithm used by the database engine to ** resolve unqualified table references. ** ** ^The third and fourth parameters to this function are the table and column ** name of the desired column, respectively. Neither of these parameters ** may be NULL. ** ** ^Metadata is returned by writing to the memory locations passed as the 5th ** and subsequent parameters to this function. ^Any of these arguments may be ** NULL, in which case the corresponding element of metadata is omitted. ** ** ^(
    ** **
    Parameter Output
    Type
    Description ** **
    5th const char* Data type **
    6th const char* Name of default collation sequence **
    7th int True if column has a NOT NULL constraint **
    8th int True if column is part of the PRIMARY KEY **
    9th int True if column is [AUTOINCREMENT] **
    **
    )^ ** ** ^The memory pointed to by the character pointers returned for the ** declaration type and collation sequence is valid only until the next ** call to any SQLite API function. ** ** ^If the specified table is actually a view, an [error code] is returned. ** ** ^If the specified column is "rowid", "oid" or "_rowid_" and an ** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output ** parameters are set for the explicitly declared column. ^(If there is no ** explicitly declared [INTEGER PRIMARY KEY] column, then the output ** parameters are set as follows: ** **
    **     data type: "INTEGER"
    **     collation sequence: "BINARY"
    **     not null: 0
    **     primary key: 1
    **     auto increment: 0
    ** 
    )^ ** ** ^(This function may load one or more schemas from database files. If an ** error occurs during this process, or if the requested table or column ** cannot be found, an [error code] is returned and an error message left ** in the [database connection] (to be retrieved using sqlite3_errmsg()).)^ ** ** ^This API is only available if the library was compiled with the ** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined. */ SQLITE_API int sqlite3_table_column_metadata( sqlite3 *db, /* Connection handle */ const char *zDbName, /* Database name or NULL */ const char *zTableName, /* Table name */ const char *zColumnName, /* Column name */ char const **pzDataType, /* OUTPUT: Declared data type */ char const **pzCollSeq, /* OUTPUT: Collation sequence name */ int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */ int *pPrimaryKey, /* OUTPUT: True if column part of PK */ int *pAutoinc /* OUTPUT: True if column is auto-increment */ ); /* ** CAPI3REF: Load An Extension ** ** ^This interface loads an SQLite extension library from the named file. ** ** ^The sqlite3_load_extension() interface attempts to load an ** SQLite extension library contained in the file zFile. ** ** ^The entry point is zProc. ** ^zProc may be 0, in which case the name of the entry point ** defaults to "sqlite3_extension_init". ** ^The sqlite3_load_extension() interface returns ** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. ** ^If an error occurs and pzErrMsg is not 0, then the ** [sqlite3_load_extension()] interface shall attempt to ** fill *pzErrMsg with error message text stored in memory ** obtained from [sqlite3_malloc()]. The calling function ** should free this memory by calling [sqlite3_free()]. ** ** ^Extension loading must be enabled using ** [sqlite3_enable_load_extension()] prior to calling this API, ** otherwise an error will be returned. ** ** See also the [load_extension() SQL function]. */ SQLITE_API int sqlite3_load_extension( sqlite3 *db, /* Load the extension into this database connection */ const char *zFile, /* Name of the shared library containing extension */ const char *zProc, /* Entry point. Derived from zFile if 0 */ char **pzErrMsg /* Put error message here if not 0 */ ); /* ** CAPI3REF: Enable Or Disable Extension Loading ** ** ^So as not to open security holes in older applications that are ** unprepared to deal with extension loading, and as a means of disabling ** extension loading while evaluating user-entered SQL, the following API ** is provided to turn the [sqlite3_load_extension()] mechanism on and off. ** ** ^Extension loading is off by default. See ticket #1863. ** ^Call the sqlite3_enable_load_extension() routine with onoff==1 ** to turn extension loading on and call it with onoff==0 to turn ** it back off again. */ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); /* ** CAPI3REF: Automatically Load Statically Linked Extensions ** ** ^This interface causes the xEntryPoint() function to be invoked for ** each new [database connection] that is created. The idea here is that ** xEntryPoint() is the entry point for a statically linked SQLite extension ** that is to be automatically loaded into all new database connections. ** ** ^(Even though the function prototype shows that xEntryPoint() takes ** no arguments and returns void, SQLite invokes xEntryPoint() with three ** arguments and expects and integer result as if the signature of the ** entry point where as follows: ** **
    **    int xEntryPoint(
    **      sqlite3 *db,
    **      const char **pzErrMsg,
    **      const struct sqlite3_api_routines *pThunk
    **    );
    ** 
    )^ ** ** If the xEntryPoint routine encounters an error, it should make *pzErrMsg ** point to an appropriate error message (obtained from [sqlite3_mprintf()]) ** and return an appropriate [error code]. ^SQLite ensures that *pzErrMsg ** is NULL before calling the xEntryPoint(). ^SQLite will invoke ** [sqlite3_free()] on *pzErrMsg after xEntryPoint() returns. ^If any ** xEntryPoint() returns an error, the [sqlite3_open()], [sqlite3_open16()], ** or [sqlite3_open_v2()] call that provoked the xEntryPoint() will fail. ** ** ^Calling sqlite3_auto_extension(X) with an entry point X that is already ** on the list of automatic extensions is a harmless no-op. ^No entry point ** will be called more than once for each database connection that is opened. ** ** See also: [sqlite3_reset_auto_extension()]. */ SQLITE_API int sqlite3_auto_extension(void (*xEntryPoint)(void)); /* ** CAPI3REF: Reset Automatic Extension Loading ** ** ^This interface disables all automatic extensions previously ** registered using [sqlite3_auto_extension()]. */ SQLITE_API void sqlite3_reset_auto_extension(void); /* ** The interface to the virtual-table mechanism is currently considered ** to be experimental. The interface might change in incompatible ways. ** If this is a problem for you, do not use the interface at this time. ** ** When the virtual-table mechanism stabilizes, we will declare the ** interface fixed, support it indefinitely, and remove this comment. */ /* ** Structures used by the virtual table interface */ typedef struct sqlite3_vtab sqlite3_vtab; typedef struct sqlite3_index_info sqlite3_index_info; typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; typedef struct sqlite3_module sqlite3_module; /* ** CAPI3REF: Virtual Table Object ** KEYWORDS: sqlite3_module {virtual table module} ** ** This structure, sometimes called a "virtual table module", ** defines the implementation of a [virtual tables]. ** This structure consists mostly of methods for the module. ** ** ^A virtual table module is created by filling in a persistent ** instance of this structure and passing a pointer to that instance ** to [sqlite3_create_module()] or [sqlite3_create_module_v2()]. ** ^The registration remains valid until it is replaced by a different ** module or until the [database connection] closes. The content ** of this structure must not change while it is registered with ** any database connection. */ struct sqlite3_module { int iVersion; int (*xCreate)(sqlite3*, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char**); int (*xConnect)(sqlite3*, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char**); int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*); int (*xDisconnect)(sqlite3_vtab *pVTab); int (*xDestroy)(sqlite3_vtab *pVTab); int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor); int (*xClose)(sqlite3_vtab_cursor*); int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, int argc, sqlite3_value **argv); int (*xNext)(sqlite3_vtab_cursor*); int (*xEof)(sqlite3_vtab_cursor*); int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int); int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid); int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *); int (*xBegin)(sqlite3_vtab *pVTab); int (*xSync)(sqlite3_vtab *pVTab); int (*xCommit)(sqlite3_vtab *pVTab); int (*xRollback)(sqlite3_vtab *pVTab); int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName, void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg); int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); /* The methods above are in version 1 of the sqlite_module object. Those ** below are for version 2 and greater. */ int (*xSavepoint)(sqlite3_vtab *pVTab, int); int (*xRelease)(sqlite3_vtab *pVTab, int); int (*xRollbackTo)(sqlite3_vtab *pVTab, int); }; /* ** CAPI3REF: Virtual Table Indexing Information ** KEYWORDS: sqlite3_index_info ** ** The sqlite3_index_info structure and its substructures is used as part ** of the [virtual table] interface to ** pass information into and receive the reply from the [xBestIndex] ** method of a [virtual table module]. The fields under **Inputs** are the ** inputs to xBestIndex and are read-only. xBestIndex inserts its ** results into the **Outputs** fields. ** ** ^(The aConstraint[] array records WHERE clause constraints of the form: ** **
    column OP expr
    ** ** where OP is =, <, <=, >, or >=.)^ ^(The particular operator is ** stored in aConstraint[].op using one of the ** [SQLITE_INDEX_CONSTRAINT_EQ | SQLITE_INDEX_CONSTRAINT_ values].)^ ** ^(The index of the column is stored in ** aConstraint[].iColumn.)^ ^(aConstraint[].usable is TRUE if the ** expr on the right-hand side can be evaluated (and thus the constraint ** is usable) and false if it cannot.)^ ** ** ^The optimizer automatically inverts terms of the form "expr OP column" ** and makes other simplifications to the WHERE clause in an attempt to ** get as many WHERE clause terms into the form shown above as possible. ** ^The aConstraint[] array only reports WHERE clause terms that are ** relevant to the particular virtual table being queried. ** ** ^Information about the ORDER BY clause is stored in aOrderBy[]. ** ^Each term of aOrderBy records a column of the ORDER BY clause. ** ** The [xBestIndex] method must fill aConstraintUsage[] with information ** about what parameters to pass to xFilter. ^If argvIndex>0 then ** the right-hand side of the corresponding aConstraint[] is evaluated ** and becomes the argvIndex-th entry in argv. ^(If aConstraintUsage[].omit ** is true, then the constraint is assumed to be fully handled by the ** virtual table and is not checked again by SQLite.)^ ** ** ^The idxNum and idxPtr values are recorded and passed into the ** [xFilter] method. ** ^[sqlite3_free()] is used to free idxPtr if and only if ** needToFreeIdxPtr is true. ** ** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in ** the correct order to satisfy the ORDER BY clause so that no separate ** sorting step is required. ** ** ^The estimatedCost value is an estimate of the cost of doing the ** particular lookup. A full scan of a table with N entries should have ** a cost of N. A binary search of a table of N entries should have a ** cost of approximately log(N). */ struct sqlite3_index_info { /* Inputs */ int nConstraint; /* Number of entries in aConstraint */ struct sqlite3_index_constraint { int iColumn; /* Column on left-hand side of constraint */ unsigned char op; /* Constraint operator */ unsigned char usable; /* True if this constraint is usable */ int iTermOffset; /* Used internally - xBestIndex should ignore */ } *aConstraint; /* Table of WHERE clause constraints */ int nOrderBy; /* Number of terms in the ORDER BY clause */ struct sqlite3_index_orderby { int iColumn; /* Column number */ unsigned char desc; /* True for DESC. False for ASC. */ } *aOrderBy; /* The ORDER BY clause */ /* Outputs */ struct sqlite3_index_constraint_usage { int argvIndex; /* if >0, constraint is part of argv to xFilter */ unsigned char omit; /* Do not code a test for this constraint */ } *aConstraintUsage; int idxNum; /* Number used to identify the index */ char *idxStr; /* String, possibly obtained from sqlite3_malloc */ int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ int orderByConsumed; /* True if output is already ordered */ double estimatedCost; /* Estimated cost of using this index */ }; /* ** CAPI3REF: Virtual Table Constraint Operator Codes ** ** These macros defined the allowed values for the ** [sqlite3_index_info].aConstraint[].op field. Each value represents ** an operator that is part of a constraint term in the wHERE clause of ** a query that uses a [virtual table]. */ #define SQLITE_INDEX_CONSTRAINT_EQ 2 #define SQLITE_INDEX_CONSTRAINT_GT 4 #define SQLITE_INDEX_CONSTRAINT_LE 8 #define SQLITE_INDEX_CONSTRAINT_LT 16 #define SQLITE_INDEX_CONSTRAINT_GE 32 #define SQLITE_INDEX_CONSTRAINT_MATCH 64 /* ** CAPI3REF: Register A Virtual Table Implementation ** ** ^These routines are used to register a new [virtual table module] name. ** ^Module names must be registered before ** creating a new [virtual table] using the module and before using a ** preexisting [virtual table] for the module. ** ** ^The module name is registered on the [database connection] specified ** by the first parameter. ^The name of the module is given by the ** second parameter. ^The third parameter is a pointer to ** the implementation of the [virtual table module]. ^The fourth ** parameter is an arbitrary client data pointer that is passed through ** into the [xCreate] and [xConnect] methods of the virtual table module ** when a new virtual table is be being created or reinitialized. ** ** ^The sqlite3_create_module_v2() interface has a fifth parameter which ** is a pointer to a destructor for the pClientData. ^SQLite will ** invoke the destructor function (if it is not NULL) when SQLite ** no longer needs the pClientData pointer. ^The destructor will also ** be invoked if the call to sqlite3_create_module_v2() fails. ** ^The sqlite3_create_module() ** interface is equivalent to sqlite3_create_module_v2() with a NULL ** destructor. */ SQLITE_API int sqlite3_create_module( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *p, /* Methods for the module */ void *pClientData /* Client data for xCreate/xConnect */ ); SQLITE_API int sqlite3_create_module_v2( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *p, /* Methods for the module */ void *pClientData, /* Client data for xCreate/xConnect */ void(*xDestroy)(void*) /* Module destructor function */ ); /* ** CAPI3REF: Virtual Table Instance Object ** KEYWORDS: sqlite3_vtab ** ** Every [virtual table module] implementation uses a subclass ** of this object to describe a particular instance ** of the [virtual table]. Each subclass will ** be tailored to the specific needs of the module implementation. ** The purpose of this superclass is to define certain fields that are ** common to all module implementations. ** ** ^Virtual tables methods can set an error message by assigning a ** string obtained from [sqlite3_mprintf()] to zErrMsg. The method should ** take care that any prior string is freed by a call to [sqlite3_free()] ** prior to assigning a new string to zErrMsg. ^After the error message ** is delivered up to the client application, the string will be automatically ** freed by sqlite3_free() and the zErrMsg field will be zeroed. */ struct sqlite3_vtab { const sqlite3_module *pModule; /* The module for this virtual table */ int nRef; /* NO LONGER USED */ char *zErrMsg; /* Error message from sqlite3_mprintf() */ /* Virtual table implementations will typically add additional fields */ }; /* ** CAPI3REF: Virtual Table Cursor Object ** KEYWORDS: sqlite3_vtab_cursor {virtual table cursor} ** ** Every [virtual table module] implementation uses a subclass of the ** following structure to describe cursors that point into the ** [virtual table] and are used ** to loop through the virtual table. Cursors are created using the ** [sqlite3_module.xOpen | xOpen] method of the module and are destroyed ** by the [sqlite3_module.xClose | xClose] method. Cursors are used ** by the [xFilter], [xNext], [xEof], [xColumn], and [xRowid] methods ** of the module. Each module implementation will define ** the content of a cursor structure to suit its own needs. ** ** This superclass exists in order to define fields of the cursor that ** are common to all implementations. */ struct sqlite3_vtab_cursor { sqlite3_vtab *pVtab; /* Virtual table of this cursor */ /* Virtual table implementations will typically add additional fields */ }; /* ** CAPI3REF: Declare The Schema Of A Virtual Table ** ** ^The [xCreate] and [xConnect] methods of a ** [virtual table module] call this interface ** to declare the format (the names and datatypes of the columns) of ** the virtual tables they implement. */ SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL); /* ** CAPI3REF: Overload A Function For A Virtual Table ** ** ^(Virtual tables can provide alternative implementations of functions ** using the [xFindFunction] method of the [virtual table module]. ** But global versions of those functions ** must exist in order to be overloaded.)^ ** ** ^(This API makes sure a global version of a function with a particular ** name and number of parameters exists. If no such function exists ** before this API is called, a new function is created.)^ ^The implementation ** of the new function always causes an exception to be thrown. So ** the new function is not good for anything by itself. Its only ** purpose is to be a placeholder function that can be overloaded ** by a [virtual table]. */ SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); /* ** The interface to the virtual-table mechanism defined above (back up ** to a comment remarkably similar to this one) is currently considered ** to be experimental. The interface might change in incompatible ways. ** If this is a problem for you, do not use the interface at this time. ** ** When the virtual-table mechanism stabilizes, we will declare the ** interface fixed, support it indefinitely, and remove this comment. */ /* ** CAPI3REF: A Handle To An Open BLOB ** KEYWORDS: {BLOB handle} {BLOB handles} ** ** An instance of this object represents an open BLOB on which ** [sqlite3_blob_open | incremental BLOB I/O] can be performed. ** ^Objects of this type are created by [sqlite3_blob_open()] ** and destroyed by [sqlite3_blob_close()]. ** ^The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces ** can be used to read or write small subsections of the BLOB. ** ^The [sqlite3_blob_bytes()] interface returns the size of the BLOB in bytes. */ typedef struct sqlite3_blob sqlite3_blob; /* ** CAPI3REF: Open A BLOB For Incremental I/O ** ** ^(This interfaces opens a [BLOB handle | handle] to the BLOB located ** in row iRow, column zColumn, table zTable in database zDb; ** in other words, the same BLOB that would be selected by: ** **
    **     SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow;
    ** 
    )^ ** ** ^If the flags parameter is non-zero, then the BLOB is opened for read ** and write access. ^If it is zero, the BLOB is opened for read access. ** ^It is not possible to open a column that is part of an index or primary ** key for writing. ^If [foreign key constraints] are enabled, it is ** not possible to open a column that is part of a [child key] for writing. ** ** ^Note that the database name is not the filename that contains ** the database but rather the symbolic name of the database that ** appears after the AS keyword when the database is connected using [ATTACH]. ** ^For the main database file, the database name is "main". ** ^For TEMP tables, the database name is "temp". ** ** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is written ** to *ppBlob. Otherwise an [error code] is returned and *ppBlob is set ** to be a null pointer.)^ ** ^This function sets the [database connection] error code and message ** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()] and related ** functions. ^Note that the *ppBlob variable is always initialized in a ** way that makes it safe to invoke [sqlite3_blob_close()] on *ppBlob ** regardless of the success or failure of this routine. ** ** ^(If the row that a BLOB handle points to is modified by an ** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects ** then the BLOB handle is marked as "expired". ** This is true if any column of the row is changed, even a column ** other than the one the BLOB handle is open on.)^ ** ^Calls to [sqlite3_blob_read()] and [sqlite3_blob_write()] for ** an expired BLOB handle fail with a return code of [SQLITE_ABORT]. ** ^(Changes written into a BLOB prior to the BLOB expiring are not ** rolled back by the expiration of the BLOB. Such changes will eventually ** commit if the transaction continues to completion.)^ ** ** ^Use the [sqlite3_blob_bytes()] interface to determine the size of ** the opened blob. ^The size of a blob may not be changed by this ** interface. Use the [UPDATE] SQL command to change the size of a ** blob. ** ** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces ** and the built-in [zeroblob] SQL function can be used, if desired, ** to create an empty, zero-filled blob in which to read or write using ** this interface. ** ** To avoid a resource leak, every open [BLOB handle] should eventually ** be released by a call to [sqlite3_blob_close()]. */ SQLITE_API int sqlite3_blob_open( sqlite3*, const char *zDb, const char *zTable, const char *zColumn, sqlite3_int64 iRow, int flags, sqlite3_blob **ppBlob ); /* ** CAPI3REF: Move a BLOB Handle to a New Row ** ** ^This function is used to move an existing blob handle so that it points ** to a different row of the same database table. ^The new row is identified ** by the rowid value passed as the second argument. Only the row can be ** changed. ^The database, table and column on which the blob handle is open ** remain the same. Moving an existing blob handle to a new row can be ** faster than closing the existing handle and opening a new one. ** ** ^(The new row must meet the same criteria as for [sqlite3_blob_open()] - ** it must exist and there must be either a blob or text value stored in ** the nominated column.)^ ^If the new row is not present in the table, or if ** it does not contain a blob or text value, or if another error occurs, an ** SQLite error code is returned and the blob handle is considered aborted. ** ^All subsequent calls to [sqlite3_blob_read()], [sqlite3_blob_write()] or ** [sqlite3_blob_reopen()] on an aborted blob handle immediately return ** SQLITE_ABORT. ^Calling [sqlite3_blob_bytes()] on an aborted blob handle ** always returns zero. ** ** ^This function sets the database handle error code and message. */ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); /* ** CAPI3REF: Close A BLOB Handle ** ** ^Closes an open [BLOB handle]. ** ** ^Closing a BLOB shall cause the current transaction to commit ** if there are no other BLOBs, no pending prepared statements, and the ** database connection is in [autocommit mode]. ** ^If any writes were made to the BLOB, they might be held in cache ** until the close operation if they will fit. ** ** ^(Closing the BLOB often forces the changes ** out to disk and so if any I/O errors occur, they will likely occur ** at the time when the BLOB is closed. Any errors that occur during ** closing are reported as a non-zero return value.)^ ** ** ^(The BLOB is closed unconditionally. Even if this routine returns ** an error code, the BLOB is still closed.)^ ** ** ^Calling this routine with a null pointer (such as would be returned ** by a failed call to [sqlite3_blob_open()]) is a harmless no-op. */ SQLITE_API int sqlite3_blob_close(sqlite3_blob *); /* ** CAPI3REF: Return The Size Of An Open BLOB ** ** ^Returns the size in bytes of the BLOB accessible via the ** successfully opened [BLOB handle] in its only argument. ^The ** incremental blob I/O routines can only read or overwriting existing ** blob content; they cannot change the size of a blob. ** ** This routine only works on a [BLOB handle] which has been created ** by a prior successful call to [sqlite3_blob_open()] and which has not ** been closed by [sqlite3_blob_close()]. Passing any other pointer in ** to this routine results in undefined and probably undesirable behavior. */ SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *); /* ** CAPI3REF: Read Data From A BLOB Incrementally ** ** ^(This function is used to read data from an open [BLOB handle] into a ** caller-supplied buffer. N bytes of data are copied into buffer Z ** from the open BLOB, starting at offset iOffset.)^ ** ** ^If offset iOffset is less than N bytes from the end of the BLOB, ** [SQLITE_ERROR] is returned and no data is read. ^If N or iOffset is ** less than zero, [SQLITE_ERROR] is returned and no data is read. ** ^The size of the blob (and hence the maximum value of N+iOffset) ** can be determined using the [sqlite3_blob_bytes()] interface. ** ** ^An attempt to read from an expired [BLOB handle] fails with an ** error code of [SQLITE_ABORT]. ** ** ^(On success, sqlite3_blob_read() returns SQLITE_OK. ** Otherwise, an [error code] or an [extended error code] is returned.)^ ** ** This routine only works on a [BLOB handle] which has been created ** by a prior successful call to [sqlite3_blob_open()] and which has not ** been closed by [sqlite3_blob_close()]. Passing any other pointer in ** to this routine results in undefined and probably undesirable behavior. ** ** See also: [sqlite3_blob_write()]. */ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); /* ** CAPI3REF: Write Data Into A BLOB Incrementally ** ** ^This function is used to write data into an open [BLOB handle] from a ** caller-supplied buffer. ^N bytes of data are copied from the buffer Z ** into the open BLOB, starting at offset iOffset. ** ** ^If the [BLOB handle] passed as the first argument was not opened for ** writing (the flags parameter to [sqlite3_blob_open()] was zero), ** this function returns [SQLITE_READONLY]. ** ** ^This function may only modify the contents of the BLOB; it is ** not possible to increase the size of a BLOB using this API. ** ^If offset iOffset is less than N bytes from the end of the BLOB, ** [SQLITE_ERROR] is returned and no data is written. ^If N is ** less than zero [SQLITE_ERROR] is returned and no data is written. ** The size of the BLOB (and hence the maximum value of N+iOffset) ** can be determined using the [sqlite3_blob_bytes()] interface. ** ** ^An attempt to write to an expired [BLOB handle] fails with an ** error code of [SQLITE_ABORT]. ^Writes to the BLOB that occurred ** before the [BLOB handle] expired are not rolled back by the ** expiration of the handle, though of course those changes might ** have been overwritten by the statement that expired the BLOB handle ** or by other independent statements. ** ** ^(On success, sqlite3_blob_write() returns SQLITE_OK. ** Otherwise, an [error code] or an [extended error code] is returned.)^ ** ** This routine only works on a [BLOB handle] which has been created ** by a prior successful call to [sqlite3_blob_open()] and which has not ** been closed by [sqlite3_blob_close()]. Passing any other pointer in ** to this routine results in undefined and probably undesirable behavior. ** ** See also: [sqlite3_blob_read()]. */ SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); /* ** CAPI3REF: Virtual File System Objects ** ** A virtual filesystem (VFS) is an [sqlite3_vfs] object ** that SQLite uses to interact ** with the underlying operating system. Most SQLite builds come with a ** single default VFS that is appropriate for the host computer. ** New VFSes can be registered and existing VFSes can be unregistered. ** The following interfaces are provided. ** ** ^The sqlite3_vfs_find() interface returns a pointer to a VFS given its name. ** ^Names are case sensitive. ** ^Names are zero-terminated UTF-8 strings. ** ^If there is no match, a NULL pointer is returned. ** ^If zVfsName is NULL then the default VFS is returned. ** ** ^New VFSes are registered with sqlite3_vfs_register(). ** ^Each new VFS becomes the default VFS if the makeDflt flag is set. ** ^The same VFS can be registered multiple times without injury. ** ^To make an existing VFS into the default VFS, register it again ** with the makeDflt flag set. If two different VFSes with the ** same name are registered, the behavior is undefined. If a ** VFS is registered with a name that is NULL or an empty string, ** then the behavior is undefined. ** ** ^Unregister a VFS with the sqlite3_vfs_unregister() interface. ** ^(If the default VFS is unregistered, another VFS is chosen as ** the default. The choice for the new VFS is arbitrary.)^ */ SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName); SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); /* ** CAPI3REF: Mutexes ** ** The SQLite core uses these routines for thread ** synchronization. Though they are intended for internal ** use by SQLite, code that links against SQLite is ** permitted to use any of these routines. ** ** The SQLite source code contains multiple implementations ** of these mutex routines. An appropriate implementation ** is selected automatically at compile-time. ^(The following ** implementations are available in the SQLite core: ** **
      **
    • SQLITE_MUTEX_PTHREADS **
    • SQLITE_MUTEX_W32 **
    • SQLITE_MUTEX_NOOP **
    )^ ** ** ^The SQLITE_MUTEX_NOOP implementation is a set of routines ** that does no real locking and is appropriate for use in ** a single-threaded application. ^The SQLITE_MUTEX_PTHREADS and ** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix ** and Windows. ** ** ^(If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor ** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex ** implementation is included with the library. In this case the ** application must supply a custom mutex implementation using the ** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function ** before calling sqlite3_initialize() or any other public sqlite3_ ** function that calls sqlite3_initialize().)^ ** ** ^The sqlite3_mutex_alloc() routine allocates a new ** mutex and returns a pointer to it. ^If it returns NULL ** that means that a mutex could not be allocated. ^SQLite ** will unwind its stack and return an error. ^(The argument ** to sqlite3_mutex_alloc() is one of these integer constants: ** **
      **
    • SQLITE_MUTEX_FAST **
    • SQLITE_MUTEX_RECURSIVE **
    • SQLITE_MUTEX_STATIC_MASTER **
    • SQLITE_MUTEX_STATIC_MEM **
    • SQLITE_MUTEX_STATIC_MEM2 **
    • SQLITE_MUTEX_STATIC_PRNG **
    • SQLITE_MUTEX_STATIC_LRU **
    • SQLITE_MUTEX_STATIC_LRU2 **
    )^ ** ** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) ** cause sqlite3_mutex_alloc() to create ** a new mutex. ^The new mutex is recursive when SQLITE_MUTEX_RECURSIVE ** is used but not necessarily so when SQLITE_MUTEX_FAST is used. ** The mutex implementation does not need to make a distinction ** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does ** not want to. ^SQLite will only request a recursive mutex in ** cases where it really needs one. ^If a faster non-recursive mutex ** implementation is available on the host platform, the mutex subsystem ** might return such a mutex in response to SQLITE_MUTEX_FAST. ** ** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other ** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return ** a pointer to a static preexisting mutex. ^Six static mutexes are ** used by the current version of SQLite. Future versions of SQLite ** may add additional static mutexes. Static mutexes are for internal ** use by SQLite only. Applications that use SQLite mutexes should ** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or ** SQLITE_MUTEX_RECURSIVE. ** ** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST ** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() ** returns a different mutex on every call. ^But for the static ** mutex types, the same mutex is returned on every call that has ** the same type number. ** ** ^The sqlite3_mutex_free() routine deallocates a previously ** allocated dynamic mutex. ^SQLite is careful to deallocate every ** dynamic mutex that it allocates. The dynamic mutexes must not be in ** use when they are deallocated. Attempting to deallocate a static ** mutex results in undefined behavior. ^SQLite never deallocates ** a static mutex. ** ** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt ** to enter a mutex. ^If another thread is already within the mutex, ** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return ** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK] ** upon successful entry. ^(Mutexes created using ** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread. ** In such cases the, ** mutex must be exited an equal number of times before another thread ** can enter.)^ ^(If the same thread tries to enter any other ** kind of mutex more than once, the behavior is undefined. ** SQLite will never exhibit ** such behavior in its own use of mutexes.)^ ** ** ^(Some systems (for example, Windows 95) do not support the operation ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() ** will always return SQLITE_BUSY. The SQLite core only ever uses ** sqlite3_mutex_try() as an optimization so this is acceptable behavior.)^ ** ** ^The sqlite3_mutex_leave() routine exits a mutex that was ** previously entered by the same thread. ^(The behavior ** is undefined if the mutex is not currently entered by the ** calling thread or is not currently allocated. SQLite will ** never do either.)^ ** ** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or ** sqlite3_mutex_leave() is a NULL pointer, then all three routines ** behave as no-ops. ** ** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. */ SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int); SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*); SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*); SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*); SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); /* ** CAPI3REF: Mutex Methods Object ** ** An instance of this structure defines the low-level routines ** used to allocate and use mutexes. ** ** Usually, the default mutex implementations provided by SQLite are ** sufficient, however the user has the option of substituting a custom ** implementation for specialized deployments or systems for which SQLite ** does not provide a suitable implementation. In this case, the user ** creates and populates an instance of this structure to pass ** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option. ** Additionally, an instance of this structure can be used as an ** output variable when querying the system for the current mutex ** implementation, using the [SQLITE_CONFIG_GETMUTEX] option. ** ** ^The xMutexInit method defined by this structure is invoked as ** part of system initialization by the sqlite3_initialize() function. ** ^The xMutexInit routine is called by SQLite exactly once for each ** effective call to [sqlite3_initialize()]. ** ** ^The xMutexEnd method defined by this structure is invoked as ** part of system shutdown by the sqlite3_shutdown() function. The ** implementation of this method is expected to release all outstanding ** resources obtained by the mutex methods implementation, especially ** those obtained by the xMutexInit method. ^The xMutexEnd() ** interface is invoked exactly once for each call to [sqlite3_shutdown()]. ** ** ^(The remaining seven methods defined by this structure (xMutexAlloc, ** xMutexFree, xMutexEnter, xMutexTry, xMutexLeave, xMutexHeld and ** xMutexNotheld) implement the following interfaces (respectively): ** **
      **
    • [sqlite3_mutex_alloc()]
    • **
    • [sqlite3_mutex_free()]
    • **
    • [sqlite3_mutex_enter()]
    • **
    • [sqlite3_mutex_try()]
    • **
    • [sqlite3_mutex_leave()]
    • **
    • [sqlite3_mutex_held()]
    • **
    • [sqlite3_mutex_notheld()]
    • **
    )^ ** ** The only difference is that the public sqlite3_XXX functions enumerated ** above silently ignore any invocations that pass a NULL pointer instead ** of a valid mutex handle. The implementations of the methods defined ** by this structure are not required to handle this case, the results ** of passing a NULL pointer instead of a valid mutex handle are undefined ** (i.e. it is acceptable to provide an implementation that segfaults if ** it is passed a NULL pointer). ** ** The xMutexInit() method must be threadsafe. ^It must be harmless to ** invoke xMutexInit() multiple times within the same process and without ** intervening calls to xMutexEnd(). Second and subsequent calls to ** xMutexInit() must be no-ops. ** ** ^xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] ** and its associates). ^Similarly, xMutexAlloc() must not use SQLite memory ** allocation for a static mutex. ^However xMutexAlloc() may use SQLite ** memory allocation for a fast or recursive mutex. ** ** ^SQLite will invoke the xMutexEnd() method when [sqlite3_shutdown()] is ** called, but only if the prior call to xMutexInit returned SQLITE_OK. ** If xMutexInit fails in any way, it is expected to clean up after itself ** prior to returning. */ typedef struct sqlite3_mutex_methods sqlite3_mutex_methods; struct sqlite3_mutex_methods { int (*xMutexInit)(void); int (*xMutexEnd)(void); sqlite3_mutex *(*xMutexAlloc)(int); void (*xMutexFree)(sqlite3_mutex *); void (*xMutexEnter)(sqlite3_mutex *); int (*xMutexTry)(sqlite3_mutex *); void (*xMutexLeave)(sqlite3_mutex *); int (*xMutexHeld)(sqlite3_mutex *); int (*xMutexNotheld)(sqlite3_mutex *); }; /* ** CAPI3REF: Mutex Verification Routines ** ** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines ** are intended for use inside assert() statements. ^The SQLite core ** never uses these routines except inside an assert() and applications ** are advised to follow the lead of the core. ^The SQLite core only ** provides implementations for these routines when it is compiled ** with the SQLITE_DEBUG flag. ^External mutex implementations ** are only required to provide these routines if SQLITE_DEBUG is ** defined and if NDEBUG is not defined. ** ** ^These routines should return true if the mutex in their argument ** is held or not held, respectively, by the calling thread. ** ** ^The implementation is not required to provide versions of these ** routines that actually work. If the implementation does not provide working ** versions of these routines, it should at least provide stubs that always ** return true so that one does not get spurious assertion failures. ** ** ^If the argument to sqlite3_mutex_held() is a NULL pointer then ** the routine should return 1. This seems counter-intuitive since ** clearly the mutex cannot be held if it does not exist. But ** the reason the mutex does not exist is because the build is not ** using mutexes. And we do not want the assert() containing the ** call to sqlite3_mutex_held() to fail, so a non-zero return is ** the appropriate thing to do. ^The sqlite3_mutex_notheld() ** interface should also return 1 when given a NULL pointer. */ #ifndef NDEBUG SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); #endif /* ** CAPI3REF: Mutex Types ** ** The [sqlite3_mutex_alloc()] interface takes a single argument ** which is one of these integer constants. ** ** The set of static mutexes may change from one SQLite release to the ** next. Applications that override the built-in mutex logic must be ** prepared to accommodate additional static mutexes. */ #define SQLITE_MUTEX_FAST 0 #define SQLITE_MUTEX_RECURSIVE 1 #define SQLITE_MUTEX_STATIC_MASTER 2 #define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ #define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ #define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ #define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */ #define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ #define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ #define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ /* ** CAPI3REF: Retrieve the mutex for a database connection ** ** ^This interface returns a pointer the [sqlite3_mutex] object that ** serializes access to the [database connection] given in the argument ** when the [threading mode] is Serialized. ** ^If the [threading mode] is Single-thread or Multi-thread then this ** routine returns a NULL pointer. */ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*); /* ** CAPI3REF: Low-Level Control Of Database Files ** ** ^The [sqlite3_file_control()] interface makes a direct call to the ** xFileControl method for the [sqlite3_io_methods] object associated ** with a particular database identified by the second argument. ^The ** name of the database is "main" for the main database or "temp" for the ** TEMP database, or the name that appears after the AS keyword for ** databases that are added using the [ATTACH] SQL command. ** ^A NULL pointer can be used in place of "main" to refer to the ** main database file. ** ^The third and fourth parameters to this routine ** are passed directly through to the second and third parameters of ** the xFileControl method. ^The return value of the xFileControl ** method becomes the return value of this routine. ** ** ^The SQLITE_FCNTL_FILE_POINTER value for the op parameter causes ** a pointer to the underlying [sqlite3_file] object to be written into ** the space pointed to by the 4th parameter. ^The SQLITE_FCNTL_FILE_POINTER ** case is a short-circuit path which does not actually invoke the ** underlying sqlite3_io_methods.xFileControl method. ** ** ^If the second parameter (zDbName) does not match the name of any ** open database file, then SQLITE_ERROR is returned. ^This error ** code is not remembered and will not be recalled by [sqlite3_errcode()] ** or [sqlite3_errmsg()]. The underlying xFileControl method might ** also return SQLITE_ERROR. There is no way to distinguish between ** an incorrect zDbName and an SQLITE_ERROR return from the underlying ** xFileControl method. ** ** See also: [SQLITE_FCNTL_LOCKSTATE] */ SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); /* ** CAPI3REF: Testing Interface ** ** ^The sqlite3_test_control() interface is used to read out internal ** state of SQLite and to inject faults into SQLite for testing ** purposes. ^The first parameter is an operation code that determines ** the number, meaning, and operation of all subsequent parameters. ** ** This interface is not for use by applications. It exists solely ** for verifying the correct operation of the SQLite library. Depending ** on how the SQLite library is compiled, this interface might not exist. ** ** The details of the operation codes, their meanings, the parameters ** they take, and what they do are all subject to change without notice. ** Unlike most of the SQLite API, this function is not guaranteed to ** operate consistently from one release to the next. */ SQLITE_API int sqlite3_test_control(int op, ...); /* ** CAPI3REF: Testing Interface Operation Codes ** ** These constants are the valid operation code parameters used ** as the first argument to [sqlite3_test_control()]. ** ** These parameters and their meanings are subject to change ** without notice. These values are for testing purposes only. ** Applications should not use any of these parameters or the ** [sqlite3_test_control()] interface. */ #define SQLITE_TESTCTRL_FIRST 5 #define SQLITE_TESTCTRL_PRNG_SAVE 5 #define SQLITE_TESTCTRL_PRNG_RESTORE 6 #define SQLITE_TESTCTRL_PRNG_RESET 7 #define SQLITE_TESTCTRL_BITVEC_TEST 8 #define SQLITE_TESTCTRL_FAULT_INSTALL 9 #define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 #define SQLITE_TESTCTRL_PENDING_BYTE 11 #define SQLITE_TESTCTRL_ASSERT 12 #define SQLITE_TESTCTRL_ALWAYS 13 #define SQLITE_TESTCTRL_RESERVE 14 #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 #define SQLITE_TESTCTRL_EXPLAIN_STMT 19 #define SQLITE_TESTCTRL_LAST 19 /* ** CAPI3REF: SQLite Runtime Status ** ** ^This interface is used to retrieve runtime status information ** about the performance of SQLite, and optionally to reset various ** highwater marks. ^The first argument is an integer code for ** the specific parameter to measure. ^(Recognized integer codes ** are of the form [status parameters | SQLITE_STATUS_...].)^ ** ^The current value of the parameter is returned into *pCurrent. ** ^The highest recorded value is returned in *pHighwater. ^If the ** resetFlag is true, then the highest record value is reset after ** *pHighwater is written. ^(Some parameters do not record the highest ** value. For those parameters ** nothing is written into *pHighwater and the resetFlag is ignored.)^ ** ^(Other parameters record only the highwater mark and not the current ** value. For these latter parameters nothing is written into *pCurrent.)^ ** ** ^The sqlite3_status() routine returns SQLITE_OK on success and a ** non-zero [error code] on failure. ** ** This routine is threadsafe but is not atomic. This routine can be ** called while other threads are running the same or different SQLite ** interfaces. However the values returned in *pCurrent and ** *pHighwater reflect the status of SQLite at different points in time ** and it is possible that another thread might change the parameter ** in between the times when *pCurrent and *pHighwater are written. ** ** See also: [sqlite3_db_status()] */ SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); /* ** CAPI3REF: Status Parameters ** KEYWORDS: {status parameters} ** ** These integer constants designate various run-time status parameters ** that can be returned by [sqlite3_status()]. ** **
    ** [[SQLITE_STATUS_MEMORY_USED]] ^(
    SQLITE_STATUS_MEMORY_USED
    **
    This parameter is the current amount of memory checked out ** using [sqlite3_malloc()], either directly or indirectly. The ** figure includes calls made to [sqlite3_malloc()] by the application ** and internal memory usage by the SQLite library. Scratch memory ** controlled by [SQLITE_CONFIG_SCRATCH] and auxiliary page-cache ** memory controlled by [SQLITE_CONFIG_PAGECACHE] is not included in ** this parameter. The amount returned is the sum of the allocation ** sizes as reported by the xSize method in [sqlite3_mem_methods].
    )^ ** ** [[SQLITE_STATUS_MALLOC_SIZE]] ^(
    SQLITE_STATUS_MALLOC_SIZE
    **
    This parameter records the largest memory allocation request ** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their ** internal equivalents). Only the value returned in the ** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
    )^ ** ** [[SQLITE_STATUS_MALLOC_COUNT]] ^(
    SQLITE_STATUS_MALLOC_COUNT
    **
    This parameter records the number of separate memory allocations ** currently checked out.
    )^ ** ** [[SQLITE_STATUS_PAGECACHE_USED]] ^(
    SQLITE_STATUS_PAGECACHE_USED
    **
    This parameter returns the number of pages used out of the ** [pagecache memory allocator] that was configured using ** [SQLITE_CONFIG_PAGECACHE]. The ** value returned is in pages, not in bytes.
    )^ ** ** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] ** ^(
    SQLITE_STATUS_PAGECACHE_OVERFLOW
    **
    This parameter returns the number of bytes of page cache ** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] ** buffer and where forced to overflow to [sqlite3_malloc()]. The ** returned value includes allocations that overflowed because they ** where too large (they were larger than the "sz" parameter to ** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because ** no space was left in the page cache.
    )^ ** ** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(
    SQLITE_STATUS_PAGECACHE_SIZE
    **
    This parameter records the largest memory allocation request ** handed to [pagecache memory allocator]. Only the value returned in the ** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
    )^ ** ** [[SQLITE_STATUS_SCRATCH_USED]] ^(
    SQLITE_STATUS_SCRATCH_USED
    **
    This parameter returns the number of allocations used out of the ** [scratch memory allocator] configured using ** [SQLITE_CONFIG_SCRATCH]. The value returned is in allocations, not ** in bytes. Since a single thread may only have one scratch allocation ** outstanding at time, this parameter also reports the number of threads ** using scratch memory at the same time.
    )^ ** ** [[SQLITE_STATUS_SCRATCH_OVERFLOW]] ^(
    SQLITE_STATUS_SCRATCH_OVERFLOW
    **
    This parameter returns the number of bytes of scratch memory ** allocation which could not be satisfied by the [SQLITE_CONFIG_SCRATCH] ** buffer and where forced to overflow to [sqlite3_malloc()]. The values ** returned include overflows because the requested allocation was too ** larger (that is, because the requested allocation was larger than the ** "sz" parameter to [SQLITE_CONFIG_SCRATCH]) and because no scratch buffer ** slots were available. **
    )^ ** ** [[SQLITE_STATUS_SCRATCH_SIZE]] ^(
    SQLITE_STATUS_SCRATCH_SIZE
    **
    This parameter records the largest memory allocation request ** handed to [scratch memory allocator]. Only the value returned in the ** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
    )^ ** ** [[SQLITE_STATUS_PARSER_STACK]] ^(
    SQLITE_STATUS_PARSER_STACK
    **
    This parameter records the deepest parser stack. It is only ** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].
    )^ **
    ** ** New status parameters may be added from time to time. */ #define SQLITE_STATUS_MEMORY_USED 0 #define SQLITE_STATUS_PAGECACHE_USED 1 #define SQLITE_STATUS_PAGECACHE_OVERFLOW 2 #define SQLITE_STATUS_SCRATCH_USED 3 #define SQLITE_STATUS_SCRATCH_OVERFLOW 4 #define SQLITE_STATUS_MALLOC_SIZE 5 #define SQLITE_STATUS_PARSER_STACK 6 #define SQLITE_STATUS_PAGECACHE_SIZE 7 #define SQLITE_STATUS_SCRATCH_SIZE 8 #define SQLITE_STATUS_MALLOC_COUNT 9 /* ** CAPI3REF: Database Connection Status ** ** ^This interface is used to retrieve runtime status information ** about a single [database connection]. ^The first argument is the ** database connection object to be interrogated. ^The second argument ** is an integer constant, taken from the set of ** [SQLITE_DBSTATUS options], that ** determines the parameter to interrogate. The set of ** [SQLITE_DBSTATUS options] is likely ** to grow in future releases of SQLite. ** ** ^The current value of the requested parameter is written into *pCur ** and the highest instantaneous value is written into *pHiwtr. ^If ** the resetFlg is true, then the highest instantaneous value is ** reset back down to the current value. ** ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a ** non-zero [error code] on failure. ** ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. */ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); /* ** CAPI3REF: Status Parameters for database connections ** KEYWORDS: {SQLITE_DBSTATUS options} ** ** These constants are the available integer "verbs" that can be passed as ** the second argument to the [sqlite3_db_status()] interface. ** ** New verbs may be added in future releases of SQLite. Existing verbs ** might be discontinued. Applications should check the return code from ** [sqlite3_db_status()] to make sure that the call worked. ** The [sqlite3_db_status()] interface will return a non-zero error code ** if a discontinued or unsupported verb is invoked. ** **
    ** [[SQLITE_DBSTATUS_LOOKASIDE_USED]] ^(
    SQLITE_DBSTATUS_LOOKASIDE_USED
    **
    This parameter returns the number of lookaside memory slots currently ** checked out.
    )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(
    SQLITE_DBSTATUS_LOOKASIDE_HIT
    **
    This parameter returns the number malloc attempts that were ** satisfied using lookaside memory. Only the high-water value is meaningful; ** the current value is always zero.)^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]] ** ^(
    SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
    **
    This parameter returns the number malloc attempts that might have ** been satisfied using lookaside memory but failed due to the amount of ** memory requested being larger than the lookaside slot size. ** Only the high-water value is meaningful; ** the current value is always zero.)^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]] ** ^(
    SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
    **
    This parameter returns the number malloc attempts that might have ** been satisfied using lookaside memory but failed due to all lookaside ** memory already being in use. ** Only the high-water value is meaningful; ** the current value is always zero.)^ ** ** [[SQLITE_DBSTATUS_CACHE_USED]] ^(
    SQLITE_DBSTATUS_CACHE_USED
    **
    This parameter returns the approximate number of of bytes of heap ** memory used by all pager caches associated with the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. ** ** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(
    SQLITE_DBSTATUS_SCHEMA_USED
    **
    This parameter returns the approximate number of of bytes of heap ** memory used to store the schema for all databases associated ** with the connection - main, temp, and any [ATTACH]-ed databases.)^ ** ^The full amount of memory used by the schemas is reported, even if the ** schema memory is shared with other database connections due to ** [shared cache mode] being enabled. ** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0. ** ** [[SQLITE_DBSTATUS_STMT_USED]] ^(
    SQLITE_DBSTATUS_STMT_USED
    **
    This parameter returns the approximate number of of bytes of heap ** and lookaside memory used by all prepared statements associated with ** the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0. **
    ** ** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(
    SQLITE_DBSTATUS_CACHE_HIT
    **
    This parameter returns the number of pager cache hits that have ** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT ** is always 0. **
    ** ** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(
    SQLITE_DBSTATUS_CACHE_MISS
    **
    This parameter returns the number of pager cache misses that have ** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS ** is always 0. **
    ** ** [[SQLITE_DBSTATUS_CACHE_WRITE]] ^(
    SQLITE_DBSTATUS_CACHE_WRITE
    **
    This parameter returns the number of dirty cache entries that have ** been written to disk. Specifically, the number of pages written to the ** wal file in wal mode databases, or the number of pages written to the ** database file in rollback mode databases. Any pages written as part of ** transaction rollback or database recovery operations are not included. ** If an IO or other error occurs while writing a page to disk, the effect ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. **
    **
    */ #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 #define SQLITE_DBSTATUS_CACHE_USED 1 #define SQLITE_DBSTATUS_SCHEMA_USED 2 #define SQLITE_DBSTATUS_STMT_USED 3 #define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 #define SQLITE_DBSTATUS_CACHE_HIT 7 #define SQLITE_DBSTATUS_CACHE_MISS 8 #define SQLITE_DBSTATUS_CACHE_WRITE 9 #define SQLITE_DBSTATUS_MAX 9 /* Largest defined DBSTATUS */ /* ** CAPI3REF: Prepared Statement Status ** ** ^(Each prepared statement maintains various ** [SQLITE_STMTSTATUS counters] that measure the number ** of times it has performed specific operations.)^ These counters can ** be used to monitor the performance characteristics of the prepared ** statements. For example, if the number of table steps greatly exceeds ** the number of table searches or result rows, that would tend to indicate ** that the prepared statement is using a full table scan rather than ** an index. ** ** ^(This interface is used to retrieve and reset counter values from ** a [prepared statement]. The first argument is the prepared statement ** object to be interrogated. The second argument ** is an integer code for a specific [SQLITE_STMTSTATUS counter] ** to be interrogated.)^ ** ^The current value of the requested counter is returned. ** ^If the resetFlg is true, then the counter is reset to zero after this ** interface call returns. ** ** See also: [sqlite3_status()] and [sqlite3_db_status()]. */ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); /* ** CAPI3REF: Status Parameters for prepared statements ** KEYWORDS: {SQLITE_STMTSTATUS counter} {SQLITE_STMTSTATUS counters} ** ** These preprocessor macros define integer codes that name counter ** values associated with the [sqlite3_stmt_status()] interface. ** The meanings of the various counters are as follows: ** **
    ** [[SQLITE_STMTSTATUS_FULLSCAN_STEP]]
    SQLITE_STMTSTATUS_FULLSCAN_STEP
    **
    ^This is the number of times that SQLite has stepped forward in ** a table as part of a full table scan. Large numbers for this counter ** may indicate opportunities for performance improvement through ** careful use of indices.
    ** ** [[SQLITE_STMTSTATUS_SORT]]
    SQLITE_STMTSTATUS_SORT
    **
    ^This is the number of sort operations that have occurred. ** A non-zero value in this counter may indicate an opportunity to ** improvement performance through careful use of indices.
    ** ** [[SQLITE_STMTSTATUS_AUTOINDEX]]
    SQLITE_STMTSTATUS_AUTOINDEX
    **
    ^This is the number of rows inserted into transient indices that ** were created automatically in order to help joins run faster. ** A non-zero value in this counter may indicate an opportunity to ** improvement performance by adding permanent indices that do not ** need to be reinitialized each time the statement is run.
    **
    */ #define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 #define SQLITE_STMTSTATUS_SORT 2 #define SQLITE_STMTSTATUS_AUTOINDEX 3 /* ** CAPI3REF: Custom Page Cache Object ** ** The sqlite3_pcache type is opaque. It is implemented by ** the pluggable module. The SQLite core has no knowledge of ** its size or internal structure and never deals with the ** sqlite3_pcache object except by holding and passing pointers ** to the object. ** ** See [sqlite3_pcache_methods2] for additional information. */ typedef struct sqlite3_pcache sqlite3_pcache; /* ** CAPI3REF: Custom Page Cache Object ** ** The sqlite3_pcache_page object represents a single page in the ** page cache. The page cache will allocate instances of this ** object. Various methods of the page cache use pointers to instances ** of this object as parameters or as their return value. ** ** See [sqlite3_pcache_methods2] for additional information. */ typedef struct sqlite3_pcache_page sqlite3_pcache_page; struct sqlite3_pcache_page { void *pBuf; /* The content of the page */ void *pExtra; /* Extra information associated with the page */ }; /* ** CAPI3REF: Application Defined Page Cache. ** KEYWORDS: {page cache} ** ** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can ** register an alternative page cache implementation by passing in an ** instance of the sqlite3_pcache_methods2 structure.)^ ** In many applications, most of the heap memory allocated by ** SQLite is used for the page cache. ** By implementing a ** custom page cache using this API, an application can better control ** the amount of memory consumed by SQLite, the way in which ** that memory is allocated and released, and the policies used to ** determine exactly which parts of a database file are cached and for ** how long. ** ** The alternative page cache mechanism is an ** extreme measure that is only needed by the most demanding applications. ** The built-in page cache is recommended for most uses. ** ** ^(The contents of the sqlite3_pcache_methods2 structure are copied to an ** internal buffer by SQLite within the call to [sqlite3_config]. Hence ** the application may discard the parameter after the call to ** [sqlite3_config()] returns.)^ ** ** [[the xInit() page cache method]] ** ^(The xInit() method is called once for each effective ** call to [sqlite3_initialize()])^ ** (usually only once during the lifetime of the process). ^(The xInit() ** method is passed a copy of the sqlite3_pcache_methods2.pArg value.)^ ** The intent of the xInit() method is to set up global data structures ** required by the custom page cache implementation. ** ^(If the xInit() method is NULL, then the ** built-in default page cache is used instead of the application defined ** page cache.)^ ** ** [[the xShutdown() page cache method]] ** ^The xShutdown() method is called by [sqlite3_shutdown()]. ** It can be used to clean up ** any outstanding resources before process shutdown, if required. ** ^The xShutdown() method may be NULL. ** ** ^SQLite automatically serializes calls to the xInit method, ** so the xInit method need not be threadsafe. ^The ** xShutdown method is only called from [sqlite3_shutdown()] so it does ** not need to be threadsafe either. All other methods must be threadsafe ** in multithreaded applications. ** ** ^SQLite will never invoke xInit() more than once without an intervening ** call to xShutdown(). ** ** [[the xCreate() page cache methods]] ** ^SQLite invokes the xCreate() method to construct a new cache instance. ** SQLite will typically create one cache instance for each open database file, ** though this is not guaranteed. ^The ** first parameter, szPage, is the size in bytes of the pages that must ** be allocated by the cache. ^szPage will always a power of two. ^The ** second parameter szExtra is a number of bytes of extra storage ** associated with each page cache entry. ^The szExtra parameter will ** a number less than 250. SQLite will use the ** extra szExtra bytes on each page to store metadata about the underlying ** database page on disk. The value passed into szExtra depends ** on the SQLite version, the target platform, and how SQLite was compiled. ** ^The third argument to xCreate(), bPurgeable, is true if the cache being ** created will be used to cache database pages of a file stored on disk, or ** false if it is used for an in-memory database. The cache implementation ** does not have to do anything special based with the value of bPurgeable; ** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will ** never invoke xUnpin() except to deliberately delete a page. ** ^In other words, calls to xUnpin() on a cache with bPurgeable set to ** false will always have the "discard" flag set to true. ** ^Hence, a cache created with bPurgeable false will ** never contain any unpinned pages. ** ** [[the xCachesize() page cache method]] ** ^(The xCachesize() method may be called at any time by SQLite to set the ** suggested maximum cache-size (number of pages stored by) the cache ** instance passed as the first argument. This is the value configured using ** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable ** parameter, the implementation is not required to do anything with this ** value; it is advisory only. ** ** [[the xPagecount() page cache methods]] ** The xPagecount() method must return the number of pages currently ** stored in the cache, both pinned and unpinned. ** ** [[the xFetch() page cache methods]] ** The xFetch() method locates a page in the cache and returns a pointer to ** an sqlite3_pcache_page object associated with that page, or a NULL pointer. ** The pBuf element of the returned sqlite3_pcache_page object will be a ** pointer to a buffer of szPage bytes used to store the content of a ** single database page. The pExtra element of sqlite3_pcache_page will be ** a pointer to the szExtra bytes of extra storage that SQLite has requested ** for each entry in the page cache. ** ** The page to be fetched is determined by the key. ^The minimum key value ** is 1. After it has been retrieved using xFetch, the page is considered ** to be "pinned". ** ** If the requested page is already in the page cache, then the page cache ** implementation must return a pointer to the page buffer with its content ** intact. If the requested page is not already in the cache, then the ** cache implementation should use the value of the createFlag ** parameter to help it determined what action to take: ** ** **
    createFlag Behaviour when page is not already in cache **
    0 Do not allocate a new page. Return NULL. **
    1 Allocate a new page if it easy and convenient to do so. ** Otherwise return NULL. **
    2 Make every effort to allocate a new page. Only return ** NULL if allocating a new page is effectively impossible. **
    ** ** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1. SQLite ** will only use a createFlag of 2 after a prior call with a createFlag of 1 ** failed.)^ In between the to xFetch() calls, SQLite may ** attempt to unpin one or more cache pages by spilling the content of ** pinned pages to disk and synching the operating system disk cache. ** ** [[the xUnpin() page cache method]] ** ^xUnpin() is called by SQLite with a pointer to a currently pinned page ** as its second argument. If the third parameter, discard, is non-zero, ** then the page must be evicted from the cache. ** ^If the discard parameter is ** zero, then the page may be discarded or retained at the discretion of ** page cache implementation. ^The page cache implementation ** may choose to evict unpinned pages at any time. ** ** The cache must not perform any reference counting. A single ** call to xUnpin() unpins the page regardless of the number of prior calls ** to xFetch(). ** ** [[the xRekey() page cache methods]] ** The xRekey() method is used to change the key value associated with the ** page passed as the second argument. If the cache ** previously contains an entry associated with newKey, it must be ** discarded. ^Any prior cache entry associated with newKey is guaranteed not ** to be pinned. ** ** When SQLite calls the xTruncate() method, the cache must discard all ** existing cache entries with page numbers (keys) greater than or equal ** to the value of the iLimit parameter passed to xTruncate(). If any ** of these pages are pinned, they are implicitly unpinned, meaning that ** they can be safely discarded. ** ** [[the xDestroy() page cache method]] ** ^The xDestroy() method is used to delete a cache allocated by xCreate(). ** All resources associated with the specified cache should be freed. ^After ** calling the xDestroy() method, SQLite considers the [sqlite3_pcache*] ** handle invalid, and will not use it with any other sqlite3_pcache_methods2 ** functions. ** ** [[the xShrink() page cache method]] ** ^SQLite invokes the xShrink() method when it wants the page cache to ** free up as much of heap memory as possible. The page cache implementation ** is not obligated to free any memory, but well-behaved implementations should ** do their best. */ typedef struct sqlite3_pcache_methods2 sqlite3_pcache_methods2; struct sqlite3_pcache_methods2 { int iVersion; void *pArg; int (*xInit)(void*); void (*xShutdown)(void*); sqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable); void (*xCachesize)(sqlite3_pcache*, int nCachesize); int (*xPagecount)(sqlite3_pcache*); sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard); void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, unsigned oldKey, unsigned newKey); void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); void (*xDestroy)(sqlite3_pcache*); void (*xShrink)(sqlite3_pcache*); }; /* ** This is the obsolete pcache_methods object that has now been replaced ** by sqlite3_pcache_methods2. This object is not used by SQLite. It is ** retained in the header file for backwards compatibility only. */ typedef struct sqlite3_pcache_methods sqlite3_pcache_methods; struct sqlite3_pcache_methods { void *pArg; int (*xInit)(void*); void (*xShutdown)(void*); sqlite3_pcache *(*xCreate)(int szPage, int bPurgeable); void (*xCachesize)(sqlite3_pcache*, int nCachesize); int (*xPagecount)(sqlite3_pcache*); void *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); void (*xUnpin)(sqlite3_pcache*, void*, int discard); void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey); void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); void (*xDestroy)(sqlite3_pcache*); }; /* ** CAPI3REF: Online Backup Object ** ** The sqlite3_backup object records state information about an ongoing ** online backup operation. ^The sqlite3_backup object is created by ** a call to [sqlite3_backup_init()] and is destroyed by a call to ** [sqlite3_backup_finish()]. ** ** See Also: [Using the SQLite Online Backup API] */ typedef struct sqlite3_backup sqlite3_backup; /* ** CAPI3REF: Online Backup API. ** ** The backup API copies the content of one database into another. ** It is useful either for creating backups of databases or ** for copying in-memory databases to or from persistent files. ** ** See Also: [Using the SQLite Online Backup API] ** ** ^SQLite holds a write transaction open on the destination database file ** for the duration of the backup operation. ** ^The source database is read-locked only while it is being read; ** it is not locked continuously for the entire backup operation. ** ^Thus, the backup may be performed on a live source database without ** preventing other database connections from ** reading or writing to the source database while the backup is underway. ** ** ^(To perform a backup operation: **
      **
    1. sqlite3_backup_init() is called once to initialize the ** backup, **
    2. sqlite3_backup_step() is called one or more times to transfer ** the data between the two databases, and finally **
    3. sqlite3_backup_finish() is called to release all resources ** associated with the backup operation. **
    )^ ** There should be exactly one call to sqlite3_backup_finish() for each ** successful call to sqlite3_backup_init(). ** ** [[sqlite3_backup_init()]] sqlite3_backup_init() ** ** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the ** [database connection] associated with the destination database ** and the database name, respectively. ** ^The database name is "main" for the main database, "temp" for the ** temporary database, or the name specified after the AS keyword in ** an [ATTACH] statement for an attached database. ** ^The S and M arguments passed to ** sqlite3_backup_init(D,N,S,M) identify the [database connection] ** and database name of the source database, respectively. ** ^The source and destination [database connections] (parameters S and D) ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. ** ** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is ** returned and an error code and error message are stored in the ** destination [database connection] D. ** ^The error code and message for the failed call to sqlite3_backup_init() ** can be retrieved using the [sqlite3_errcode()], [sqlite3_errmsg()], and/or ** [sqlite3_errmsg16()] functions. ** ^A successful call to sqlite3_backup_init() returns a pointer to an ** [sqlite3_backup] object. ** ^The [sqlite3_backup] object may be used with the sqlite3_backup_step() and ** sqlite3_backup_finish() functions to perform the specified backup ** operation. ** ** [[sqlite3_backup_step()]] sqlite3_backup_step() ** ** ^Function sqlite3_backup_step(B,N) will copy up to N pages between ** the source and destination databases specified by [sqlite3_backup] object B. ** ^If N is negative, all remaining source pages are copied. ** ^If sqlite3_backup_step(B,N) successfully copies N pages and there ** are still more pages to be copied, then the function returns [SQLITE_OK]. ** ^If sqlite3_backup_step(B,N) successfully finishes copying all pages ** from source to destination, then it returns [SQLITE_DONE]. ** ^If an error occurs while running sqlite3_backup_step(B,N), ** then an [error code] is returned. ^As well as [SQLITE_OK] and ** [SQLITE_DONE], a call to sqlite3_backup_step() may return [SQLITE_READONLY], ** [SQLITE_NOMEM], [SQLITE_BUSY], [SQLITE_LOCKED], or an ** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX] extended error code. ** ** ^(The sqlite3_backup_step() might return [SQLITE_READONLY] if **
      **
    1. the destination database was opened read-only, or **
    2. the destination database is using write-ahead-log journaling ** and the destination and source page sizes differ, or **
    3. the destination database is an in-memory database and the ** destination and source page sizes differ. **
    )^ ** ** ^If sqlite3_backup_step() cannot obtain a required file-system lock, then ** the [sqlite3_busy_handler | busy-handler function] ** is invoked (if one is specified). ^If the ** busy-handler returns non-zero before the lock is available, then ** [SQLITE_BUSY] is returned to the caller. ^In this case the call to ** sqlite3_backup_step() can be retried later. ^If the source ** [database connection] ** is being used to write to the source database when sqlite3_backup_step() ** is called, then [SQLITE_LOCKED] is returned immediately. ^Again, in this ** case the call to sqlite3_backup_step() can be retried later on. ^(If ** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or ** [SQLITE_READONLY] is returned, then ** there is no point in retrying the call to sqlite3_backup_step(). These ** errors are considered fatal.)^ The application must accept ** that the backup operation has failed and pass the backup operation handle ** to the sqlite3_backup_finish() to release associated resources. ** ** ^The first call to sqlite3_backup_step() obtains an exclusive lock ** on the destination file. ^The exclusive lock is not released until either ** sqlite3_backup_finish() is called or the backup operation is complete ** and sqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to ** sqlite3_backup_step() obtains a [shared lock] on the source database that ** lasts for the duration of the sqlite3_backup_step() call. ** ^Because the source database is not locked between calls to ** sqlite3_backup_step(), the source database may be modified mid-way ** through the backup process. ^If the source database is modified by an ** external process or via a database connection other than the one being ** used by the backup operation, then the backup will be automatically ** restarted by the next call to sqlite3_backup_step(). ^If the source ** database is modified by the using the same database connection as is used ** by the backup operation, then the backup database is automatically ** updated at the same time. ** ** [[sqlite3_backup_finish()]] sqlite3_backup_finish() ** ** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the ** application wishes to abandon the backup operation, the application ** should destroy the [sqlite3_backup] by passing it to sqlite3_backup_finish(). ** ^The sqlite3_backup_finish() interfaces releases all ** resources associated with the [sqlite3_backup] object. ** ^If sqlite3_backup_step() has not yet returned [SQLITE_DONE], then any ** active write-transaction on the destination database is rolled back. ** The [sqlite3_backup] object is invalid ** and may not be used following a call to sqlite3_backup_finish(). ** ** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no ** sqlite3_backup_step() errors occurred, regardless or whether or not ** sqlite3_backup_step() completed. ** ^If an out-of-memory condition or IO error occurred during any prior ** sqlite3_backup_step() call on the same [sqlite3_backup] object, then ** sqlite3_backup_finish() returns the corresponding [error code]. ** ** ^A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from sqlite3_backup_step() ** is not a permanent error and does not affect the return value of ** sqlite3_backup_finish(). ** ** [[sqlite3_backup__remaining()]] [[sqlite3_backup_pagecount()]] ** sqlite3_backup_remaining() and sqlite3_backup_pagecount() ** ** ^Each call to sqlite3_backup_step() sets two values inside ** the [sqlite3_backup] object: the number of pages still to be backed ** up and the total number of pages in the source database file. ** The sqlite3_backup_remaining() and sqlite3_backup_pagecount() interfaces ** retrieve these two values, respectively. ** ** ^The values returned by these functions are only updated by ** sqlite3_backup_step(). ^If the source database is modified during a backup ** operation, then the values are not updated to account for any extra ** pages that need to be updated or the size of the source database file ** changing. ** ** Concurrent Usage of Database Handles ** ** ^The source [database connection] may be used by the application for other ** purposes while a backup operation is underway or being initialized. ** ^If SQLite is compiled and configured to support threadsafe database ** connections, then the source database connection may be used concurrently ** from within other threads. ** ** However, the application must guarantee that the destination ** [database connection] is not passed to any other API (by any thread) after ** sqlite3_backup_init() is called and before the corresponding call to ** sqlite3_backup_finish(). SQLite does not currently check to see ** if the application incorrectly accesses the destination [database connection] ** and so no error code is reported, but the operations may malfunction ** nevertheless. Use of the destination database connection while a ** backup is in progress might also also cause a mutex deadlock. ** ** If running in [shared cache mode], the application must ** guarantee that the shared cache used by the destination database ** is not accessed while the backup is running. In practice this means ** that the application must guarantee that the disk file being ** backed up to is not accessed by any connection within the process, ** not just the specific connection that was passed to sqlite3_backup_init(). ** ** The [sqlite3_backup] object itself is partially threadsafe. Multiple ** threads may safely make multiple concurrent calls to sqlite3_backup_step(). ** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() ** APIs are not strictly speaking threadsafe. If they are invoked at the ** same time as another thread is invoking sqlite3_backup_step() it is ** possible that they return invalid values. */ SQLITE_API sqlite3_backup *sqlite3_backup_init( sqlite3 *pDest, /* Destination database handle */ const char *zDestName, /* Destination database name */ sqlite3 *pSource, /* Source database handle */ const char *zSourceName /* Source database name */ ); SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage); SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p); SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p); SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); /* ** CAPI3REF: Unlock Notification ** ** ^When running in shared-cache mode, a database operation may fail with ** an [SQLITE_LOCKED] error if the required locks on the shared-cache or ** individual tables within the shared-cache cannot be obtained. See ** [SQLite Shared-Cache Mode] for a description of shared-cache locking. ** ^This API may be used to register a callback that SQLite will invoke ** when the connection currently holding the required lock relinquishes it. ** ^This API is only available if the library was compiled with the ** [SQLITE_ENABLE_UNLOCK_NOTIFY] C-preprocessor symbol defined. ** ** See Also: [Using the SQLite Unlock Notification Feature]. ** ** ^Shared-cache locks are released when a database connection concludes ** its current transaction, either by committing it or rolling it back. ** ** ^When a connection (known as the blocked connection) fails to obtain a ** shared-cache lock and SQLITE_LOCKED is returned to the caller, the ** identity of the database connection (the blocking connection) that ** has locked the required resource is stored internally. ^After an ** application receives an SQLITE_LOCKED error, it may call the ** sqlite3_unlock_notify() method with the blocked connection handle as ** the first argument to register for a callback that will be invoked ** when the blocking connections current transaction is concluded. ^The ** callback is invoked from within the [sqlite3_step] or [sqlite3_close] ** call that concludes the blocking connections transaction. ** ** ^(If sqlite3_unlock_notify() is called in a multi-threaded application, ** there is a chance that the blocking connection will have already ** concluded its transaction by the time sqlite3_unlock_notify() is invoked. ** If this happens, then the specified callback is invoked immediately, ** from within the call to sqlite3_unlock_notify().)^ ** ** ^If the blocked connection is attempting to obtain a write-lock on a ** shared-cache table, and more than one other connection currently holds ** a read-lock on the same table, then SQLite arbitrarily selects one of ** the other connections to use as the blocking connection. ** ** ^(There may be at most one unlock-notify callback registered by a ** blocked connection. If sqlite3_unlock_notify() is called when the ** blocked connection already has a registered unlock-notify callback, ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is ** called with a NULL pointer as its second argument, then any existing ** unlock-notify callback is canceled. ^The blocked connections ** unlock-notify callback may also be canceled by closing the blocked ** connection using [sqlite3_close()]. ** ** The unlock-notify callback is not reentrant. If an application invokes ** any sqlite3_xxx API functions from within an unlock-notify callback, a ** crash or deadlock may be the result. ** ** ^Unless deadlock is detected (see below), sqlite3_unlock_notify() always ** returns SQLITE_OK. ** ** Callback Invocation Details ** ** When an unlock-notify callback is registered, the application provides a ** single void* pointer that is passed to the callback when it is invoked. ** However, the signature of the callback function allows SQLite to pass ** it an array of void* context pointers. The first argument passed to ** an unlock-notify callback is a pointer to an array of void* pointers, ** and the second is the number of entries in the array. ** ** When a blocking connections transaction is concluded, there may be ** more than one blocked connection that has registered for an unlock-notify ** callback. ^If two or more such blocked connections have specified the ** same callback function, then instead of invoking the callback function ** multiple times, it is invoked once with the set of void* context pointers ** specified by the blocked connections bundled together into an array. ** This gives the application an opportunity to prioritize any actions ** related to the set of unblocked database connections. ** ** Deadlock Detection ** ** Assuming that after registering for an unlock-notify callback a ** database waits for the callback to be issued before taking any further ** action (a reasonable assumption), then using this API may cause the ** application to deadlock. For example, if connection X is waiting for ** connection Y's transaction to be concluded, and similarly connection ** Y is waiting on connection X's transaction, then neither connection ** will proceed and the system may remain deadlocked indefinitely. ** ** To avoid this scenario, the sqlite3_unlock_notify() performs deadlock ** detection. ^If a given call to sqlite3_unlock_notify() would put the ** system in a deadlocked state, then SQLITE_LOCKED is returned and no ** unlock-notify callback is registered. The system is said to be in ** a deadlocked state if connection A has registered for an unlock-notify ** callback on the conclusion of connection B's transaction, and connection ** B has itself registered for an unlock-notify callback when connection ** A's transaction is concluded. ^Indirect deadlock is also detected, so ** the system is also considered to be deadlocked if connection B has ** registered for an unlock-notify callback on the conclusion of connection ** C's transaction, where connection C is waiting on connection A. ^Any ** number of levels of indirection are allowed. ** ** The "DROP TABLE" Exception ** ** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost ** always appropriate to call sqlite3_unlock_notify(). There is however, ** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement, ** SQLite checks if there are any currently executing SELECT statements ** that belong to the same connection. If there are, SQLITE_LOCKED is ** returned. In this case there is no "blocking connection", so invoking ** sqlite3_unlock_notify() results in the unlock-notify callback being ** invoked immediately. If the application then re-attempts the "DROP TABLE" ** or "DROP INDEX" query, an infinite loop might be the result. ** ** One way around this problem is to check the extended error code returned ** by an sqlite3_step() call. ^(If there is a blocking connection, then the ** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in ** the special "DROP TABLE/INDEX" case, the extended error code is just ** SQLITE_LOCKED.)^ */ SQLITE_API int sqlite3_unlock_notify( sqlite3 *pBlocked, /* Waiting connection */ void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */ void *pNotifyArg /* Argument to pass to xNotify */ ); /* ** CAPI3REF: String Comparison ** ** ^The [sqlite3_stricmp()] and [sqlite3_strnicmp()] APIs allow applications ** and extensions to compare the contents of two buffers containing UTF-8 ** strings in a case-independent fashion, using the same definition of "case ** independence" that SQLite uses internally when comparing identifiers. */ SQLITE_API int sqlite3_stricmp(const char *, const char *); SQLITE_API int sqlite3_strnicmp(const char *, const char *, int); /* ** CAPI3REF: Error Logging Interface ** ** ^The [sqlite3_log()] interface writes a message into the error log ** established by the [SQLITE_CONFIG_LOG] option to [sqlite3_config()]. ** ^If logging is enabled, the zFormat string and subsequent arguments are ** used with [sqlite3_snprintf()] to generate the final output string. ** ** The sqlite3_log() interface is intended for use by extensions such as ** virtual tables, collating functions, and SQL functions. While there is ** nothing to prevent an application from calling sqlite3_log(), doing so ** is considered bad form. ** ** The zFormat string must not be NULL. ** ** To avoid deadlocks and other threading problems, the sqlite3_log() routine ** will not use dynamically allocated memory. The log message is stored in ** a fixed-length buffer on the stack. If the log message is longer than ** a few hundred characters, it will be truncated to the length of the ** buffer. */ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); /* ** CAPI3REF: Write-Ahead Log Commit Hook ** ** ^The [sqlite3_wal_hook()] function is used to register a callback that ** will be invoked each time a database connection commits data to a ** [write-ahead log] (i.e. whenever a transaction is committed in ** [journal_mode | journal_mode=WAL mode]). ** ** ^The callback is invoked by SQLite after the commit has taken place and ** the associated write-lock on the database released, so the implementation ** may read, write or [checkpoint] the database as required. ** ** ^The first parameter passed to the callback function when it is invoked ** is a copy of the third parameter passed to sqlite3_wal_hook() when ** registering the callback. ^The second is a copy of the database handle. ** ^The third parameter is the name of the database that was written to - ** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter ** is the number of pages currently in the write-ahead log file, ** including those that were just committed. ** ** The callback function should normally return [SQLITE_OK]. ^If an error ** code is returned, that error will propagate back up through the ** SQLite code base to cause the statement that provoked the callback ** to report an error, though the commit will have still occurred. If the ** callback returns [SQLITE_ROW] or [SQLITE_DONE], or if it returns a value ** that does not correspond to any valid SQLite error code, the results ** are undefined. ** ** A single database handle may have at most a single write-ahead log callback ** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any ** previously registered write-ahead log callback. ^Note that the ** [sqlite3_wal_autocheckpoint()] interface and the ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will ** those overwrite any prior [sqlite3_wal_hook()] settings. */ SQLITE_API void *sqlite3_wal_hook( sqlite3*, int(*)(void *,sqlite3*,const char*,int), void* ); /* ** CAPI3REF: Configure an auto-checkpoint ** ** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around ** [sqlite3_wal_hook()] that causes any database on [database connection] D ** to automatically [checkpoint] ** after committing a transaction if there are N or ** more frames in the [write-ahead log] file. ^Passing zero or ** a negative value as the nFrame parameter disables automatic ** checkpoints entirely. ** ** ^The callback registered by this function replaces any existing callback ** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback ** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism ** configured by this function. ** ** ^The [wal_autocheckpoint pragma] can be used to invoke this interface ** from SQL. ** ** ^Every new [database connection] defaults to having the auto-checkpoint ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] ** pages. The use of this interface ** is only necessary if the default setting is found to be suboptimal ** for a particular application. */ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); /* ** CAPI3REF: Checkpoint a database ** ** ^The [sqlite3_wal_checkpoint(D,X)] interface causes database named X ** on [database connection] D to be [checkpointed]. ^If X is NULL or an ** empty string, then a checkpoint is run on all databases of ** connection D. ^If the database connection D is not in ** [WAL | write-ahead log mode] then this interface is a harmless no-op. ** ** ^The [wal_checkpoint pragma] can be used to invoke this interface ** from SQL. ^The [sqlite3_wal_autocheckpoint()] interface and the ** [wal_autocheckpoint pragma] can be used to cause this interface to be ** run whenever the WAL reaches a certain size threshold. ** ** See also: [sqlite3_wal_checkpoint_v2()] */ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); /* ** CAPI3REF: Checkpoint a database ** ** Run a checkpoint operation on WAL database zDb attached to database ** handle db. The specific operation is determined by the value of the ** eMode parameter: ** **
    **
    SQLITE_CHECKPOINT_PASSIVE
    ** Checkpoint as many frames as possible without waiting for any database ** readers or writers to finish. Sync the db file if all frames in the log ** are checkpointed. This mode is the same as calling ** sqlite3_wal_checkpoint(). The busy-handler callback is never invoked. ** **
    SQLITE_CHECKPOINT_FULL
    ** This mode blocks (calls the busy-handler callback) until there is no ** database writer and all readers are reading from the most recent database ** snapshot. It then checkpoints all frames in the log file and syncs the ** database file. This call blocks database writers while it is running, ** but not database readers. ** **
    SQLITE_CHECKPOINT_RESTART
    ** This mode works the same way as SQLITE_CHECKPOINT_FULL, except after ** checkpointing the log file it blocks (calls the busy-handler callback) ** until all readers are reading from the database file only. This ensures ** that the next client to write to the database file restarts the log file ** from the beginning. This call blocks database writers while it is running, ** but not database readers. **
    ** ** If pnLog is not NULL, then *pnLog is set to the total number of frames in ** the log file before returning. If pnCkpt is not NULL, then *pnCkpt is set to ** the total number of checkpointed frames (including any that were already ** checkpointed when this function is called). *pnLog and *pnCkpt may be ** populated even if sqlite3_wal_checkpoint_v2() returns other than SQLITE_OK. ** If no values are available because of an error, they are both set to -1 ** before returning to communicate this to the caller. ** ** All calls obtain an exclusive "checkpoint" lock on the database file. If ** any other process is running a checkpoint operation at the same time, the ** lock cannot be obtained and SQLITE_BUSY is returned. Even if there is a ** busy-handler configured, it will not be invoked in this case. ** ** The SQLITE_CHECKPOINT_FULL and RESTART modes also obtain the exclusive ** "writer" lock on the database file. If the writer lock cannot be obtained ** immediately, and a busy-handler is configured, it is invoked and the writer ** lock retried until either the busy-handler returns 0 or the lock is ** successfully obtained. The busy-handler is also invoked while waiting for ** database readers as described above. If the busy-handler returns 0 before ** the writer lock is obtained or while waiting for database readers, the ** checkpoint operation proceeds from that point in the same way as ** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible ** without blocking any further. SQLITE_BUSY is returned in this case. ** ** If parameter zDb is NULL or points to a zero length string, then the ** specified operation is attempted on all WAL databases. In this case the ** values written to output parameters *pnLog and *pnCkpt are undefined. If ** an SQLITE_BUSY error is encountered when processing one or more of the ** attached WAL databases, the operation is still attempted on any remaining ** attached databases and SQLITE_BUSY is returned to the caller. If any other ** error occurs while processing an attached database, processing is abandoned ** and the error code returned to the caller immediately. If no error ** (SQLITE_BUSY or otherwise) is encountered while processing the attached ** databases, SQLITE_OK is returned. ** ** If database zDb is the name of an attached database that is not in WAL ** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. If ** zDb is not NULL (or a zero length string) and is not the name of any ** attached database, SQLITE_ERROR is returned to the caller. */ SQLITE_API int sqlite3_wal_checkpoint_v2( sqlite3 *db, /* Database handle */ const char *zDb, /* Name of attached database (or NULL) */ int eMode, /* SQLITE_CHECKPOINT_* value */ int *pnLog, /* OUT: Size of WAL log in frames */ int *pnCkpt /* OUT: Total number of frames checkpointed */ ); /* ** CAPI3REF: Checkpoint operation parameters ** ** These constants can be used as the 3rd parameter to ** [sqlite3_wal_checkpoint_v2()]. See the [sqlite3_wal_checkpoint_v2()] ** documentation for additional information about the meaning and use of ** each of these values. */ #define SQLITE_CHECKPOINT_PASSIVE 0 #define SQLITE_CHECKPOINT_FULL 1 #define SQLITE_CHECKPOINT_RESTART 2 /* ** CAPI3REF: Virtual Table Interface Configuration ** ** This function may be called by either the [xConnect] or [xCreate] method ** of a [virtual table] implementation to configure ** various facets of the virtual table interface. ** ** If this interface is invoked outside the context of an xConnect or ** xCreate virtual table method then the behavior is undefined. ** ** At present, there is only one option that may be configured using ** this function. (See [SQLITE_VTAB_CONSTRAINT_SUPPORT].) Further options ** may be added in the future. */ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); /* ** CAPI3REF: Virtual Table Configuration Options ** ** These macros define the various options to the ** [sqlite3_vtab_config()] interface that [virtual table] implementations ** can use to customize and optimize their behavior. ** **
    **
    SQLITE_VTAB_CONSTRAINT_SUPPORT **
    Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported, ** where X is an integer. If X is zero, then the [virtual table] whose ** [xCreate] or [xConnect] method invoked [sqlite3_vtab_config()] does not ** support constraints. In this configuration (which is the default) if ** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire ** statement is rolled back as if [ON CONFLICT | OR ABORT] had been ** specified as part of the users SQL statement, regardless of the actual ** ON CONFLICT mode specified. ** ** If X is non-zero, then the virtual table implementation guarantees ** that if [xUpdate] returns [SQLITE_CONSTRAINT], it will do so before ** any modifications to internal or persistent data structures have been made. ** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite ** is able to roll back a statement or database transaction, and abandon ** or continue processing the current SQL statement as appropriate. ** If the ON CONFLICT mode is REPLACE and the [xUpdate] method returns ** [SQLITE_CONSTRAINT], SQLite handles this as if the ON CONFLICT mode ** had been ABORT. ** ** Virtual table implementations that are required to handle OR REPLACE ** must do so within the [xUpdate] method. If a call to the ** [sqlite3_vtab_on_conflict()] function indicates that the current ON ** CONFLICT policy is REPLACE, the virtual table implementation should ** silently replace the appropriate rows within the xUpdate callback and ** return SQLITE_OK. Or, if this is not possible, it may return ** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT ** constraint handling. **
    */ #define SQLITE_VTAB_CONSTRAINT_SUPPORT 1 /* ** CAPI3REF: Determine The Virtual Table Conflict Policy ** ** This function may only be called from within a call to the [xUpdate] method ** of a [virtual table] implementation for an INSERT or UPDATE operation. ^The ** value returned is one of [SQLITE_ROLLBACK], [SQLITE_IGNORE], [SQLITE_FAIL], ** [SQLITE_ABORT], or [SQLITE_REPLACE], according to the [ON CONFLICT] mode ** of the SQL statement that triggered the call to the [xUpdate] method of the ** [virtual table]. */ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); /* ** CAPI3REF: Conflict resolution modes ** ** These constants are returned by [sqlite3_vtab_on_conflict()] to ** inform a [virtual table] implementation what the [ON CONFLICT] mode ** is for the SQL statement being evaluated. ** ** Note that the [SQLITE_IGNORE] constant is also used as a potential ** return value from the [sqlite3_set_authorizer()] callback and that ** [SQLITE_ABORT] is also a [result code]. */ #define SQLITE_ROLLBACK 1 /* #define SQLITE_IGNORE 2 // Also used by sqlite3_authorizer() callback */ #define SQLITE_FAIL 3 /* #define SQLITE_ABORT 4 // Also an error code */ #define SQLITE_REPLACE 5 /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. */ #ifdef SQLITE_OMIT_FLOATING_POINT # undef double #endif #if 0 } /* End of the 'extern "C"' block */ #endif #endif /* ** 2010 August 30 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* */ #ifndef _SQLITE3RTREE_H_ #define _SQLITE3RTREE_H_ #if 0 extern "C" { #endif typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry; /* ** Register a geometry callback named zGeom that can be used as part of an ** R-Tree geometry query as follows: ** ** SELECT ... FROM WHERE MATCH $zGeom(... params ...) */ SQLITE_API int sqlite3_rtree_geometry_callback( sqlite3 *db, const char *zGeom, #ifdef SQLITE_RTREE_INT_ONLY int (*xGeom)(sqlite3_rtree_geometry*, int n, sqlite3_int64 *a, int *pRes), #else int (*xGeom)(sqlite3_rtree_geometry*, int n, double *a, int *pRes), #endif void *pContext ); /* ** A pointer to a structure of the following type is passed as the first ** argument to callbacks registered using rtree_geometry_callback(). */ struct sqlite3_rtree_geometry { void *pContext; /* Copy of pContext passed to s_r_g_c() */ int nParam; /* Size of array aParam[] */ double *aParam; /* Parameters passed to SQL geom function */ void *pUser; /* Callback implementation user data */ void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */ }; #if 0 } /* end of the 'extern "C"' block */ #endif #endif /* ifndef _SQLITE3RTREE_H_ */ /************** End of sqlite3.h *********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /************** Include hash.h in the middle of sqliteInt.h ******************/ /************** Begin file hash.h ********************************************/ /* ** 2001 September 22 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This is the header file for the generic hash-table implemenation ** used in SQLite. */ #ifndef _SQLITE_HASH_H_ #define _SQLITE_HASH_H_ /* Forward declarations of structures. */ typedef struct Hash Hash; typedef struct HashElem HashElem; /* A complete hash table is an instance of the following structure. ** The internals of this structure are intended to be opaque -- client ** code should not attempt to access or modify the fields of this structure ** directly. Change this structure only by using the routines below. ** However, some of the "procedures" and "functions" for modifying and ** accessing this structure are really macros, so we can't really make ** this structure opaque. ** ** All elements of the hash table are on a single doubly-linked list. ** Hash.first points to the head of this list. ** ** There are Hash.htsize buckets. Each bucket points to a spot in ** the global doubly-linked list. The contents of the bucket are the ** element pointed to plus the next _ht.count-1 elements in the list. ** ** Hash.htsize and Hash.ht may be zero. In that case lookup is done ** by a linear search of the global list. For small tables, the ** Hash.ht table is never allocated because if there are few elements ** in the table, it is faster to do a linear search than to manage ** the hash table. */ struct Hash { unsigned int htsize; /* Number of buckets in the hash table */ unsigned int count; /* Number of entries in this table */ HashElem *first; /* The first element of the array */ struct _ht { /* the hash table */ int count; /* Number of entries with this hash */ HashElem *chain; /* Pointer to first entry with this hash */ } *ht; }; /* Each element in the hash table is an instance of the following ** structure. All elements are stored on a single doubly-linked list. ** ** Again, this structure is intended to be opaque, but it can't really ** be opaque because it is used by macros. */ struct HashElem { HashElem *next, *prev; /* Next and previous elements in the table */ void *data; /* Data associated with this element */ const char *pKey; int nKey; /* Key associated with this element */ }; /* ** Access routines. To delete, insert a NULL pointer. */ SQLITE_PRIVATE void sqlite3HashInit(Hash*); SQLITE_PRIVATE void *sqlite3HashInsert(Hash*, const char *pKey, int nKey, void *pData); SQLITE_PRIVATE void *sqlite3HashFind(const Hash*, const char *pKey, int nKey); SQLITE_PRIVATE void sqlite3HashClear(Hash*); /* ** Macros for looping over all elements of a hash table. The idiom is ** like this: ** ** Hash h; ** HashElem *p; ** ... ** for(p=sqliteHashFirst(&h); p; p=sqliteHashNext(p)){ ** SomeStructure *pData = sqliteHashData(p); ** // do something with pData ** } */ #define sqliteHashFirst(H) ((H)->first) #define sqliteHashNext(E) ((E)->next) #define sqliteHashData(E) ((E)->data) /* #define sqliteHashKey(E) ((E)->pKey) // NOT USED */ /* #define sqliteHashKeysize(E) ((E)->nKey) // NOT USED */ /* ** Number of entries in a hash table */ /* #define sqliteHashCount(H) ((H)->count) // NOT USED */ #endif /* _SQLITE_HASH_H_ */ /************** End of hash.h ************************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /************** Include parse.h in the middle of sqliteInt.h *****************/ /************** Begin file parse.h *******************************************/ #define TK_SEMI 1 #define TK_EXPLAIN 2 #define TK_QUERY 3 #define TK_PLAN 4 #define TK_BEGIN 5 #define TK_TRANSACTION 6 #define TK_DEFERRED 7 #define TK_IMMEDIATE 8 #define TK_EXCLUSIVE 9 #define TK_COMMIT 10 #define TK_END 11 #define TK_ROLLBACK 12 #define TK_SAVEPOINT 13 #define TK_RELEASE 14 #define TK_TO 15 #define TK_TABLE 16 #define TK_CREATE 17 #define TK_IF 18 #define TK_NOT 19 #define TK_EXISTS 20 #define TK_TEMP 21 #define TK_LP 22 #define TK_RP 23 #define TK_AS 24 #define TK_COMMA 25 #define TK_ID 26 #define TK_INDEXED 27 #define TK_ABORT 28 #define TK_ACTION 29 #define TK_AFTER 30 #define TK_ANALYZE 31 #define TK_ASC 32 #define TK_ATTACH 33 #define TK_BEFORE 34 #define TK_BY 35 #define TK_CASCADE 36 #define TK_CAST 37 #define TK_COLUMNKW 38 #define TK_CONFLICT 39 #define TK_DATABASE 40 #define TK_DESC 41 #define TK_DETACH 42 #define TK_EACH 43 #define TK_FAIL 44 #define TK_FOR 45 #define TK_IGNORE 46 #define TK_INITIALLY 47 #define TK_INSTEAD 48 #define TK_LIKE_KW 49 #define TK_MATCH 50 #define TK_NO 51 #define TK_KEY 52 #define TK_OF 53 #define TK_OFFSET 54 #define TK_PRAGMA 55 #define TK_RAISE 56 #define TK_REPLACE 57 #define TK_RESTRICT 58 #define TK_ROW 59 #define TK_TRIGGER 60 #define TK_VACUUM 61 #define TK_VIEW 62 #define TK_VIRTUAL 63 #define TK_REINDEX 64 #define TK_RENAME 65 #define TK_CTIME_KW 66 #define TK_ANY 67 #define TK_OR 68 #define TK_AND 69 #define TK_IS 70 #define TK_BETWEEN 71 #define TK_IN 72 #define TK_ISNULL 73 #define TK_NOTNULL 74 #define TK_NE 75 #define TK_EQ 76 #define TK_GT 77 #define TK_LE 78 #define TK_LT 79 #define TK_GE 80 #define TK_ESCAPE 81 #define TK_BITAND 82 #define TK_BITOR 83 #define TK_LSHIFT 84 #define TK_RSHIFT 85 #define TK_PLUS 86 #define TK_MINUS 87 #define TK_STAR 88 #define TK_SLASH 89 #define TK_REM 90 #define TK_CONCAT 91 #define TK_COLLATE 92 #define TK_BITNOT 93 #define TK_STRING 94 #define TK_JOIN_KW 95 #define TK_CONSTRAINT 96 #define TK_DEFAULT 97 #define TK_NULL 98 #define TK_PRIMARY 99 #define TK_UNIQUE 100 #define TK_CHECK 101 #define TK_REFERENCES 102 #define TK_AUTOINCR 103 #define TK_ON 104 #define TK_INSERT 105 #define TK_DELETE 106 #define TK_UPDATE 107 #define TK_SET 108 #define TK_DEFERRABLE 109 #define TK_FOREIGN 110 #define TK_DROP 111 #define TK_UNION 112 #define TK_ALL 113 #define TK_EXCEPT 114 #define TK_INTERSECT 115 #define TK_SELECT 116 #define TK_DISTINCT 117 #define TK_DOT 118 #define TK_FROM 119 #define TK_JOIN 120 #define TK_USING 121 #define TK_ORDER 122 #define TK_GROUP 123 #define TK_HAVING 124 #define TK_LIMIT 125 #define TK_WHERE 126 #define TK_INTO 127 #define TK_VALUES 128 #define TK_INTEGER 129 #define TK_FLOAT 130 #define TK_BLOB 131 #define TK_REGISTER 132 #define TK_VARIABLE 133 #define TK_CASE 134 #define TK_WHEN 135 #define TK_THEN 136 #define TK_ELSE 137 #define TK_INDEX 138 #define TK_ALTER 139 #define TK_ADD 140 #define TK_TO_TEXT 141 #define TK_TO_BLOB 142 #define TK_TO_NUMERIC 143 #define TK_TO_INT 144 #define TK_TO_REAL 145 #define TK_ISNOT 146 #define TK_END_OF_FILE 147 #define TK_ILLEGAL 148 #define TK_SPACE 149 #define TK_UNCLOSED_STRING 150 #define TK_FUNCTION 151 #define TK_COLUMN 152 #define TK_AGG_FUNCTION 153 #define TK_AGG_COLUMN 154 #define TK_CONST_FUNC 155 #define TK_UMINUS 156 #define TK_UPLUS 157 /************** End of parse.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ #include #include #include #include #include /* ** If compiling for a processor that lacks floating point support, ** substitute integer for floating-point */ #ifdef SQLITE_OMIT_FLOATING_POINT # define double sqlite_int64 # define float sqlite_int64 # define LONGDOUBLE_TYPE sqlite_int64 # ifndef SQLITE_BIG_DBL # define SQLITE_BIG_DBL (((sqlite3_int64)1)<<50) # endif # define SQLITE_OMIT_DATETIME_FUNCS 1 # define SQLITE_OMIT_TRACE 1 # undef SQLITE_MIXED_ENDIAN_64BIT_FLOAT # undef SQLITE_HAVE_ISNAN #endif #ifndef SQLITE_BIG_DBL # define SQLITE_BIG_DBL (1e99) #endif /* ** OMIT_TEMPDB is set to 1 if SQLITE_OMIT_TEMPDB is defined, or 0 ** afterward. Having this macro allows us to cause the C compiler ** to omit code used by TEMP tables without messy #ifndef statements. */ #ifdef SQLITE_OMIT_TEMPDB #define OMIT_TEMPDB 1 #else #define OMIT_TEMPDB 0 #endif /* ** The "file format" number is an integer that is incremented whenever ** the VDBE-level file format changes. The following macros define the ** the default file format for new databases and the maximum file format ** that the library can read. */ #define SQLITE_MAX_FILE_FORMAT 4 #ifndef SQLITE_DEFAULT_FILE_FORMAT # define SQLITE_DEFAULT_FILE_FORMAT 4 #endif /* ** Determine whether triggers are recursive by default. This can be ** changed at run-time using a pragma. */ #ifndef SQLITE_DEFAULT_RECURSIVE_TRIGGERS # define SQLITE_DEFAULT_RECURSIVE_TRIGGERS 0 #endif /* ** Provide a default value for SQLITE_TEMP_STORE in case it is not specified ** on the command-line */ #ifndef SQLITE_TEMP_STORE # define SQLITE_TEMP_STORE 1 #endif /* ** GCC does not define the offsetof() macro so we'll have to do it ** ourselves. */ #ifndef offsetof #define offsetof(STRUCTURE,FIELD) ((int)((char*)&((STRUCTURE*)0)->FIELD)) #endif /* ** Check to see if this machine uses EBCDIC. (Yes, believe it or ** not, there are still machines out there that use EBCDIC.) */ #if 'A' == '\301' # define SQLITE_EBCDIC 1 #else # define SQLITE_ASCII 1 #endif /* ** Integers of known sizes. These typedefs might change for architectures ** where the sizes very. Preprocessor macros are available so that the ** types can be conveniently redefined at compile-type. Like this: ** ** cc '-DUINTPTR_TYPE=long long int' ... */ #ifndef UINT32_TYPE # ifdef HAVE_UINT32_T # define UINT32_TYPE uint32_t # else # define UINT32_TYPE unsigned int # endif #endif #ifndef UINT16_TYPE # ifdef HAVE_UINT16_T # define UINT16_TYPE uint16_t # else # define UINT16_TYPE unsigned short int # endif #endif #ifndef INT16_TYPE # ifdef HAVE_INT16_T # define INT16_TYPE int16_t # else # define INT16_TYPE short int # endif #endif #ifndef UINT8_TYPE # ifdef HAVE_UINT8_T # define UINT8_TYPE uint8_t # else # define UINT8_TYPE unsigned char # endif #endif #ifndef INT8_TYPE # ifdef HAVE_INT8_T # define INT8_TYPE int8_t # else # define INT8_TYPE signed char # endif #endif #ifndef LONGDOUBLE_TYPE # define LONGDOUBLE_TYPE long double #endif typedef sqlite_int64 i64; /* 8-byte signed integer */ typedef sqlite_uint64 u64; /* 8-byte unsigned integer */ typedef UINT32_TYPE u32; /* 4-byte unsigned integer */ typedef UINT16_TYPE u16; /* 2-byte unsigned integer */ typedef INT16_TYPE i16; /* 2-byte signed integer */ typedef UINT8_TYPE u8; /* 1-byte unsigned integer */ typedef INT8_TYPE i8; /* 1-byte signed integer */ /* ** SQLITE_MAX_U32 is a u64 constant that is the maximum u64 value ** that can be stored in a u32 without loss of data. The value ** is 0x00000000ffffffff. But because of quirks of some compilers, we ** have to specify the value in the less intuitive manner shown: */ #define SQLITE_MAX_U32 ((((u64)1)<<32)-1) /* ** The datatype used to store estimates of the number of rows in a ** table or index. This is an unsigned integer type. For 99.9% of ** the world, a 32-bit integer is sufficient. But a 64-bit integer ** can be used at compile-time if desired. */ #ifdef SQLITE_64BIT_STATS typedef u64 tRowcnt; /* 64-bit only if requested at compile-time */ #else typedef u32 tRowcnt; /* 32-bit is the default */ #endif /* ** Macros to determine whether the machine is big or little endian, ** evaluated at runtime. */ #ifdef SQLITE_AMALGAMATION SQLITE_PRIVATE const int sqlite3one = 1; #else SQLITE_PRIVATE const int sqlite3one; #endif #if defined(i386) || defined(__i386__) || defined(_M_IX86)\ || defined(__x86_64) || defined(__x86_64__) # define SQLITE_BIGENDIAN 0 # define SQLITE_LITTLEENDIAN 1 # define SQLITE_UTF16NATIVE SQLITE_UTF16LE #else # define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0) # define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1) # define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE) #endif /* ** Constants for the largest and smallest possible 64-bit signed integers. ** These macros are designed to work correctly on both 32-bit and 64-bit ** compilers. */ #define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) #define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) /* ** Round up a number to the next larger multiple of 8. This is used ** to force 8-byte alignment on 64-bit architectures. */ #define ROUND8(x) (((x)+7)&~7) /* ** Round down to the nearest multiple of 8 */ #define ROUNDDOWN8(x) ((x)&~7) /* ** Assert that the pointer X is aligned to an 8-byte boundary. This ** macro is used only within assert() to verify that the code gets ** all alignment restrictions correct. ** ** Except, if SQLITE_4_BYTE_ALIGNED_MALLOC is defined, then the ** underlying malloc() implemention might return us 4-byte aligned ** pointers. In that case, only verify 4-byte alignment. */ #ifdef SQLITE_4_BYTE_ALIGNED_MALLOC # define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&3)==0) #else # define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&7)==0) #endif /* ** An instance of the following structure is used to store the busy-handler ** callback for a given sqlite handle. ** ** The sqlite.busyHandler member of the sqlite struct contains the busy ** callback for the database handle. Each pager opened via the sqlite ** handle is passed a pointer to sqlite.busyHandler. The busy-handler ** callback is currently invoked only from within pager.c. */ typedef struct BusyHandler BusyHandler; struct BusyHandler { int (*xFunc)(void *,int); /* The busy callback */ void *pArg; /* First arg to busy callback */ int nBusy; /* Incremented with each busy call */ }; /* ** Name of the master database table. The master database table ** is a special table that holds the names and attributes of all ** user tables and indices. */ #define MASTER_NAME "sqlite_master" #define TEMP_MASTER_NAME "sqlite_temp_master" /* ** The root-page of the master database table. */ #define MASTER_ROOT 1 /* ** The name of the schema table. */ #define SCHEMA_TABLE(x) ((!OMIT_TEMPDB)&&(x==1)?TEMP_MASTER_NAME:MASTER_NAME) /* ** A convenience macro that returns the number of elements in ** an array. */ #define ArraySize(X) ((int)(sizeof(X)/sizeof(X[0]))) /* ** The following value as a destructor means to use sqlite3DbFree(). ** The sqlite3DbFree() routine requires two parameters instead of the ** one parameter that destructors normally want. So we have to introduce ** this magic value that the code knows to handle differently. Any ** pointer will work here as long as it is distinct from SQLITE_STATIC ** and SQLITE_TRANSIENT. */ #define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3MallocSize) /* ** When SQLITE_OMIT_WSD is defined, it means that the target platform does ** not support Writable Static Data (WSD) such as global and static variables. ** All variables must either be on the stack or dynamically allocated from ** the heap. When WSD is unsupported, the variable declarations scattered ** throughout the SQLite code must become constants instead. The SQLITE_WSD ** macro is used for this purpose. And instead of referencing the variable ** directly, we use its constant as a key to lookup the run-time allocated ** buffer that holds real variable. The constant is also the initializer ** for the run-time allocated buffer. ** ** In the usual case where WSD is supported, the SQLITE_WSD and GLOBAL ** macros become no-ops and have zero performance impact. */ #ifdef SQLITE_OMIT_WSD #define SQLITE_WSD const #define GLOBAL(t,v) (*(t*)sqlite3_wsd_find((void*)&(v), sizeof(v))) #define sqlite3GlobalConfig GLOBAL(struct Sqlite3Config, sqlite3Config) SQLITE_API int sqlite3_wsd_init(int N, int J); SQLITE_API void *sqlite3_wsd_find(void *K, int L); #else #define SQLITE_WSD #define GLOBAL(t,v) v #define sqlite3GlobalConfig sqlite3Config #endif /* ** The following macros are used to suppress compiler warnings and to ** make it clear to human readers when a function parameter is deliberately ** left unused within the body of a function. This usually happens when ** a function is called via a function pointer. For example the ** implementation of an SQL aggregate step callback may not use the ** parameter indicating the number of arguments passed to the aggregate, ** if it knows that this is enforced elsewhere. ** ** When a function parameter is not used at all within the body of a function, ** it is generally named "NotUsed" or "NotUsed2" to make things even clearer. ** However, these macros may also be used to suppress warnings related to ** parameters that may or may not be used depending on compilation options. ** For example those parameters only used in assert() statements. In these ** cases the parameters are named as per the usual conventions. */ #define UNUSED_PARAMETER(x) (void)(x) #define UNUSED_PARAMETER2(x,y) UNUSED_PARAMETER(x),UNUSED_PARAMETER(y) /* ** Forward references to structures */ typedef struct AggInfo AggInfo; typedef struct AuthContext AuthContext; typedef struct AutoincInfo AutoincInfo; typedef struct Bitvec Bitvec; typedef struct CollSeq CollSeq; typedef struct Column Column; typedef struct Db Db; typedef struct Schema Schema; typedef struct Expr Expr; typedef struct ExprList ExprList; typedef struct ExprSpan ExprSpan; typedef struct FKey FKey; typedef struct FuncDestructor FuncDestructor; typedef struct FuncDef FuncDef; typedef struct FuncDefHash FuncDefHash; typedef struct IdList IdList; typedef struct Index Index; typedef struct IndexSample IndexSample; typedef struct KeyClass KeyClass; typedef struct KeyInfo KeyInfo; typedef struct Lookaside Lookaside; typedef struct LookasideSlot LookasideSlot; typedef struct Module Module; typedef struct NameContext NameContext; typedef struct Parse Parse; typedef struct RowSet RowSet; typedef struct Savepoint Savepoint; typedef struct Select Select; typedef struct SrcList SrcList; typedef struct StrAccum StrAccum; typedef struct Table Table; typedef struct TableLock TableLock; typedef struct Token Token; typedef struct Trigger Trigger; typedef struct TriggerPrg TriggerPrg; typedef struct TriggerStep TriggerStep; typedef struct UnpackedRecord UnpackedRecord; typedef struct VTable VTable; typedef struct VtabCtx VtabCtx; typedef struct Walker Walker; typedef struct WherePlan WherePlan; typedef struct WhereInfo WhereInfo; typedef struct WhereLevel WhereLevel; /* ** Defer sourcing vdbe.h and btree.h until after the "u8" and ** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque ** pointer types (i.e. FuncDef) defined above. */ /************** Include btree.h in the middle of sqliteInt.h *****************/ /************** Begin file btree.h *******************************************/ /* ** 2001 September 15 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This header file defines the interface that the sqlite B-Tree file ** subsystem. See comments in the source code for a detailed description ** of what each interface routine does. */ #ifndef _BTREE_H_ #define _BTREE_H_ /* TODO: This definition is just included so other modules compile. It ** needs to be revisited. */ #define SQLITE_N_BTREE_META 10 /* ** If defined as non-zero, auto-vacuum is enabled by default. Otherwise ** it must be turned on for each database using "PRAGMA auto_vacuum = 1". */ #ifndef SQLITE_DEFAULT_AUTOVACUUM #define SQLITE_DEFAULT_AUTOVACUUM 0 #endif #define BTREE_AUTOVACUUM_NONE 0 /* Do not do auto-vacuum */ #define BTREE_AUTOVACUUM_FULL 1 /* Do full auto-vacuum */ #define BTREE_AUTOVACUUM_INCR 2 /* Incremental vacuum */ /* ** Forward declarations of structure */ typedef struct Btree Btree; typedef struct BtCursor BtCursor; typedef struct BtShared BtShared; SQLITE_PRIVATE int sqlite3BtreeOpen( sqlite3_vfs *pVfs, /* VFS to use with this b-tree */ const char *zFilename, /* Name of database file to open */ sqlite3 *db, /* Associated database connection */ Btree **ppBtree, /* Return open Btree* here */ int flags, /* Flags */ int vfsFlags /* Flags passed through to VFS open */ ); /* The flags parameter to sqlite3BtreeOpen can be the bitwise or of the ** following values. ** ** NOTE: These values must match the corresponding PAGER_ values in ** pager.h. */ #define BTREE_OMIT_JOURNAL 1 /* Do not create or use a rollback journal */ #define BTREE_MEMORY 2 /* This is an in-memory DB */ #define BTREE_SINGLE 4 /* The file contains at most 1 b-tree */ #define BTREE_UNORDERED 8 /* Use of a hash implementation is OK */ SQLITE_PRIVATE int sqlite3BtreeClose(Btree*); SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree*,int); SQLITE_PRIVATE int sqlite3BtreeSetSafetyLevel(Btree*,int,int,int); SQLITE_PRIVATE int sqlite3BtreeSyncDisabled(Btree*); SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix); SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*); SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int); SQLITE_PRIVATE u32 sqlite3BtreeLastPage(Btree*); SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree*,int); SQLITE_PRIVATE int sqlite3BtreeGetReserve(Btree*); SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int); SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *); SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int); SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*, int); SQLITE_PRIVATE int sqlite3BtreeCommit(Btree*); SQLITE_PRIVATE int sqlite3BtreeRollback(Btree*,int); SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree*,int); SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree*, int*, int flags); SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree*); SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree*); SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree*); SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *, int, void(*)(void *)); SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *pBtree); SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *pBtree, int iTab, u8 isWriteLock); SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *, int, int); SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *); SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *); SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *, Btree *); SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *); /* The flags parameter to sqlite3BtreeCreateTable can be the bitwise OR ** of the flags shown below. ** ** Every SQLite table must have either BTREE_INTKEY or BTREE_BLOBKEY set. ** With BTREE_INTKEY, the table key is a 64-bit integer and arbitrary data ** is stored in the leaves. (BTREE_INTKEY is used for SQL tables.) With ** BTREE_BLOBKEY, the key is an arbitrary BLOB and no content is stored ** anywhere - the key is the content. (BTREE_BLOBKEY is used for SQL ** indices.) */ #define BTREE_INTKEY 1 /* Table has only 64-bit signed integer keys */ #define BTREE_BLOBKEY 2 /* Table has keys only - no data */ SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree*, int, int*); SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int, int*); SQLITE_PRIVATE void sqlite3BtreeTripAllCursors(Btree*, int); SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *pBtree, int idx, u32 *pValue); SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value); /* ** The second parameter to sqlite3BtreeGetMeta or sqlite3BtreeUpdateMeta ** should be one of the following values. The integer values are assigned ** to constants so that the offset of the corresponding field in an ** SQLite database header may be found using the following formula: ** ** offset = 36 + (idx * 4) ** ** For example, the free-page-count field is located at byte offset 36 of ** the database file header. The incr-vacuum-flag field is located at ** byte offset 64 (== 36+4*7). */ #define BTREE_FREE_PAGE_COUNT 0 #define BTREE_SCHEMA_VERSION 1 #define BTREE_FILE_FORMAT 2 #define BTREE_DEFAULT_CACHE_SIZE 3 #define BTREE_LARGEST_ROOT_PAGE 4 #define BTREE_TEXT_ENCODING 5 #define BTREE_USER_VERSION 6 #define BTREE_INCR_VACUUM 7 /* ** Values that may be OR'd together to form the second argument of an ** sqlite3BtreeCursorHints() call. */ #define BTREE_BULKLOAD 0x00000001 SQLITE_PRIVATE int sqlite3BtreeCursor( Btree*, /* BTree containing table to open */ int iTable, /* Index of root page */ int wrFlag, /* 1 for writing. 0 for read-only */ struct KeyInfo*, /* First argument to compare function */ BtCursor *pCursor /* Space to write cursor structure */ ); SQLITE_PRIVATE int sqlite3BtreeCursorSize(void); SQLITE_PRIVATE void sqlite3BtreeCursorZero(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( BtCursor*, UnpackedRecord *pUnKey, i64 intKey, int bias, int *pRes ); SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*, int*); SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const void *pKey, i64 nKey, const void *pData, int nData, int nZero, int bias, int seekResult); SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*); SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeKeySize(BtCursor*, i64 *pSize); SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE const void *sqlite3BtreeKeyFetch(BtCursor*, int *pAmt); SQLITE_PRIVATE const void *sqlite3BtreeDataFetch(BtCursor*, int *pAmt); SQLITE_PRIVATE int sqlite3BtreeDataSize(BtCursor*, u32 *pSize); SQLITE_PRIVATE int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE void sqlite3BtreeSetCachedRowid(BtCursor*, sqlite3_int64); SQLITE_PRIVATE sqlite3_int64 sqlite3BtreeGetCachedRowid(BtCursor*); SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*); SQLITE_PRIVATE struct Pager *sqlite3BtreePager(Btree*); SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE void sqlite3BtreeCacheOverflow(BtCursor *); SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *); SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBt, int iVersion); SQLITE_PRIVATE void sqlite3BtreeCursorHints(BtCursor *, unsigned int mask); #ifndef NDEBUG SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor*); #endif #ifndef SQLITE_OMIT_BTREECOUNT SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *, i64 *); #endif #ifdef SQLITE_TEST SQLITE_PRIVATE int sqlite3BtreeCursorInfo(BtCursor*, int*, int); SQLITE_PRIVATE void sqlite3BtreeCursorList(Btree*); #endif #ifndef SQLITE_OMIT_WAL SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *); #endif /* ** If we are not using shared cache, then there is no need to ** use mutexes to access the BtShared structures. So make the ** Enter and Leave procedures no-ops. */ #ifndef SQLITE_OMIT_SHARED_CACHE SQLITE_PRIVATE void sqlite3BtreeEnter(Btree*); SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3*); #else # define sqlite3BtreeEnter(X) # define sqlite3BtreeEnterAll(X) #endif #if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE SQLITE_PRIVATE int sqlite3BtreeSharable(Btree*); SQLITE_PRIVATE void sqlite3BtreeLeave(Btree*); SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor*); SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor*); SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3*); #ifndef NDEBUG /* These routines are used inside assert() statements only. */ SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree*); SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3*); SQLITE_PRIVATE int sqlite3SchemaMutexHeld(sqlite3*,int,Schema*); #endif #else # define sqlite3BtreeSharable(X) 0 # define sqlite3BtreeLeave(X) # define sqlite3BtreeEnterCursor(X) # define sqlite3BtreeLeaveCursor(X) # define sqlite3BtreeLeaveAll(X) # define sqlite3BtreeHoldsMutex(X) 1 # define sqlite3BtreeHoldsAllMutexes(X) 1 # define sqlite3SchemaMutexHeld(X,Y,Z) 1 #endif #endif /* _BTREE_H_ */ /************** End of btree.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /************** Include vdbe.h in the middle of sqliteInt.h ******************/ /************** Begin file vdbe.h ********************************************/ /* ** 2001 September 15 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** Header file for the Virtual DataBase Engine (VDBE) ** ** This header defines the interface to the virtual database engine ** or VDBE. The VDBE implements an abstract machine that runs a ** simple program to access and modify the underlying database. */ #ifndef _SQLITE_VDBE_H_ #define _SQLITE_VDBE_H_ /* #include */ /* ** A single VDBE is an opaque structure named "Vdbe". Only routines ** in the source file sqliteVdbe.c are allowed to see the insides ** of this structure. */ typedef struct Vdbe Vdbe; /* ** The names of the following types declared in vdbeInt.h are required ** for the VdbeOp definition. */ typedef struct VdbeFunc VdbeFunc; typedef struct Mem Mem; typedef struct SubProgram SubProgram; /* ** A single instruction of the virtual machine has an opcode ** and as many as three operands. The instruction is recorded ** as an instance of the following structure: */ struct VdbeOp { u8 opcode; /* What operation to perform */ signed char p4type; /* One of the P4_xxx constants for p4 */ u8 opflags; /* Mask of the OPFLG_* flags in opcodes.h */ u8 p5; /* Fifth parameter is an unsigned character */ int p1; /* First operand */ int p2; /* Second parameter (often the jump destination) */ int p3; /* The third parameter */ union { /* fourth parameter */ int i; /* Integer value if p4type==P4_INT32 */ void *p; /* Generic pointer */ char *z; /* Pointer to data for string (char array) types */ i64 *pI64; /* Used when p4type is P4_INT64 */ double *pReal; /* Used when p4type is P4_REAL */ FuncDef *pFunc; /* Used when p4type is P4_FUNCDEF */ VdbeFunc *pVdbeFunc; /* Used when p4type is P4_VDBEFUNC */ CollSeq *pColl; /* Used when p4type is P4_COLLSEQ */ Mem *pMem; /* Used when p4type is P4_MEM */ VTable *pVtab; /* Used when p4type is P4_VTAB */ KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */ int *ai; /* Used when p4type is P4_INTARRAY */ SubProgram *pProgram; /* Used when p4type is P4_SUBPROGRAM */ int (*xAdvance)(BtCursor *, int *); } p4; #ifdef SQLITE_DEBUG char *zComment; /* Comment to improve readability */ #endif #ifdef VDBE_PROFILE int cnt; /* Number of times this instruction was executed */ u64 cycles; /* Total time spent executing this instruction */ #endif }; typedef struct VdbeOp VdbeOp; /* ** A sub-routine used to implement a trigger program. */ struct SubProgram { VdbeOp *aOp; /* Array of opcodes for sub-program */ int nOp; /* Elements in aOp[] */ int nMem; /* Number of memory cells required */ int nCsr; /* Number of cursors required */ int nOnce; /* Number of OP_Once instructions */ void *token; /* id that may be used to recursive triggers */ SubProgram *pNext; /* Next sub-program already visited */ }; /* ** A smaller version of VdbeOp used for the VdbeAddOpList() function because ** it takes up less space. */ struct VdbeOpList { u8 opcode; /* What operation to perform */ signed char p1; /* First operand */ signed char p2; /* Second parameter (often the jump destination) */ signed char p3; /* Third parameter */ }; typedef struct VdbeOpList VdbeOpList; /* ** Allowed values of VdbeOp.p4type */ #define P4_NOTUSED 0 /* The P4 parameter is not used */ #define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */ #define P4_STATIC (-2) /* Pointer to a static string */ #define P4_COLLSEQ (-4) /* P4 is a pointer to a CollSeq structure */ #define P4_FUNCDEF (-5) /* P4 is a pointer to a FuncDef structure */ #define P4_KEYINFO (-6) /* P4 is a pointer to a KeyInfo structure */ #define P4_VDBEFUNC (-7) /* P4 is a pointer to a VdbeFunc structure */ #define P4_MEM (-8) /* P4 is a pointer to a Mem* structure */ #define P4_TRANSIENT 0 /* P4 is a pointer to a transient string */ #define P4_VTAB (-10) /* P4 is a pointer to an sqlite3_vtab structure */ #define P4_MPRINTF (-11) /* P4 is a string obtained from sqlite3_mprintf() */ #define P4_REAL (-12) /* P4 is a 64-bit floating point value */ #define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ #define P4_INT32 (-14) /* P4 is a 32-bit signed integer */ #define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ #define P4_SUBPROGRAM (-18) /* P4 is a pointer to a SubProgram structure */ #define P4_ADVANCE (-19) /* P4 is a pointer to BtreeNext() or BtreePrev() */ /* When adding a P4 argument using P4_KEYINFO, a copy of the KeyInfo structure ** is made. That copy is freed when the Vdbe is finalized. But if the ** argument is P4_KEYINFO_HANDOFF, the passed in pointer is used. It still ** gets freed when the Vdbe is finalized so it still should be obtained ** from a single sqliteMalloc(). But no copy is made and the calling ** function should *not* try to free the KeyInfo. */ #define P4_KEYINFO_HANDOFF (-16) #define P4_KEYINFO_STATIC (-17) /* ** The Vdbe.aColName array contains 5n Mem structures, where n is the ** number of columns of data returned by the statement. */ #define COLNAME_NAME 0 #define COLNAME_DECLTYPE 1 #define COLNAME_DATABASE 2 #define COLNAME_TABLE 3 #define COLNAME_COLUMN 4 #ifdef SQLITE_ENABLE_COLUMN_METADATA # define COLNAME_N 5 /* Number of COLNAME_xxx symbols */ #else # ifdef SQLITE_OMIT_DECLTYPE # define COLNAME_N 1 /* Store only the name */ # else # define COLNAME_N 2 /* Store the name and decltype */ # endif #endif /* ** The following macro converts a relative address in the p2 field ** of a VdbeOp structure into a negative number so that ** sqlite3VdbeAddOpList() knows that the address is relative. Calling ** the macro again restores the address. */ #define ADDR(X) (-1-(X)) /* ** The makefile scans the vdbe.c source file and creates the "opcodes.h" ** header file that defines a number for each opcode used by the VDBE. */ /************** Include opcodes.h in the middle of vdbe.h ********************/ /************** Begin file opcodes.h *****************************************/ /* Automatically generated. Do not edit */ /* See the mkopcodeh.awk script for details */ #define OP_Goto 1 #define OP_Gosub 2 #define OP_Return 3 #define OP_Yield 4 #define OP_HaltIfNull 5 #define OP_Halt 6 #define OP_Integer 7 #define OP_Int64 8 #define OP_Real 130 /* same as TK_FLOAT */ #define OP_String8 94 /* same as TK_STRING */ #define OP_String 9 #define OP_Null 10 #define OP_Blob 11 #define OP_Variable 12 #define OP_Move 13 #define OP_Copy 14 #define OP_SCopy 15 #define OP_ResultRow 16 #define OP_Concat 91 /* same as TK_CONCAT */ #define OP_Add 86 /* same as TK_PLUS */ #define OP_Subtract 87 /* same as TK_MINUS */ #define OP_Multiply 88 /* same as TK_STAR */ #define OP_Divide 89 /* same as TK_SLASH */ #define OP_Remainder 90 /* same as TK_REM */ #define OP_CollSeq 17 #define OP_Function 18 #define OP_BitAnd 82 /* same as TK_BITAND */ #define OP_BitOr 83 /* same as TK_BITOR */ #define OP_ShiftLeft 84 /* same as TK_LSHIFT */ #define OP_ShiftRight 85 /* same as TK_RSHIFT */ #define OP_AddImm 20 #define OP_MustBeInt 21 #define OP_RealAffinity 22 #define OP_ToText 141 /* same as TK_TO_TEXT */ #define OP_ToBlob 142 /* same as TK_TO_BLOB */ #define OP_ToNumeric 143 /* same as TK_TO_NUMERIC*/ #define OP_ToInt 144 /* same as TK_TO_INT */ #define OP_ToReal 145 /* same as TK_TO_REAL */ #define OP_Eq 76 /* same as TK_EQ */ #define OP_Ne 75 /* same as TK_NE */ #define OP_Lt 79 /* same as TK_LT */ #define OP_Le 78 /* same as TK_LE */ #define OP_Gt 77 /* same as TK_GT */ #define OP_Ge 80 /* same as TK_GE */ #define OP_Permutation 23 #define OP_Compare 24 #define OP_Jump 25 #define OP_And 69 /* same as TK_AND */ #define OP_Or 68 /* same as TK_OR */ #define OP_Not 19 /* same as TK_NOT */ #define OP_BitNot 93 /* same as TK_BITNOT */ #define OP_Once 26 #define OP_If 27 #define OP_IfNot 28 #define OP_IsNull 73 /* same as TK_ISNULL */ #define OP_NotNull 74 /* same as TK_NOTNULL */ #define OP_Column 29 #define OP_Affinity 30 #define OP_MakeRecord 31 #define OP_Count 32 #define OP_Savepoint 33 #define OP_AutoCommit 34 #define OP_Transaction 35 #define OP_ReadCookie 36 #define OP_SetCookie 37 #define OP_VerifyCookie 38 #define OP_OpenRead 39 #define OP_OpenWrite 40 #define OP_OpenAutoindex 41 #define OP_OpenEphemeral 42 #define OP_SorterOpen 43 #define OP_OpenPseudo 44 #define OP_Close 45 #define OP_SeekLt 46 #define OP_SeekLe 47 #define OP_SeekGe 48 #define OP_SeekGt 49 #define OP_Seek 50 #define OP_NotFound 51 #define OP_Found 52 #define OP_IsUnique 53 #define OP_NotExists 54 #define OP_Sequence 55 #define OP_NewRowid 56 #define OP_Insert 57 #define OP_InsertInt 58 #define OP_Delete 59 #define OP_ResetCount 60 #define OP_SorterCompare 61 #define OP_SorterData 62 #define OP_RowKey 63 #define OP_RowData 64 #define OP_Rowid 65 #define OP_NullRow 66 #define OP_Last 67 #define OP_SorterSort 70 #define OP_Sort 71 #define OP_Rewind 72 #define OP_SorterNext 81 #define OP_Prev 92 #define OP_Next 95 #define OP_SorterInsert 96 #define OP_IdxInsert 97 #define OP_IdxDelete 98 #define OP_IdxRowid 99 #define OP_IdxLT 100 #define OP_IdxGE 101 #define OP_Destroy 102 #define OP_Clear 103 #define OP_CreateIndex 104 #define OP_CreateTable 105 #define OP_ParseSchema 106 #define OP_LoadAnalysis 107 #define OP_DropTable 108 #define OP_DropIndex 109 #define OP_DropTrigger 110 #define OP_IntegrityCk 111 #define OP_RowSetAdd 112 #define OP_RowSetRead 113 #define OP_RowSetTest 114 #define OP_Program 115 #define OP_Param 116 #define OP_FkCounter 117 #define OP_FkIfZero 118 #define OP_MemMax 119 #define OP_IfPos 120 #define OP_IfNeg 121 #define OP_IfZero 122 #define OP_AggStep 123 #define OP_AggFinal 124 #define OP_Checkpoint 125 #define OP_JournalMode 126 #define OP_Vacuum 127 #define OP_IncrVacuum 128 #define OP_Expire 129 #define OP_TableLock 131 #define OP_VBegin 132 #define OP_VCreate 133 #define OP_VDestroy 134 #define OP_VOpen 135 #define OP_VFilter 136 #define OP_VColumn 137 #define OP_VNext 138 #define OP_VRename 139 #define OP_VUpdate 140 #define OP_Pagecount 146 #define OP_MaxPgcnt 147 #define OP_Trace 148 #define OP_Noop 149 #define OP_Explain 150 /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c ** are encoded into bitvectors as follows: */ #define OPFLG_JUMP 0x0001 /* jump: P2 holds jmp target */ #define OPFLG_OUT2_PRERELEASE 0x0002 /* out2-prerelease: */ #define OPFLG_IN1 0x0004 /* in1: P1 is an input */ #define OPFLG_IN2 0x0008 /* in2: P2 is an input */ #define OPFLG_IN3 0x0010 /* in3: P3 is an input */ #define OPFLG_OUT2 0x0020 /* out2: P2 is an output */ #define OPFLG_OUT3 0x0040 /* out3: P3 is an output */ #define OPFLG_INITIALIZER {\ /* 0 */ 0x00, 0x01, 0x01, 0x04, 0x04, 0x10, 0x00, 0x02,\ /* 8 */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x24, 0x24,\ /* 16 */ 0x00, 0x00, 0x00, 0x24, 0x04, 0x05, 0x04, 0x00,\ /* 24 */ 0x00, 0x01, 0x01, 0x05, 0x05, 0x00, 0x00, 0x00,\ /* 32 */ 0x02, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00,\ /* 40 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11,\ /* 48 */ 0x11, 0x11, 0x08, 0x11, 0x11, 0x11, 0x11, 0x02,\ /* 56 */ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ /* 64 */ 0x00, 0x02, 0x00, 0x01, 0x4c, 0x4c, 0x01, 0x01,\ /* 72 */ 0x01, 0x05, 0x05, 0x15, 0x15, 0x15, 0x15, 0x15,\ /* 80 */ 0x15, 0x01, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c,\ /* 88 */ 0x4c, 0x4c, 0x4c, 0x4c, 0x01, 0x24, 0x02, 0x01,\ /* 96 */ 0x08, 0x08, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00,\ /* 104 */ 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ /* 112 */ 0x0c, 0x45, 0x15, 0x01, 0x02, 0x00, 0x01, 0x08,\ /* 120 */ 0x05, 0x05, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00,\ /* 128 */ 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,\ /* 136 */ 0x01, 0x00, 0x01, 0x00, 0x00, 0x04, 0x04, 0x04,\ /* 144 */ 0x04, 0x04, 0x02, 0x02, 0x00, 0x00, 0x00,} /************** End of opcodes.h *********************************************/ /************** Continuing where we left off in vdbe.h ***********************/ /* ** Prototypes for the VDBE interface. See comments on the implementation ** for a description of what each of these routines does. */ SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(sqlite3*); SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe*,int); SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe*,int,int); SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe*,int,int,int); SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe*,int,int,int,int); SQLITE_PRIVATE int sqlite3VdbeAddOp4(Vdbe*,int,int,int,int,const char *zP4,int); SQLITE_PRIVATE int sqlite3VdbeAddOp4Int(Vdbe*,int,int,int,int,int); SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp); SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*); SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, u32 addr, int P1); SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, u32 addr, int P2); SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, u32 addr, int P3); SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u8 P5); SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe*, int addr); SQLITE_PRIVATE void sqlite3VdbeChangeToNoop(Vdbe*, int addr); SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe*, int); SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeDeleteObject(sqlite3*,Vdbe*); SQLITE_PRIVATE void sqlite3VdbeMakeReady(Vdbe*,Parse*); SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe*, int); SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe*); #ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *, int); SQLITE_PRIVATE void sqlite3VdbeTrace(Vdbe*,FILE*); #endif SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe*); SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe*,int); SQLITE_PRIVATE int sqlite3VdbeSetColName(Vdbe*, int, int, const char *, void(*)(void*)); SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe*); SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe*, const char *z, int n, int); SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe*,Vdbe*); SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe*, int*, int*); SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetValue(Vdbe*, int, u8); SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe*, int); #ifndef SQLITE_OMIT_TRACE SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*); #endif SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*); SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *, char *, int, char **); #ifndef SQLITE_OMIT_TRIGGER SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *); #endif #ifndef NDEBUG SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe*, const char*, ...); # define VdbeComment(X) sqlite3VdbeComment X SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); # define VdbeNoopComment(X) sqlite3VdbeNoopComment X #else # define VdbeComment(X) # define VdbeNoopComment(X) #endif #endif /************** End of vdbe.h ************************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /************** Include pager.h in the middle of sqliteInt.h *****************/ /************** Begin file pager.h *******************************************/ /* ** 2001 September 15 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This header file defines the interface that the sqlite page cache ** subsystem. The page cache subsystem reads and writes a file a page ** at a time and provides a journal for rollback. */ #ifndef _PAGER_H_ #define _PAGER_H_ /* ** Default maximum size for persistent journal files. A negative ** value means no limit. This value may be overridden using the ** sqlite3PagerJournalSizeLimit() API. See also "PRAGMA journal_size_limit". */ #ifndef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT #define SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT -1 #endif /* ** The type used to represent a page number. The first page in a file ** is called page 1. 0 is used to represent "not a page". */ typedef u32 Pgno; /* ** Each open file is managed by a separate instance of the "Pager" structure. */ typedef struct Pager Pager; /* ** Handle type for pages. */ typedef struct PgHdr DbPage; /* ** Page number PAGER_MJ_PGNO is never used in an SQLite database (it is ** reserved for working around a windows/posix incompatibility). It is ** used in the journal to signify that the remainder of the journal file ** is devoted to storing a master journal name - there are no more pages to ** roll back. See comments for function writeMasterJournal() in pager.c ** for details. */ #define PAGER_MJ_PGNO(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1)) /* ** Allowed values for the flags parameter to sqlite3PagerOpen(). ** ** NOTE: These values must match the corresponding BTREE_ values in btree.h. */ #define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */ #define PAGER_MEMORY 0x0002 /* In-memory database */ /* ** Valid values for the second argument to sqlite3PagerLockingMode(). */ #define PAGER_LOCKINGMODE_QUERY -1 #define PAGER_LOCKINGMODE_NORMAL 0 #define PAGER_LOCKINGMODE_EXCLUSIVE 1 /* ** Numeric constants that encode the journalmode. */ #define PAGER_JOURNALMODE_QUERY (-1) /* Query the value of journalmode */ #define PAGER_JOURNALMODE_DELETE 0 /* Commit by deleting journal file */ #define PAGER_JOURNALMODE_PERSIST 1 /* Commit by zeroing journal header */ #define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */ #define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */ #define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */ #define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */ /* ** The remainder of this file contains the declarations of the functions ** that make up the Pager sub-system API. See source code comments for ** a detailed description of each routine. */ /* Open and close a Pager connection. */ SQLITE_PRIVATE int sqlite3PagerOpen( sqlite3_vfs*, Pager **ppPager, const char*, int, int, int, void(*)(DbPage*) ); SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager); SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); /* Functions used to configure a Pager object. */ SQLITE_PRIVATE void sqlite3PagerSetBusyhandler(Pager*, int(*)(void *), void *); SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager*, u32*, int); SQLITE_PRIVATE int sqlite3PagerMaxPageCount(Pager*, int); SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager*, int); SQLITE_PRIVATE void sqlite3PagerShrink(Pager*); SQLITE_PRIVATE void sqlite3PagerSetSafetyLevel(Pager*,int,int,int); SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *, int); SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *, int); SQLITE_PRIVATE int sqlite3PagerGetJournalMode(Pager*); SQLITE_PRIVATE int sqlite3PagerOkToChangeJournalMode(Pager*); SQLITE_PRIVATE i64 sqlite3PagerJournalSizeLimit(Pager *, i64); SQLITE_PRIVATE sqlite3_backup **sqlite3PagerBackupPtr(Pager*); /* Functions used to obtain and release page references. */ SQLITE_PRIVATE int sqlite3PagerAcquire(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag); #define sqlite3PagerGet(A,B,C) sqlite3PagerAcquire(A,B,C,0) SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno); SQLITE_PRIVATE void sqlite3PagerRef(DbPage*); SQLITE_PRIVATE void sqlite3PagerUnref(DbPage*); /* Operations on page references. */ SQLITE_PRIVATE int sqlite3PagerWrite(DbPage*); SQLITE_PRIVATE void sqlite3PagerDontWrite(DbPage*); SQLITE_PRIVATE int sqlite3PagerMovepage(Pager*,DbPage*,Pgno,int); SQLITE_PRIVATE int sqlite3PagerPageRefcount(DbPage*); SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *); SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *); /* Functions used to manage pager transactions and savepoints. */ SQLITE_PRIVATE void sqlite3PagerPagecount(Pager*, int*); SQLITE_PRIVATE int sqlite3PagerBegin(Pager*, int exFlag, int); SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int); SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager*); SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager); SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager*); SQLITE_PRIVATE int sqlite3PagerRollback(Pager*); SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int n); SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager); SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, int, int*, int*); SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager); SQLITE_PRIVATE int sqlite3PagerWalCallback(Pager *pPager); SQLITE_PRIVATE int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen); SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager); #ifdef SQLITE_ENABLE_ZIPVFS SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager); #endif /* Functions used to query pager state and configuration. */ SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager*); SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*); SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager*); SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager*, int); SQLITE_PRIVATE const sqlite3_vfs *sqlite3PagerVfs(Pager*); SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*); SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager*); SQLITE_PRIVATE int sqlite3PagerNosync(Pager*); SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, int *); SQLITE_PRIVATE void sqlite3PagerClearCache(Pager *); /* Functions used to truncate the database file. */ SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) SQLITE_PRIVATE void *sqlite3PagerCodec(DbPage *); #endif /* Functions to support testing and debugging. */ #if !defined(NDEBUG) || defined(SQLITE_TEST) SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage*); SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage*); #endif #ifdef SQLITE_TEST SQLITE_PRIVATE int *sqlite3PagerStats(Pager*); SQLITE_PRIVATE void sqlite3PagerRefdump(Pager*); void disable_simulated_io_errors(void); void enable_simulated_io_errors(void); #else # define disable_simulated_io_errors() # define enable_simulated_io_errors() #endif #endif /* _PAGER_H_ */ /************** End of pager.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /************** Include pcache.h in the middle of sqliteInt.h ****************/ /************** Begin file pcache.h ******************************************/ /* ** 2008 August 05 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This header file defines the interface that the sqlite page cache ** subsystem. */ #ifndef _PCACHE_H_ typedef struct PgHdr PgHdr; typedef struct PCache PCache; /* ** Every page in the cache is controlled by an instance of the following ** structure. */ struct PgHdr { sqlite3_pcache_page *pPage; /* Pcache object page handle */ void *pData; /* Page data */ void *pExtra; /* Extra content */ PgHdr *pDirty; /* Transient list of dirty pages */ Pager *pPager; /* The pager this page is part of */ Pgno pgno; /* Page number for this page */ #ifdef SQLITE_CHECK_PAGES u32 pageHash; /* Hash of page content */ #endif u16 flags; /* PGHDR flags defined below */ /********************************************************************** ** Elements above are public. All that follows is private to pcache.c ** and should not be accessed by other modules. */ i16 nRef; /* Number of users of this page */ PCache *pCache; /* Cache that owns this page */ PgHdr *pDirtyNext; /* Next element in list of dirty pages */ PgHdr *pDirtyPrev; /* Previous element in list of dirty pages */ }; /* Bit values for PgHdr.flags */ #define PGHDR_DIRTY 0x002 /* Page has changed */ #define PGHDR_NEED_SYNC 0x004 /* Fsync the rollback journal before ** writing this page to the database */ #define PGHDR_NEED_READ 0x008 /* Content is unread */ #define PGHDR_REUSE_UNLIKELY 0x010 /* A hint that reuse is unlikely */ #define PGHDR_DONT_WRITE 0x020 /* Do not write content to disk */ /* Initialize and shutdown the page cache subsystem */ SQLITE_PRIVATE int sqlite3PcacheInitialize(void); SQLITE_PRIVATE void sqlite3PcacheShutdown(void); /* Page cache buffer management: ** These routines implement SQLITE_CONFIG_PAGECACHE. */ SQLITE_PRIVATE void sqlite3PCacheBufferSetup(void *, int sz, int n); /* Create a new pager cache. ** Under memory stress, invoke xStress to try to make pages clean. ** Only clean and unpinned pages can be reclaimed. */ SQLITE_PRIVATE void sqlite3PcacheOpen( int szPage, /* Size of every page */ int szExtra, /* Extra space associated with each page */ int bPurgeable, /* True if pages are on backing store */ int (*xStress)(void*, PgHdr*), /* Call to try to make pages clean */ void *pStress, /* Argument to xStress */ PCache *pToInit /* Preallocated space for the PCache */ ); /* Modify the page-size after the cache has been created. */ SQLITE_PRIVATE void sqlite3PcacheSetPageSize(PCache *, int); /* Return the size in bytes of a PCache object. Used to preallocate ** storage space. */ SQLITE_PRIVATE int sqlite3PcacheSize(void); /* One release per successful fetch. Page is pinned until released. ** Reference counted. */ SQLITE_PRIVATE int sqlite3PcacheFetch(PCache*, Pgno, int createFlag, PgHdr**); SQLITE_PRIVATE void sqlite3PcacheRelease(PgHdr*); SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr*); /* Remove page from cache */ SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr*); /* Make sure page is marked dirty */ SQLITE_PRIVATE void sqlite3PcacheMakeClean(PgHdr*); /* Mark a single page as clean */ SQLITE_PRIVATE void sqlite3PcacheCleanAll(PCache*); /* Mark all dirty list pages as clean */ /* Change a page number. Used by incr-vacuum. */ SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr*, Pgno); /* Remove all pages with pgno>x. Reset the cache if x==0 */ SQLITE_PRIVATE void sqlite3PcacheTruncate(PCache*, Pgno x); /* Get a list of all dirty pages in the cache, sorted by page number */ SQLITE_PRIVATE PgHdr *sqlite3PcacheDirtyList(PCache*); /* Reset and close the cache object */ SQLITE_PRIVATE void sqlite3PcacheClose(PCache*); /* Clear flags from pages of the page cache */ SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *); /* Discard the contents of the cache */ SQLITE_PRIVATE void sqlite3PcacheClear(PCache*); /* Return the total number of outstanding page references */ SQLITE_PRIVATE int sqlite3PcacheRefCount(PCache*); /* Increment the reference count of an existing page */ SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr*); SQLITE_PRIVATE int sqlite3PcachePageRefcount(PgHdr*); /* Return the total number of pages stored in the cache */ SQLITE_PRIVATE int sqlite3PcachePagecount(PCache*); #if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG) /* Iterate through all dirty pages currently stored in the cache. This ** interface is only available if SQLITE_CHECK_PAGES is defined when the ** library is built. */ SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *)); #endif /* Set and get the suggested cache-size for the specified pager-cache. ** ** If no global maximum is configured, then the system attempts to limit ** the total number of pages cached by purgeable pager-caches to the sum ** of the suggested cache-sizes. */ SQLITE_PRIVATE void sqlite3PcacheSetCachesize(PCache *, int); #ifdef SQLITE_TEST SQLITE_PRIVATE int sqlite3PcacheGetCachesize(PCache *); #endif /* Free up as much memory as possible from the page cache */ SQLITE_PRIVATE void sqlite3PcacheShrink(PCache*); #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT /* Try to return memory used by the pcache module to the main memory heap */ SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int); #endif #ifdef SQLITE_TEST SQLITE_PRIVATE void sqlite3PcacheStats(int*,int*,int*,int*); #endif SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); #endif /* _PCACHE_H_ */ /************** End of pcache.h **********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /************** Include os.h in the middle of sqliteInt.h ********************/ /************** Begin file os.h **********************************************/ /* ** 2001 September 16 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This header file (together with is companion C source-code file ** "os.c") attempt to abstract the underlying operating system so that ** the SQLite library will work on both POSIX and windows systems. ** ** This header file is #include-ed by sqliteInt.h and thus ends up ** being included by every source file. */ #ifndef _SQLITE_OS_H_ #define _SQLITE_OS_H_ /* ** Figure out if we are dealing with Unix, Windows, or some other ** operating system. After the following block of preprocess macros, ** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, and SQLITE_OS_OTHER ** will defined to either 1 or 0. One of the four will be 1. The other ** three will be 0. */ #if defined(SQLITE_OS_OTHER) # if SQLITE_OS_OTHER==1 # undef SQLITE_OS_UNIX # define SQLITE_OS_UNIX 0 # undef SQLITE_OS_WIN # define SQLITE_OS_WIN 0 # else # undef SQLITE_OS_OTHER # endif #endif #if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER) # define SQLITE_OS_OTHER 0 # ifndef SQLITE_OS_WIN # if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(__BORLANDC__) # define SQLITE_OS_WIN 1 # define SQLITE_OS_UNIX 0 # else # define SQLITE_OS_WIN 0 # define SQLITE_OS_UNIX 1 # endif # else # define SQLITE_OS_UNIX 0 # endif #else # ifndef SQLITE_OS_WIN # define SQLITE_OS_WIN 0 # endif #endif #if SQLITE_OS_WIN # include #endif /* ** Determine if we are dealing with Windows NT. ** ** We ought to be able to determine if we are compiling for win98 or winNT ** using the _WIN32_WINNT macro as follows: ** ** #if defined(_WIN32_WINNT) ** # define SQLITE_OS_WINNT 1 ** #else ** # define SQLITE_OS_WINNT 0 ** #endif ** ** However, vs2005 does not set _WIN32_WINNT by default, as it ought to, ** so the above test does not work. We'll just assume that everything is ** winNT unless the programmer explicitly says otherwise by setting ** SQLITE_OS_WINNT to 0. */ #if SQLITE_OS_WIN && !defined(SQLITE_OS_WINNT) # define SQLITE_OS_WINNT 1 #endif /* ** Determine if we are dealing with WindowsCE - which has a much ** reduced API. */ #if defined(_WIN32_WCE) # define SQLITE_OS_WINCE 1 #else # define SQLITE_OS_WINCE 0 #endif /* ** Determine if we are dealing with WinRT, which provides only a subset of ** the full Win32 API. */ #if !defined(SQLITE_OS_WINRT) # define SQLITE_OS_WINRT 0 #endif /* ** When compiled for WinCE or WinRT, there is no concept of the current ** directory. */ #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT # define SQLITE_CURDIR 1 #endif /* If the SET_FULLSYNC macro is not defined above, then make it ** a no-op */ #ifndef SET_FULLSYNC # define SET_FULLSYNC(x,y) #endif /* ** The default size of a disk sector */ #ifndef SQLITE_DEFAULT_SECTOR_SIZE # define SQLITE_DEFAULT_SECTOR_SIZE 4096 #endif /* ** Temporary files are named starting with this prefix followed by 16 random ** alphanumeric characters, and no file extension. They are stored in the ** OS's standard temporary file directory, and are deleted prior to exit. ** If sqlite is being embedded in another program, you may wish to change the ** prefix to reflect your program's name, so that if your program exits ** prematurely, old temporary files can be easily identified. This can be done ** using -DSQLITE_TEMP_FILE_PREFIX=myprefix_ on the compiler command line. ** ** 2006-10-31: The default prefix used to be "sqlite_". But then ** Mcafee started using SQLite in their anti-virus product and it ** started putting files with the "sqlite" name in the c:/temp folder. ** This annoyed many windows users. Those users would then do a ** Google search for "sqlite", find the telephone numbers of the ** developers and call to wake them up at night and complain. ** For this reason, the default name prefix is changed to be "sqlite" ** spelled backwards. So the temp files are still identified, but ** anybody smart enough to figure out the code is also likely smart ** enough to know that calling the developer will not help get rid ** of the file. */ #ifndef SQLITE_TEMP_FILE_PREFIX # define SQLITE_TEMP_FILE_PREFIX "etilqs_" #endif /* ** The following values may be passed as the second argument to ** sqlite3OsLock(). The various locks exhibit the following semantics: ** ** SHARED: Any number of processes may hold a SHARED lock simultaneously. ** RESERVED: A single process may hold a RESERVED lock on a file at ** any time. Other processes may hold and obtain new SHARED locks. ** PENDING: A single process may hold a PENDING lock on a file at ** any one time. Existing SHARED locks may persist, but no new ** SHARED locks may be obtained by other processes. ** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. ** ** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a ** process that requests an EXCLUSIVE lock may actually obtain a PENDING ** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to ** sqlite3OsLock(). */ #define NO_LOCK 0 #define SHARED_LOCK 1 #define RESERVED_LOCK 2 #define PENDING_LOCK 3 #define EXCLUSIVE_LOCK 4 /* ** File Locking Notes: (Mostly about windows but also some info for Unix) ** ** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because ** those functions are not available. So we use only LockFile() and ** UnlockFile(). ** ** LockFile() prevents not just writing but also reading by other processes. ** A SHARED_LOCK is obtained by locking a single randomly-chosen ** byte out of a specific range of bytes. The lock byte is obtained at ** random so two separate readers can probably access the file at the ** same time, unless they are unlucky and choose the same lock byte. ** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range. ** There can only be one writer. A RESERVED_LOCK is obtained by locking ** a single byte of the file that is designated as the reserved lock byte. ** A PENDING_LOCK is obtained by locking a designated byte different from ** the RESERVED_LOCK byte. ** ** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available, ** which means we can use reader/writer locks. When reader/writer locks ** are used, the lock is placed on the same range of bytes that is used ** for probabilistic locking in Win95/98/ME. Hence, the locking scheme ** will support two or more Win95 readers or two or more WinNT readers. ** But a single Win95 reader will lock out all WinNT readers and a single ** WinNT reader will lock out all other Win95 readers. ** ** The following #defines specify the range of bytes used for locking. ** SHARED_SIZE is the number of bytes available in the pool from which ** a random byte is selected for a shared lock. The pool of bytes for ** shared locks begins at SHARED_FIRST. ** ** The same locking strategy and ** byte ranges are used for Unix. This leaves open the possiblity of having ** clients on win95, winNT, and unix all talking to the same shared file ** and all locking correctly. To do so would require that samba (or whatever ** tool is being used for file sharing) implements locks correctly between ** windows and unix. I'm guessing that isn't likely to happen, but by ** using the same locking range we are at least open to the possibility. ** ** Locking in windows is manditory. For this reason, we cannot store ** actual data in the bytes used for locking. The pager never allocates ** the pages involved in locking therefore. SHARED_SIZE is selected so ** that all locks will fit on a single page even at the minimum page size. ** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE ** is set high so that we don't have to allocate an unused page except ** for very large databases. But one should test the page skipping logic ** by setting PENDING_BYTE low and running the entire regression suite. ** ** Changing the value of PENDING_BYTE results in a subtly incompatible ** file format. Depending on how it is changed, you might not notice ** the incompatibility right away, even running a full regression test. ** The default location of PENDING_BYTE is the first byte past the ** 1GB boundary. ** */ #ifdef SQLITE_OMIT_WSD # define PENDING_BYTE (0x40000000) #else # define PENDING_BYTE sqlite3PendingByte #endif #define RESERVED_BYTE (PENDING_BYTE+1) #define SHARED_FIRST (PENDING_BYTE+2) #define SHARED_SIZE 510 /* ** Wrapper around OS specific sqlite3_os_init() function. */ SQLITE_PRIVATE int sqlite3OsInit(void); /* ** Functions for accessing sqlite3_file methods */ SQLITE_PRIVATE int sqlite3OsClose(sqlite3_file*); SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset); SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file*, const void*, int amt, i64 offset); SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file*, i64 size); SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file*, int); SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file*, i64 *pSize); SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file*, int); SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file*, int); SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut); SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file*,int,void*); SQLITE_PRIVATE void sqlite3OsFileControlHint(sqlite3_file*,int,void*); #define SQLITE_FCNTL_DB_UNCHANGED 0xca093fa0 SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id); SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id); SQLITE_PRIVATE int sqlite3OsShmMap(sqlite3_file *,int,int,int,void volatile **); SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int, int, int); SQLITE_PRIVATE void sqlite3OsShmBarrier(sqlite3_file *id); SQLITE_PRIVATE int sqlite3OsShmUnmap(sqlite3_file *id, int); /* ** Functions for accessing sqlite3_vfs methods */ SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *); SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int); SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *, const char *, int, int *pResOut); SQLITE_PRIVATE int sqlite3OsFullPathname(sqlite3_vfs *, const char *, int, char *); #ifndef SQLITE_OMIT_LOAD_EXTENSION SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *, const char *); SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *, int, char *); SQLITE_PRIVATE void (*sqlite3OsDlSym(sqlite3_vfs *, void *, const char *))(void); SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *, void *); #endif /* SQLITE_OMIT_LOAD_EXTENSION */ SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *, int, char *); SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int); SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *, sqlite3_int64*); /* ** Convenience functions for opening and closing files using ** sqlite3_malloc() to obtain space for the file-handle structure. */ SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*); SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *); #endif /* _SQLITE_OS_H_ */ /************** End of os.h **************************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /************** Include mutex.h in the middle of sqliteInt.h *****************/ /************** Begin file mutex.h *******************************************/ /* ** 2007 August 28 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** ** This file contains the common header for all mutex implementations. ** The sqliteInt.h header #includes this file so that it is available ** to all source files. We break it out in an effort to keep the code ** better organized. ** ** NOTE: source files should *not* #include this header file directly. ** Source files should #include the sqliteInt.h file and let that file ** include this one indirectly. */ /* ** Figure out what version of the code to use. The choices are ** ** SQLITE_MUTEX_OMIT No mutex logic. Not even stubs. The ** mutexes implemention cannot be overridden ** at start-time. ** ** SQLITE_MUTEX_NOOP For single-threaded applications. No ** mutual exclusion is provided. But this ** implementation can be overridden at ** start-time. ** ** SQLITE_MUTEX_PTHREADS For multi-threaded applications on Unix. ** ** SQLITE_MUTEX_W32 For multi-threaded applications on Win32. */ #if !SQLITE_THREADSAFE # define SQLITE_MUTEX_OMIT #endif #if SQLITE_THREADSAFE && !defined(SQLITE_MUTEX_NOOP) # if SQLITE_OS_UNIX # define SQLITE_MUTEX_PTHREADS # elif SQLITE_OS_WIN # define SQLITE_MUTEX_W32 # else # define SQLITE_MUTEX_NOOP # endif #endif #ifdef SQLITE_MUTEX_OMIT /* ** If this is a no-op implementation, implement everything as macros. */ #define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) #define sqlite3_mutex_free(X) #define sqlite3_mutex_enter(X) #define sqlite3_mutex_try(X) SQLITE_OK #define sqlite3_mutex_leave(X) #define sqlite3_mutex_held(X) ((void)(X),1) #define sqlite3_mutex_notheld(X) ((void)(X),1) #define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8) #define sqlite3MutexInit() SQLITE_OK #define sqlite3MutexEnd() #define MUTEX_LOGIC(X) #else #define MUTEX_LOGIC(X) X #endif /* defined(SQLITE_MUTEX_OMIT) */ /************** End of mutex.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /* ** Each database file to be accessed by the system is an instance ** of the following structure. There are normally two of these structures ** in the sqlite.aDb[] array. aDb[0] is the main database file and ** aDb[1] is the database file used to hold temporary tables. Additional ** databases may be attached. */ struct Db { char *zName; /* Name of this database */ Btree *pBt; /* The B*Tree structure for this database file */ u8 inTrans; /* 0: not writable. 1: Transaction. 2: Checkpoint */ u8 safety_level; /* How aggressive at syncing data to disk */ Schema *pSchema; /* Pointer to database schema (possibly shared) */ }; /* ** An instance of the following structure stores a database schema. ** ** Most Schema objects are associated with a Btree. The exception is ** the Schema for the TEMP databaes (sqlite3.aDb[1]) which is free-standing. ** In shared cache mode, a single Schema object can be shared by multiple ** Btrees that refer to the same underlying BtShared object. ** ** Schema objects are automatically deallocated when the last Btree that ** references them is destroyed. The TEMP Schema is manually freed by ** sqlite3_close(). * ** A thread must be holding a mutex on the corresponding Btree in order ** to access Schema content. This implies that the thread must also be ** holding a mutex on the sqlite3 connection pointer that owns the Btree. ** For a TEMP Schema, only the connection mutex is required. */ struct Schema { int schema_cookie; /* Database schema version number for this file */ int iGeneration; /* Generation counter. Incremented with each change */ Hash tblHash; /* All tables indexed by name */ Hash idxHash; /* All (named) indices indexed by name */ Hash trigHash; /* All triggers indexed by name */ Hash fkeyHash; /* All foreign keys by referenced table name */ Table *pSeqTab; /* The sqlite_sequence table used by AUTOINCREMENT */ u8 file_format; /* Schema format version for this file */ u8 enc; /* Text encoding used by this database */ u16 flags; /* Flags associated with this schema */ int cache_size; /* Number of pages to use in the cache */ }; /* ** These macros can be used to test, set, or clear bits in the ** Db.pSchema->flags field. */ #define DbHasProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))==(P)) #define DbHasAnyProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))!=0) #define DbSetProperty(D,I,P) (D)->aDb[I].pSchema->flags|=(P) #define DbClearProperty(D,I,P) (D)->aDb[I].pSchema->flags&=~(P) /* ** Allowed values for the DB.pSchema->flags field. ** ** The DB_SchemaLoaded flag is set after the database schema has been ** read into internal hash tables. ** ** DB_UnresetViews means that one or more views have column names that ** have been filled out. If the schema changes, these column names might ** changes and so the view will need to be reset. */ #define DB_SchemaLoaded 0x0001 /* The schema has been loaded */ #define DB_UnresetViews 0x0002 /* Some views have defined column names */ #define DB_Empty 0x0004 /* The file is empty (length 0 bytes) */ /* ** The number of different kinds of things that can be limited ** using the sqlite3_limit() interface. */ #define SQLITE_N_LIMIT (SQLITE_LIMIT_TRIGGER_DEPTH+1) /* ** Lookaside malloc is a set of fixed-size buffers that can be used ** to satisfy small transient memory allocation requests for objects ** associated with a particular database connection. The use of ** lookaside malloc provides a significant performance enhancement ** (approx 10%) by avoiding numerous malloc/free requests while parsing ** SQL statements. ** ** The Lookaside structure holds configuration information about the ** lookaside malloc subsystem. Each available memory allocation in ** the lookaside subsystem is stored on a linked list of LookasideSlot ** objects. ** ** Lookaside allocations are only allowed for objects that are associated ** with a particular database connection. Hence, schema information cannot ** be stored in lookaside because in shared cache mode the schema information ** is shared by multiple database connections. Therefore, while parsing ** schema information, the Lookaside.bEnabled flag is cleared so that ** lookaside allocations are not used to construct the schema objects. */ struct Lookaside { u16 sz; /* Size of each buffer in bytes */ u8 bEnabled; /* False to disable new lookaside allocations */ u8 bMalloced; /* True if pStart obtained from sqlite3_malloc() */ int nOut; /* Number of buffers currently checked out */ int mxOut; /* Highwater mark for nOut */ int anStat[3]; /* 0: hits. 1: size misses. 2: full misses */ LookasideSlot *pFree; /* List of available buffers */ void *pStart; /* First byte of available memory space */ void *pEnd; /* First byte past end of available space */ }; struct LookasideSlot { LookasideSlot *pNext; /* Next buffer in the list of free buffers */ }; /* ** A hash table for function definitions. ** ** Hash each FuncDef structure into one of the FuncDefHash.a[] slots. ** Collisions are on the FuncDef.pHash chain. */ struct FuncDefHash { FuncDef *a[23]; /* Hash table for functions */ }; /* ** Each database connection is an instance of the following structure. */ struct sqlite3 { sqlite3_vfs *pVfs; /* OS Interface */ struct Vdbe *pVdbe; /* List of active virtual machines */ CollSeq *pDfltColl; /* The default collating sequence (BINARY) */ sqlite3_mutex *mutex; /* Connection mutex */ Db *aDb; /* All backends */ int nDb; /* Number of backends currently in use */ int flags; /* Miscellaneous flags. See below */ i64 lastRowid; /* ROWID of most recent insert (see above) */ unsigned int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ int errCode; /* Most recent error code (SQLITE_*) */ int errMask; /* & result codes with this before returning */ u8 autoCommit; /* The auto-commit flag. */ u8 temp_store; /* 1: file 2: memory 0: default */ u8 mallocFailed; /* True if we have seen a malloc failure */ u8 dfltLockMode; /* Default locking-mode for attached dbs */ signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */ u8 suppressErr; /* Do not issue error messages if true */ u8 vtabOnConflict; /* Value to return for s3_vtab_on_conflict() */ u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */ int nextPagesize; /* Pagesize after VACUUM if >0 */ u32 magic; /* Magic number for detect library misuse */ int nChange; /* Value returned by sqlite3_changes() */ int nTotalChange; /* Value returned by sqlite3_total_changes() */ int aLimit[SQLITE_N_LIMIT]; /* Limits */ struct sqlite3InitInfo { /* Information used during initialization */ int newTnum; /* Rootpage of table being initialized */ u8 iDb; /* Which db file is being initialized */ u8 busy; /* TRUE if currently initializing */ u8 orphanTrigger; /* Last statement is orphaned TEMP trigger */ } init; int activeVdbeCnt; /* Number of VDBEs currently executing */ int writeVdbeCnt; /* Number of active VDBEs that are writing */ int vdbeExecCnt; /* Number of nested calls to VdbeExec() */ int nExtension; /* Number of loaded extensions */ void **aExtension; /* Array of shared library handles */ void (*xTrace)(void*,const char*); /* Trace function */ void *pTraceArg; /* Argument to the trace function */ void (*xProfile)(void*,const char*,u64); /* Profiling function */ void *pProfileArg; /* Argument to profile function */ void *pCommitArg; /* Argument to xCommitCallback() */ int (*xCommitCallback)(void*); /* Invoked at every commit. */ void *pRollbackArg; /* Argument to xRollbackCallback() */ void (*xRollbackCallback)(void*); /* Invoked at every commit. */ void *pUpdateArg; void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64); #ifndef SQLITE_OMIT_WAL int (*xWalCallback)(void *, sqlite3 *, const char *, int); void *pWalArg; #endif void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*); void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*); void *pCollNeededArg; sqlite3_value *pErr; /* Most recent error message */ char *zErrMsg; /* Most recent error message (UTF-8 encoded) */ char *zErrMsg16; /* Most recent error message (UTF-16 encoded) */ union { volatile int isInterrupted; /* True if sqlite3_interrupt has been called */ double notUsed1; /* Spacer */ } u1; Lookaside lookaside; /* Lookaside malloc configuration */ #ifndef SQLITE_OMIT_AUTHORIZATION int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); /* Access authorization function */ void *pAuthArg; /* 1st argument to the access auth function */ #endif #ifndef SQLITE_OMIT_PROGRESS_CALLBACK int (*xProgress)(void *); /* The progress callback */ void *pProgressArg; /* Argument to the progress callback */ int nProgressOps; /* Number of opcodes for progress callback */ #endif #ifndef SQLITE_OMIT_VIRTUALTABLE int nVTrans; /* Allocated size of aVTrans */ Hash aModule; /* populated by sqlite3_create_module() */ VtabCtx *pVtabCtx; /* Context for active vtab connect/create */ VTable **aVTrans; /* Virtual tables with open transactions */ VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */ #endif FuncDefHash aFunc; /* Hash table of connection functions */ Hash aCollSeq; /* All collating sequences */ BusyHandler busyHandler; /* Busy callback */ Db aDbStatic[2]; /* Static space for the 2 default backends */ Savepoint *pSavepoint; /* List of active savepoints */ int busyTimeout; /* Busy handler timeout, in msec */ int nSavepoint; /* Number of non-transaction savepoints */ int nStatement; /* Number of nested statement-transactions */ i64 nDeferredCons; /* Net deferred constraints this transaction. */ int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY /* The following variables are all protected by the STATIC_MASTER ** mutex, not by sqlite3.mutex. They are used by code in notify.c. ** ** When X.pUnlockConnection==Y, that means that X is waiting for Y to ** unlock so that it can proceed. ** ** When X.pBlockingConnection==Y, that means that something that X tried ** tried to do recently failed with an SQLITE_LOCKED error due to locks ** held by Y. */ sqlite3 *pBlockingConnection; /* Connection that caused SQLITE_LOCKED */ sqlite3 *pUnlockConnection; /* Connection to watch for unlock */ void *pUnlockArg; /* Argument to xUnlockNotify */ void (*xUnlockNotify)(void **, int); /* Unlock notify callback */ sqlite3 *pNextBlocked; /* Next in list of all blocked connections */ #endif }; /* ** A macro to discover the encoding of a database. */ #define ENC(db) ((db)->aDb[0].pSchema->enc) /* ** Possible values for the sqlite3.flags. */ #define SQLITE_VdbeTrace 0x00000100 /* True to trace VDBE execution */ #define SQLITE_InternChanges 0x00000200 /* Uncommitted Hash table changes */ #define SQLITE_FullColNames 0x00000400 /* Show full column names on SELECT */ #define SQLITE_ShortColNames 0x00000800 /* Show short columns names */ #define SQLITE_CountRows 0x00001000 /* Count rows changed by INSERT, */ /* DELETE, or UPDATE and return */ /* the count using a callback. */ #define SQLITE_NullCallback 0x00002000 /* Invoke the callback once if the */ /* result set is empty */ #define SQLITE_SqlTrace 0x00004000 /* Debug print SQL as it executes */ #define SQLITE_VdbeListing 0x00008000 /* Debug listings of VDBE programs */ #define SQLITE_WriteSchema 0x00010000 /* OK to update SQLITE_MASTER */ /* 0x00020000 Unused */ #define SQLITE_IgnoreChecks 0x00040000 /* Do not enforce check constraints */ #define SQLITE_ReadUncommitted 0x0080000 /* For shared-cache mode */ #define SQLITE_LegacyFileFmt 0x00100000 /* Create new databases in format 1 */ #define SQLITE_FullFSync 0x00200000 /* Use full fsync on the backend */ #define SQLITE_CkptFullFSync 0x00400000 /* Use full fsync for checkpoint */ #define SQLITE_RecoveryMode 0x00800000 /* Ignore schema errors */ #define SQLITE_ReverseOrder 0x01000000 /* Reverse unordered SELECTs */ #define SQLITE_RecTriggers 0x02000000 /* Enable recursive triggers */ #define SQLITE_ForeignKeys 0x04000000 /* Enforce foreign key constraints */ #define SQLITE_AutoIndex 0x08000000 /* Enable automatic indexes */ #define SQLITE_PreferBuiltin 0x10000000 /* Preference to built-in funcs */ #define SQLITE_LoadExtension 0x20000000 /* Enable load_extension */ #define SQLITE_EnableTrigger 0x40000000 /* True to enable triggers */ /* ** Bits of the sqlite3.flags field that are used by the ** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface. ** These must be the low-order bits of the flags field. */ #define SQLITE_QueryFlattener 0x01 /* Disable query flattening */ #define SQLITE_ColumnCache 0x02 /* Disable the column cache */ #define SQLITE_IndexSort 0x04 /* Disable indexes for sorting */ #define SQLITE_IndexSearch 0x08 /* Disable indexes for searching */ #define SQLITE_IndexCover 0x10 /* Disable index covering table */ #define SQLITE_GroupByOrder 0x20 /* Disable GROUPBY cover of ORDERBY */ #define SQLITE_FactorOutConst 0x40 /* Disable factoring out constants */ #define SQLITE_IdxRealAsInt 0x80 /* Store REAL as INT in indices */ #define SQLITE_DistinctOpt 0x80 /* DISTINCT using indexes */ #define SQLITE_OptMask 0xff /* Mask of all disablable opts */ /* ** Possible values for the sqlite.magic field. ** The numbers are obtained at random and have no special meaning, other ** than being distinct from one another. */ #define SQLITE_MAGIC_OPEN 0xa029a697 /* Database is open */ #define SQLITE_MAGIC_CLOSED 0x9f3c2d33 /* Database is closed */ #define SQLITE_MAGIC_SICK 0x4b771290 /* Error and awaiting close */ #define SQLITE_MAGIC_BUSY 0xf03b7906 /* Database currently in use */ #define SQLITE_MAGIC_ERROR 0xb5357930 /* An SQLITE_MISUSE error occurred */ #define SQLITE_MAGIC_ZOMBIE 0x64cffc7f /* Close with last statement close */ /* ** Each SQL function is defined by an instance of the following ** structure. A pointer to this structure is stored in the sqlite.aFunc ** hash table. When multiple functions have the same name, the hash table ** points to a linked list of these structures. */ struct FuncDef { i16 nArg; /* Number of arguments. -1 means unlimited */ u8 iPrefEnc; /* Preferred text encoding (SQLITE_UTF8, 16LE, 16BE) */ u8 flags; /* Some combination of SQLITE_FUNC_* */ void *pUserData; /* User data parameter */ FuncDef *pNext; /* Next function with same name */ void (*xFunc)(sqlite3_context*,int,sqlite3_value**); /* Regular function */ void (*xStep)(sqlite3_context*,int,sqlite3_value**); /* Aggregate step */ void (*xFinalize)(sqlite3_context*); /* Aggregate finalizer */ char *zName; /* SQL name of the function. */ FuncDef *pHash; /* Next with a different name but the same hash */ FuncDestructor *pDestructor; /* Reference counted destructor function */ }; /* ** This structure encapsulates a user-function destructor callback (as ** configured using create_function_v2()) and a reference counter. When ** create_function_v2() is called to create a function with a destructor, ** a single object of this type is allocated. FuncDestructor.nRef is set to ** the number of FuncDef objects created (either 1 or 3, depending on whether ** or not the specified encoding is SQLITE_ANY). The FuncDef.pDestructor ** member of each of the new FuncDef objects is set to point to the allocated ** FuncDestructor. ** ** Thereafter, when one of the FuncDef objects is deleted, the reference ** count on this object is decremented. When it reaches 0, the destructor ** is invoked and the FuncDestructor structure freed. */ struct FuncDestructor { int nRef; void (*xDestroy)(void *); void *pUserData; }; /* ** Possible values for FuncDef.flags. Note that the _LENGTH and _TYPEOF ** values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG. There ** are assert() statements in the code to verify this. */ #define SQLITE_FUNC_LIKE 0x01 /* Candidate for the LIKE optimization */ #define SQLITE_FUNC_CASE 0x02 /* Case-sensitive LIKE-type function */ #define SQLITE_FUNC_EPHEM 0x04 /* Ephemeral. Delete with VDBE */ #define SQLITE_FUNC_NEEDCOLL 0x08 /* sqlite3GetFuncCollSeq() might be called */ #define SQLITE_FUNC_COUNT 0x10 /* Built-in count(*) aggregate */ #define SQLITE_FUNC_COALESCE 0x20 /* Built-in coalesce() or ifnull() function */ #define SQLITE_FUNC_LENGTH 0x40 /* Built-in length() function */ #define SQLITE_FUNC_TYPEOF 0x80 /* Built-in typeof() function */ /* ** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are ** used to create the initializers for the FuncDef structures. ** ** FUNCTION(zName, nArg, iArg, bNC, xFunc) ** Used to create a scalar function definition of a function zName ** implemented by C function xFunc that accepts nArg arguments. The ** value passed as iArg is cast to a (void*) and made available ** as the user-data (sqlite3_user_data()) for the function. If ** argument bNC is true, then the SQLITE_FUNC_NEEDCOLL flag is set. ** ** AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal) ** Used to create an aggregate function definition implemented by ** the C functions xStep and xFinal. The first four parameters ** are interpreted in the same way as the first 4 parameters to ** FUNCTION(). ** ** LIKEFUNC(zName, nArg, pArg, flags) ** Used to create a scalar function definition of a function zName ** that accepts nArg arguments and is implemented by a call to C ** function likeFunc. Argument pArg is cast to a (void *) and made ** available as the function user-data (sqlite3_user_data()). The ** FuncDef.flags variable is set to the value passed as the flags ** parameter. */ #define FUNCTION(zName, nArg, iArg, bNC, xFunc) \ {nArg, SQLITE_UTF8, (bNC*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0} #define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \ {nArg, SQLITE_UTF8, (bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags, \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0} #define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \ {nArg, SQLITE_UTF8, bNC*SQLITE_FUNC_NEEDCOLL, \ pArg, 0, xFunc, 0, 0, #zName, 0, 0} #define LIKEFUNC(zName, nArg, arg, flags) \ {nArg, SQLITE_UTF8, flags, (void *)arg, 0, likeFunc, 0, 0, #zName, 0, 0} #define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal) \ {nArg, SQLITE_UTF8, nc*SQLITE_FUNC_NEEDCOLL, \ SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0,0} /* ** All current savepoints are stored in a linked list starting at ** sqlite3.pSavepoint. The first element in the list is the most recently ** opened savepoint. Savepoints are added to the list by the vdbe ** OP_Savepoint instruction. */ struct Savepoint { char *zName; /* Savepoint name (nul-terminated) */ i64 nDeferredCons; /* Number of deferred fk violations */ Savepoint *pNext; /* Parent savepoint (if any) */ }; /* ** The following are used as the second parameter to sqlite3Savepoint(), ** and as the P1 argument to the OP_Savepoint instruction. */ #define SAVEPOINT_BEGIN 0 #define SAVEPOINT_RELEASE 1 #define SAVEPOINT_ROLLBACK 2 /* ** Each SQLite module (virtual table definition) is defined by an ** instance of the following structure, stored in the sqlite3.aModule ** hash table. */ struct Module { const sqlite3_module *pModule; /* Callback pointers */ const char *zName; /* Name passed to create_module() */ void *pAux; /* pAux passed to create_module() */ void (*xDestroy)(void *); /* Module destructor function */ }; /* ** information about each column of an SQL table is held in an instance ** of this structure. */ struct Column { char *zName; /* Name of this column */ Expr *pDflt; /* Default value of this column */ char *zDflt; /* Original text of the default value */ char *zType; /* Data type for this column */ char *zColl; /* Collating sequence. If NULL, use the default */ u8 notNull; /* True if there is a NOT NULL constraint */ u8 isPrimKey; /* True if this column is part of the PRIMARY KEY */ char affinity; /* One of the SQLITE_AFF_... values */ #ifndef SQLITE_OMIT_VIRTUALTABLE u8 isHidden; /* True if this column is 'hidden' */ #endif }; /* ** A "Collating Sequence" is defined by an instance of the following ** structure. Conceptually, a collating sequence consists of a name and ** a comparison routine that defines the order of that sequence. ** ** There may two separate implementations of the collation function, one ** that processes text in UTF-8 encoding (CollSeq.xCmp) and another that ** processes text encoded in UTF-16 (CollSeq.xCmp16), using the machine ** native byte order. When a collation sequence is invoked, SQLite selects ** the version that will require the least expensive encoding ** translations, if any. ** ** The CollSeq.pUser member variable is an extra parameter that passed in ** as the first argument to the UTF-8 comparison function, xCmp. ** CollSeq.pUser16 is the equivalent for the UTF-16 comparison function, ** xCmp16. ** ** If both CollSeq.xCmp and CollSeq.xCmp16 are NULL, it means that the ** collating sequence is undefined. Indices built on an undefined ** collating sequence may not be read or written. */ struct CollSeq { char *zName; /* Name of the collating sequence, UTF-8 encoded */ u8 enc; /* Text encoding handled by xCmp() */ void *pUser; /* First argument to xCmp() */ int (*xCmp)(void*,int, const void*, int, const void*); void (*xDel)(void*); /* Destructor for pUser */ }; /* ** A sort order can be either ASC or DESC. */ #define SQLITE_SO_ASC 0 /* Sort in ascending order */ #define SQLITE_SO_DESC 1 /* Sort in ascending order */ /* ** Column affinity types. ** ** These used to have mnemonic name like 'i' for SQLITE_AFF_INTEGER and ** 't' for SQLITE_AFF_TEXT. But we can save a little space and improve ** the speed a little by numbering the values consecutively. ** ** But rather than start with 0 or 1, we begin with 'a'. That way, ** when multiple affinity types are concatenated into a string and ** used as the P4 operand, they will be more readable. ** ** Note also that the numeric types are grouped together so that testing ** for a numeric type is a single comparison. */ #define SQLITE_AFF_TEXT 'a' #define SQLITE_AFF_NONE 'b' #define SQLITE_AFF_NUMERIC 'c' #define SQLITE_AFF_INTEGER 'd' #define SQLITE_AFF_REAL 'e' #define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) /* ** The SQLITE_AFF_MASK values masks off the significant bits of an ** affinity value. */ #define SQLITE_AFF_MASK 0x67 /* ** Additional bit values that can be ORed with an affinity without ** changing the affinity. */ #define SQLITE_JUMPIFNULL 0x08 /* jumps if either operand is NULL */ #define SQLITE_STOREP2 0x10 /* Store result in reg[P2] rather than jump */ #define SQLITE_NULLEQ 0x80 /* NULL=NULL */ /* ** An object of this type is created for each virtual table present in ** the database schema. ** ** If the database schema is shared, then there is one instance of this ** structure for each database connection (sqlite3*) that uses the shared ** schema. This is because each database connection requires its own unique ** instance of the sqlite3_vtab* handle used to access the virtual table ** implementation. sqlite3_vtab* handles can not be shared between ** database connections, even when the rest of the in-memory database ** schema is shared, as the implementation often stores the database ** connection handle passed to it via the xConnect() or xCreate() method ** during initialization internally. This database connection handle may ** then be used by the virtual table implementation to access real tables ** within the database. So that they appear as part of the callers ** transaction, these accesses need to be made via the same database ** connection as that used to execute SQL operations on the virtual table. ** ** All VTable objects that correspond to a single table in a shared ** database schema are initially stored in a linked-list pointed to by ** the Table.pVTable member variable of the corresponding Table object. ** When an sqlite3_prepare() operation is required to access the virtual ** table, it searches the list for the VTable that corresponds to the ** database connection doing the preparing so as to use the correct ** sqlite3_vtab* handle in the compiled query. ** ** When an in-memory Table object is deleted (for example when the ** schema is being reloaded for some reason), the VTable objects are not ** deleted and the sqlite3_vtab* handles are not xDisconnect()ed ** immediately. Instead, they are moved from the Table.pVTable list to ** another linked list headed by the sqlite3.pDisconnect member of the ** corresponding sqlite3 structure. They are then deleted/xDisconnected ** next time a statement is prepared using said sqlite3*. This is done ** to avoid deadlock issues involving multiple sqlite3.mutex mutexes. ** Refer to comments above function sqlite3VtabUnlockList() for an ** explanation as to why it is safe to add an entry to an sqlite3.pDisconnect ** list without holding the corresponding sqlite3.mutex mutex. ** ** The memory for objects of this type is always allocated by ** sqlite3DbMalloc(), using the connection handle stored in VTable.db as ** the first argument. */ struct VTable { sqlite3 *db; /* Database connection associated with this table */ Module *pMod; /* Pointer to module implementation */ sqlite3_vtab *pVtab; /* Pointer to vtab instance */ int nRef; /* Number of pointers to this structure */ u8 bConstraint; /* True if constraints are supported */ int iSavepoint; /* Depth of the SAVEPOINT stack */ VTable *pNext; /* Next in linked list (see above) */ }; /* ** Each SQL table is represented in memory by an instance of the ** following structure. ** ** Table.zName is the name of the table. The case of the original ** CREATE TABLE statement is stored, but case is not significant for ** comparisons. ** ** Table.nCol is the number of columns in this table. Table.aCol is a ** pointer to an array of Column structures, one for each column. ** ** If the table has an INTEGER PRIMARY KEY, then Table.iPKey is the index of ** the column that is that key. Otherwise Table.iPKey is negative. Note ** that the datatype of the PRIMARY KEY must be INTEGER for this field to ** be set. An INTEGER PRIMARY KEY is used as the rowid for each row of ** the table. If a table has no INTEGER PRIMARY KEY, then a random rowid ** is generated for each row of the table. TF_HasPrimaryKey is set if ** the table has any PRIMARY KEY, INTEGER or otherwise. ** ** Table.tnum is the page number for the root BTree page of the table in the ** database file. If Table.iDb is the index of the database table backend ** in sqlite.aDb[]. 0 is for the main database and 1 is for the file that ** holds temporary tables and indices. If TF_Ephemeral is set ** then the table is stored in a file that is automatically deleted ** when the VDBE cursor to the table is closed. In this case Table.tnum ** refers VDBE cursor number that holds the table open, not to the root ** page number. Transient tables are used to hold the results of a ** sub-query that appears instead of a real table name in the FROM clause ** of a SELECT statement. */ struct Table { char *zName; /* Name of the table or view */ int iPKey; /* If not negative, use aCol[iPKey] as the primary key */ int nCol; /* Number of columns in this table */ Column *aCol; /* Information about each column */ Index *pIndex; /* List of SQL indexes on this table. */ int tnum; /* Root BTree node for this table (see note above) */ tRowcnt nRowEst; /* Estimated rows in table - from sqlite_stat1 table */ Select *pSelect; /* NULL for tables. Points to definition if a view. */ u16 nRef; /* Number of pointers to this Table */ u8 tabFlags; /* Mask of TF_* values */ u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ FKey *pFKey; /* Linked list of all foreign keys in this table */ char *zColAff; /* String defining the affinity of each column */ #ifndef SQLITE_OMIT_CHECK ExprList *pCheck; /* All CHECK constraints */ #endif #ifndef SQLITE_OMIT_ALTERTABLE int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ #endif #ifndef SQLITE_OMIT_VIRTUALTABLE VTable *pVTable; /* List of VTable objects. */ int nModuleArg; /* Number of arguments to the module */ char **azModuleArg; /* Text of all module args. [0] is module name */ #endif Trigger *pTrigger; /* List of triggers stored in pSchema */ Schema *pSchema; /* Schema that contains this table */ Table *pNextZombie; /* Next on the Parse.pZombieTab list */ }; /* ** Allowed values for Tabe.tabFlags. */ #define TF_Readonly 0x01 /* Read-only system table */ #define TF_Ephemeral 0x02 /* An ephemeral table */ #define TF_HasPrimaryKey 0x04 /* Table has a primary key */ #define TF_Autoincrement 0x08 /* Integer primary key is autoincrement */ #define TF_Virtual 0x10 /* Is a virtual table */ /* ** Test to see whether or not a table is a virtual table. This is ** done as a macro so that it will be optimized out when virtual ** table support is omitted from the build. */ #ifndef SQLITE_OMIT_VIRTUALTABLE # define IsVirtual(X) (((X)->tabFlags & TF_Virtual)!=0) # define IsHiddenColumn(X) ((X)->isHidden) #else # define IsVirtual(X) 0 # define IsHiddenColumn(X) 0 #endif /* ** Each foreign key constraint is an instance of the following structure. ** ** A foreign key is associated with two tables. The "from" table is ** the table that contains the REFERENCES clause that creates the foreign ** key. The "to" table is the table that is named in the REFERENCES clause. ** Consider this example: ** ** CREATE TABLE ex1( ** a INTEGER PRIMARY KEY, ** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x) ** ); ** ** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2". ** ** Each REFERENCES clause generates an instance of the following structure ** which is attached to the from-table. The to-table need not exist when ** the from-table is created. The existence of the to-table is not checked. */ struct FKey { Table *pFrom; /* Table containing the REFERENCES clause (aka: Child) */ FKey *pNextFrom; /* Next foreign key in pFrom */ char *zTo; /* Name of table that the key points to (aka: Parent) */ FKey *pNextTo; /* Next foreign key on table named zTo */ FKey *pPrevTo; /* Previous foreign key on table named zTo */ int nCol; /* Number of columns in this key */ /* EV: R-30323-21917 */ u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ u8 aAction[2]; /* ON DELETE and ON UPDATE actions, respectively */ Trigger *apTrigger[2]; /* Triggers for aAction[] actions */ struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ int iFrom; /* Index of column in pFrom */ char *zCol; /* Name of column in zTo. If 0 use PRIMARY KEY */ } aCol[1]; /* One entry for each of nCol column s */ }; /* ** SQLite supports many different ways to resolve a constraint ** error. ROLLBACK processing means that a constraint violation ** causes the operation in process to fail and for the current transaction ** to be rolled back. ABORT processing means the operation in process ** fails and any prior changes from that one operation are backed out, ** but the transaction is not rolled back. FAIL processing means that ** the operation in progress stops and returns an error code. But prior ** changes due to the same operation are not backed out and no rollback ** occurs. IGNORE means that the particular row that caused the constraint ** error is not inserted or updated. Processing continues and no error ** is returned. REPLACE means that preexisting database rows that caused ** a UNIQUE constraint violation are removed so that the new insert or ** update can proceed. Processing continues and no error is reported. ** ** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys. ** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the ** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign ** key is set to NULL. CASCADE means that a DELETE or UPDATE of the ** referenced table row is propagated into the row that holds the ** foreign key. ** ** The following symbolic values are used to record which type ** of action to take. */ #define OE_None 0 /* There is no constraint to check */ #define OE_Rollback 1 /* Fail the operation and rollback the transaction */ #define OE_Abort 2 /* Back out changes but do no rollback transaction */ #define OE_Fail 3 /* Stop the operation but leave all prior changes */ #define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */ #define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */ #define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */ #define OE_SetNull 7 /* Set the foreign key value to NULL */ #define OE_SetDflt 8 /* Set the foreign key value to its default */ #define OE_Cascade 9 /* Cascade the changes */ #define OE_Default 99 /* Do whatever the default action is */ /* ** An instance of the following structure is passed as the first ** argument to sqlite3VdbeKeyCompare and is used to control the ** comparison of the two index keys. */ struct KeyInfo { sqlite3 *db; /* The database connection */ u8 enc; /* Text encoding - one of the SQLITE_UTF* values */ u16 nField; /* Number of entries in aColl[] */ u8 *aSortOrder; /* Sort order for each column. May be NULL */ CollSeq *aColl[1]; /* Collating sequence for each term of the key */ }; /* ** An instance of the following structure holds information about a ** single index record that has already been parsed out into individual ** values. ** ** A record is an object that contains one or more fields of data. ** Records are used to store the content of a table row and to store ** the key of an index. A blob encoding of a record is created by ** the OP_MakeRecord opcode of the VDBE and is disassembled by the ** OP_Column opcode. ** ** This structure holds a record that has already been disassembled ** into its constituent fields. */ struct UnpackedRecord { KeyInfo *pKeyInfo; /* Collation and sort-order information */ u16 nField; /* Number of entries in apMem[] */ u8 flags; /* Boolean settings. UNPACKED_... below */ i64 rowid; /* Used by UNPACKED_PREFIX_SEARCH */ Mem *aMem; /* Values */ }; /* ** Allowed values of UnpackedRecord.flags */ #define UNPACKED_INCRKEY 0x01 /* Make this key an epsilon larger */ #define UNPACKED_PREFIX_MATCH 0x02 /* A prefix match is considered OK */ #define UNPACKED_PREFIX_SEARCH 0x04 /* Ignore final (rowid) field */ /* ** Each SQL index is represented in memory by an ** instance of the following structure. ** ** The columns of the table that are to be indexed are described ** by the aiColumn[] field of this structure. For example, suppose ** we have the following table and index: ** ** CREATE TABLE Ex1(c1 int, c2 int, c3 text); ** CREATE INDEX Ex2 ON Ex1(c3,c1); ** ** In the Table structure describing Ex1, nCol==3 because there are ** three columns in the table. In the Index structure describing ** Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed. ** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the ** first column to be indexed (c3) has an index of 2 in Ex1.aCol[]. ** The second column to be indexed (c1) has an index of 0 in ** Ex1.aCol[], hence Ex2.aiColumn[1]==0. ** ** The Index.onError field determines whether or not the indexed columns ** must be unique and what to do if they are not. When Index.onError=OE_None, ** it means this is not a unique index. Otherwise it is a unique index ** and the value of Index.onError indicate the which conflict resolution ** algorithm to employ whenever an attempt is made to insert a non-unique ** element. */ struct Index { char *zName; /* Name of this index */ int *aiColumn; /* Which columns are used by this index. 1st is 0 */ tRowcnt *aiRowEst; /* Result of ANALYZE: Est. rows selected by each column */ Table *pTable; /* The SQL table being indexed */ char *zColAff; /* String defining the affinity of each column */ Index *pNext; /* The next index associated with the same table */ Schema *pSchema; /* Schema containing this index */ u8 *aSortOrder; /* Array of size Index.nColumn. True==DESC, False==ASC */ char **azColl; /* Array of collation sequence names for index */ int nColumn; /* Number of columns in the table used by this index */ int tnum; /* Page containing root of this index in database file */ u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ u8 autoIndex; /* True if is automatically created (ex: by UNIQUE) */ u8 bUnordered; /* Use this index for == or IN queries only */ #ifdef SQLITE_ENABLE_STAT3 int nSample; /* Number of elements in aSample[] */ tRowcnt avgEq; /* Average nEq value for key values not in aSample */ IndexSample *aSample; /* Samples of the left-most key */ #endif }; /* ** Each sample stored in the sqlite_stat3 table is represented in memory ** using a structure of this type. See documentation at the top of the ** analyze.c source file for additional information. */ struct IndexSample { union { char *z; /* Value if eType is SQLITE_TEXT or SQLITE_BLOB */ double r; /* Value if eType is SQLITE_FLOAT */ i64 i; /* Value if eType is SQLITE_INTEGER */ } u; u8 eType; /* SQLITE_NULL, SQLITE_INTEGER ... etc. */ int nByte; /* Size in byte of text or blob. */ tRowcnt nEq; /* Est. number of rows where the key equals this sample */ tRowcnt nLt; /* Est. number of rows where key is less than this sample */ tRowcnt nDLt; /* Est. number of distinct keys less than this sample */ }; /* ** Each token coming out of the lexer is an instance of ** this structure. Tokens are also used as part of an expression. ** ** Note if Token.z==0 then Token.dyn and Token.n are undefined and ** may contain random values. Do not make any assumptions about Token.dyn ** and Token.n when Token.z==0. */ struct Token { const char *z; /* Text of the token. Not NULL-terminated! */ unsigned int n; /* Number of characters in this token */ }; /* ** An instance of this structure contains information needed to generate ** code for a SELECT that contains aggregate functions. ** ** If Expr.op==TK_AGG_COLUMN or TK_AGG_FUNCTION then Expr.pAggInfo is a ** pointer to this structure. The Expr.iColumn field is the index in ** AggInfo.aCol[] or AggInfo.aFunc[] of information needed to generate ** code for that node. ** ** AggInfo.pGroupBy and AggInfo.aFunc.pExpr point to fields within the ** original Select structure that describes the SELECT statement. These ** fields do not need to be freed when deallocating the AggInfo structure. */ struct AggInfo { u8 directMode; /* Direct rendering mode means take data directly ** from source tables rather than from accumulators */ u8 useSortingIdx; /* In direct mode, reference the sorting index rather ** than the source table */ int sortingIdx; /* Cursor number of the sorting index */ int sortingIdxPTab; /* Cursor number of pseudo-table */ int nSortingColumn; /* Number of columns in the sorting index */ ExprList *pGroupBy; /* The group by clause */ struct AggInfo_col { /* For each column used in source tables */ Table *pTab; /* Source table */ int iTable; /* Cursor number of the source table */ int iColumn; /* Column number within the source table */ int iSorterColumn; /* Column number in the sorting index */ int iMem; /* Memory location that acts as accumulator */ Expr *pExpr; /* The original expression */ } *aCol; int nColumn; /* Number of used entries in aCol[] */ int nAccumulator; /* Number of columns that show through to the output. ** Additional columns are used only as parameters to ** aggregate functions */ struct AggInfo_func { /* For each aggregate function */ Expr *pExpr; /* Expression encoding the function */ FuncDef *pFunc; /* The aggregate function implementation */ int iMem; /* Memory location that acts as accumulator */ int iDistinct; /* Ephemeral table used to enforce DISTINCT */ } *aFunc; int nFunc; /* Number of entries in aFunc[] */ }; /* ** The datatype ynVar is a signed integer, either 16-bit or 32-bit. ** Usually it is 16-bits. But if SQLITE_MAX_VARIABLE_NUMBER is greater ** than 32767 we have to make it 32-bit. 16-bit is preferred because ** it uses less memory in the Expr object, which is a big memory user ** in systems with lots of prepared statements. And few applications ** need more than about 10 or 20 variables. But some extreme users want ** to have prepared statements with over 32767 variables, and for them ** the option is available (at compile-time). */ #if SQLITE_MAX_VARIABLE_NUMBER<=32767 typedef i16 ynVar; #else typedef int ynVar; #endif /* ** Each node of an expression in the parse tree is an instance ** of this structure. ** ** Expr.op is the opcode. The integer parser token codes are reused ** as opcodes here. For example, the parser defines TK_GE to be an integer ** code representing the ">=" operator. This same integer code is reused ** to represent the greater-than-or-equal-to operator in the expression ** tree. ** ** If the expression is an SQL literal (TK_INTEGER, TK_FLOAT, TK_BLOB, ** or TK_STRING), then Expr.token contains the text of the SQL literal. If ** the expression is a variable (TK_VARIABLE), then Expr.token contains the ** variable name. Finally, if the expression is an SQL function (TK_FUNCTION), ** then Expr.token contains the name of the function. ** ** Expr.pRight and Expr.pLeft are the left and right subexpressions of a ** binary operator. Either or both may be NULL. ** ** Expr.x.pList is a list of arguments if the expression is an SQL function, ** a CASE expression or an IN expression of the form " IN (, ...)". ** Expr.x.pSelect is used if the expression is a sub-select or an expression of ** the form " IN (SELECT ...)". If the EP_xIsSelect bit is set in the ** Expr.flags mask, then Expr.x.pSelect is valid. Otherwise, Expr.x.pList is ** valid. ** ** An expression of the form ID or ID.ID refers to a column in a table. ** For such expressions, Expr.op is set to TK_COLUMN and Expr.iTable is ** the integer cursor number of a VDBE cursor pointing to that table and ** Expr.iColumn is the column number for the specific column. If the ** expression is used as a result in an aggregate SELECT, then the ** value is also stored in the Expr.iAgg column in the aggregate so that ** it can be accessed after all aggregates are computed. ** ** If the expression is an unbound variable marker (a question mark ** character '?' in the original SQL) then the Expr.iTable holds the index ** number for that variable. ** ** If the expression is a subquery then Expr.iColumn holds an integer ** register number containing the result of the subquery. If the ** subquery gives a constant result, then iTable is -1. If the subquery ** gives a different answer at different times during statement processing ** then iTable is the address of a subroutine that computes the subquery. ** ** If the Expr is of type OP_Column, and the table it is selecting from ** is a disk table or the "old.*" pseudo-table, then pTab points to the ** corresponding table definition. ** ** ALLOCATION NOTES: ** ** Expr objects can use a lot of memory space in database schema. To ** help reduce memory requirements, sometimes an Expr object will be ** truncated. And to reduce the number of memory allocations, sometimes ** two or more Expr objects will be stored in a single memory allocation, ** together with Expr.zToken strings. ** ** If the EP_Reduced and EP_TokenOnly flags are set when ** an Expr object is truncated. When EP_Reduced is set, then all ** the child Expr objects in the Expr.pLeft and Expr.pRight subtrees ** are contained within the same memory allocation. Note, however, that ** the subtrees in Expr.x.pList or Expr.x.pSelect are always separately ** allocated, regardless of whether or not EP_Reduced is set. */ struct Expr { u8 op; /* Operation performed by this node */ char affinity; /* The affinity of the column or 0 if not a column */ u16 flags; /* Various flags. EP_* See below */ union { char *zToken; /* Token value. Zero terminated and dequoted */ int iValue; /* Non-negative integer value if EP_IntValue */ } u; /* If the EP_TokenOnly flag is set in the Expr.flags mask, then no ** space is allocated for the fields below this point. An attempt to ** access them will result in a segfault or malfunction. *********************************************************************/ Expr *pLeft; /* Left subnode */ Expr *pRight; /* Right subnode */ union { ExprList *pList; /* Function arguments or in " IN ( IN (