1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-09-06 04:02:54 +02:00

Compare commits

...

174 Commits

Author SHA1 Message Date
Dominik Schmidt
5075c9ddbf Fix compilation with libvlc < 2.2.2 2016-02-22 16:10:47 +01:00
Dominik Schmidt
8b3a916aa6 Update VLC for Windows 2016-02-22 16:10:47 +01:00
Dominik Schmidt
cd412add8f Persist muted state accross restarts 2016-02-22 16:10:47 +01:00
Dominik Schmidt
68851843f5 Unmute AudioOutput on start up 2016-02-22 16:10:47 +01:00
Dominik Schmidt
af8811af30 Fix volume on external (un)muting 2016-02-22 16:10:47 +01:00
Dominik Schmidt
fa38ed5e91 Sync muted state as well 2016-02-22 16:10:47 +01:00
Dominik Schmidt
b7ff630808 Sync audio volume if it's changed externally in vlc 2016-02-22 16:10:47 +01:00
Dominik Schmidt
196256d77b Fix startup volume for good 2016-02-22 16:10:47 +01:00
Dominik Schmidt
79cd3a7f0a Revert "Fix startup volume for good"
This reverts commit 86cd7c1be0.
2016-02-22 16:10:20 +01:00
Dominik Schmidt
f0f45673b1 Fix #419: Fix HLS streams on Windows (e.g. Amazon Prime Music) 2016-02-22 16:09:11 +01:00
Dominik Schmidt
86cd7c1be0 Fix startup volume for good 2016-02-22 00:10:17 +01:00
Dominik Schmidt
f74c6ae429 Fix #446: Fix compilation with Qt4 2016-02-21 18:06:14 +01:00
Dominik Schmidt
015b24479c Fix crash in context menu 2016-02-21 11:12:17 +01:00
Jason Herskowitz
8b904cf806 Make folder icon match other context menu icons 2016-02-19 10:04:32 -05:00
Christian Muehlhaeuser
175e498c43 Use folder icon for file manager in context menu. 2016-02-19 04:48:42 +01:00
Jason Herskowitz
65908f9dfa Add folder icon 2016-02-18 22:35:42 -05:00
Christian Muehlhaeuser
3c9deb6389 A few improvements regarding VLC's weird mute state on startup. 2016-02-19 04:11:11 +01:00
Christian Muehlhaeuser
978c1e58de Fixed crash in ContextMenu and moved 'open file manager' action up. 2016-02-19 03:50:17 +01:00
Christian Muehlhaeuser
0722dd7812 Merge pull request #442 from tomahawk-player/open-in-file-manager
Add "Open File in File Manager..." context menu
2016-02-19 03:37:50 +01:00
Dominik Schmidt
ff34afb4e3 Fix #425: add "Open in Folder in File Manager..." context menu to local results 2016-02-18 16:02:16 +01:00
Dominik Schmidt
bd607b6c54 DRY in ContextMenu 2016-02-18 15:40:08 +01:00
Dominik Schmidt
81f7e3f8f4 consistency++: Pass track to linkGenerator instead of title 2016-02-18 14:14:21 +01:00
Christian Muehlhaeuser
798ca62190 When waiting for a query to resolve for playback, accept the earliest online result, preventing further playback delay. 2016-02-18 07:03:17 +01:00
Christian Muehlhaeuser
f2631cda74 Don't wait for resolving to finish if we want to play a track and already got some results. 2016-02-18 06:40:16 +01:00
Dominik Schmidt
4813698a9a Allow creating basic data types from QtConcurrent::run
When QObjects are created from QtConcurrent::run they live in a pooled thread that doesn't handle events/signals/slots properly
2016-02-18 00:52:48 +01:00
Christian Muehlhaeuser
dc80d19a08 Merge pull request #399 from tomahawk-player/things
Various download and branding improvements
2016-02-17 20:32:06 +01:00
Dominik Schmidt
93745b19ef Deployment scripts branding update 2016-02-17 19:37:46 +01:00
Dominik Schmidt
1b7b7ca4b3 Bump snorenotify requirement 2016-02-17 19:37:46 +01:00
Dominik Schmidt
3808e230d4 Make result more thread safe 2016-02-17 19:37:46 +01:00
Dominik Schmidt
0bdb8061e2 Do not set empty mimetype on results 2016-02-17 19:37:46 +01:00
Dominik Schmidt
87278456f4 Add download context menu to all playlist views but database collections 2016-02-17 19:37:46 +01:00
Dominik Schmidt
a5693d6663 Workaround preview widget flicker in column view 2016-02-17 19:37:46 +01:00
Dominik Schmidt
6416b34e8c Don't reset column sort order on collection updated 2016-02-17 19:37:45 +01:00
Dominik Schmidt
f95bd4aab8 Correctly paint hover state in DropDownButton 2016-02-17 19:37:45 +01:00
Dominik Schmidt
996d112ebb Fix DropDownButton font size and boldness 2016-02-17 19:37:45 +01:00
Dominik Schmidt
fc413ac103 Fix download all assert/crash 2016-02-17 19:37:45 +01:00
Dominik Schmidt
2020aad725 Download and branding improvements 2016-02-17 19:37:33 +01:00
Christian Muehlhaeuser
198201aec2 Don't try to reset hover-state if there is none. This speeds up collection browsing. 2016-02-17 12:15:24 +01:00
Christian Muehlhaeuser
6b84630ba1 Reset GridView's hover-state on wheel event. 2016-02-17 12:15:24 +01:00
Christian Muehlhaeuser
205dfca60d Don't use native windows for each widget. This is insane. Thanks Domme for figuring this out. 2016-02-17 01:57:39 +01:00
Dominik Schmidt
ca2da81132 Don't parse artists/albums for non-fulltext queries 2016-02-16 17:11:52 +01:00
Dominik Schmidt
08004ddbd4 Allow returning arrays or maps from resolve/search in JS 2016-02-16 16:47:58 +01:00
Dominik Schmidt
4dadbb5acd Support album and artist results in JSResolvers 2016-02-16 14:53:39 +01:00
Christian Muehlhaeuser
dee504c997 Use correct units for filesize. 2016-02-16 14:40:25 +01:00
Christian Muehlhaeuser
81dae62349 Merge pull request #438 from tomahawk-player/makeresolverdevelopmentgreatagain-wip
Add "Open Account Debugger" menu to ScriptAccounts in SettingsDialog
2016-02-16 02:00:41 +01:00
Dominik Schmidt
3c00f36df6 Add ScriptAccount debugger menu to SettingsDialog/AccountListWidget 2016-02-16 01:59:45 +01:00
Christian Muehlhaeuser
f13ea2f6df Merge pull request #436 from tomahawk-player/makeresolverdevelopmentgreatagain
Show debugger for script errors
2016-02-15 17:04:25 +01:00
Dominik Schmidt
2f4eb75745 Show debugger (WebInspector for JS) when clicking script errors 2016-02-14 13:50:05 +01:00
Dominik Schmidt
942eded60d Make JobStatusItems clickable 2016-02-14 13:41:20 +01:00
Dominik Schmidt
1b2ec05ea3 Move infoplugin wrong thread log message above assert 2016-02-12 22:16:42 +01:00
Dominik Schmidt
3875b10bb4 Don't leak spotify info plugin 2016-02-12 22:13:58 +01:00
Dominik Schmidt
f396f59922 Fix xmpp info plugin 2016-02-12 22:13:39 +01:00
Dominik Schmidt
7271a95942 Add friendlyName to XMPP info plugin 2016-02-12 22:12:54 +01:00
Dominik Schmidt
9737b7a8bc Make InfoPluginPtr a QSharedPointer 2016-02-12 21:37:47 +01:00
Dominik Schmidt
d87f966d9e Merge pull request #429 from midzer/patch1
erasing in an iteration and pass by reference
2016-02-10 12:11:23 +01:00
Dominik Schmidt
cb3ccd5a76 Protect m_formats access with mutex in Result 2016-02-10 00:21:31 +01:00
Dennis
f81858e28a pass by reference 2016-02-10 00:07:09 +01:00
Dennis
8fbc0319ba Erase elements from QStringList safely 2016-02-09 23:59:43 +01:00
Christian Muehlhaeuser
e7d83835d2 What a typo. 2016-02-07 11:39:04 +01:00
Christian Muehlhaeuser
6035889b90 Coding style. 2016-02-06 07:07:53 +01:00
Christian Muehlhaeuser
2bb0113515 Make sure Query's resultSorter provides stable sorting. 2016-02-06 07:07:53 +01:00
Christian Muehlhaeuser
8ee8721dda Merge pull request #423 from cash/patch-1
fixed a typo in PlaydarApi documentation
2016-02-02 00:30:35 +01:00
Cash Costello
293b3b1b4f fixed a typo in PlaydarApi documentation 2016-02-01 18:01:29 -05:00
Dominik Schmidt
ba0cc1a00c Don't assert for linkParser plugins, placeholder for real implementation 2016-02-01 15:18:06 +01:00
Dominik Schmidt
759bfbe63b Merge pull request #388 from theli-ua/nonalphanum
Improve track matching with non-alphanum character differences
2016-01-29 00:58:20 +01:00
Anton Romanov
e3877bf0bf Improve track matching with non-alphanum character differences 2016-01-28 15:52:15 -08:00
Dominik Schmidt
f1cf645f31 Ship D3DCompiler_43.dll to make QtDeclarative work 2016-01-28 22:16:11 +01:00
Dominik Schmidt
62dea3a196 Merge pull request #416 from theli-ua/resolver_sort
only use resolvers to compare if there are resolvers tied to results
2016-01-28 00:44:50 +01:00
Dominik Schmidt
09f68fa2f5 Use v09 synchrotron 2016-01-28 00:34:57 +01:00
Anton Romanov
944827c48c only use resolvers to compare if there are resolvers tied to results 2016-01-27 15:01:45 -08:00
Dominik Schmidt
771b2f5ae1 Add resolve default implementation in Tomahawk.Resolver 2016-01-27 18:03:40 +01:00
Dominik Schmidt
d50c16740a Fix crash in parseResultVariantList for broken track data 2016-01-27 17:31:48 +01:00
Dominik Schmidt
a5968b58e5 Update dll names 2016-01-26 02:11:10 +00:00
Dominik Schmidt
5f20308177 Go back to lowercase installer names on windows 2016-01-26 02:10:53 +00:00
Dominik Schmidt
1710813dc8 Correct indentation in NSIS.template.in 2016-01-25 16:03:27 +01:00
Dominik Schmidt
7fd306ca07 Don't use processEvents and still don't block ui for loading a script collection 2016-01-25 16:03:27 +01:00
Christian Muehlhaeuser
bdaf1ef2f4 Merge pull request #405 from theli-ua/resolver_sort
Sort equal results by resolver's weight
2016-01-20 22:18:26 +01:00
Anton Romanov
c068d8c9e3 Left goes to the left, right to the right 2016-01-20 12:45:55 -08:00
Anton Romanov
1e6b8b3747 Fix sorting order 2016-01-19 16:35:38 -08:00
Anton Romanov
0535d09f08 Sort equal results by resolver's weight 2016-01-19 11:11:11 -08:00
Christian Muehlhaeuser
c93302c563 Revert "Process events while parsing potentially very long script responses"
This reverts commit 1025e77d3e.
2016-01-18 11:04:19 +01:00
Christian Muehlhaeuser
1192c668b0 Too much whitespace in debug output. 2016-01-18 11:03:09 +01:00
Dominik Schmidt
53b27d0ea1 Fix isDown state in ImageButton 2016-01-18 04:55:48 +01:00
Dominik Schmidt
9ebe155313 Clean up PlayableProxyModel::columnCount 2016-01-18 03:19:01 +01:00
Dominik Schmidt
ddf030037e Fix artist colum not being rearrangable 2016-01-18 03:15:00 +01:00
Dominik Schmidt
5d950bd139 Add colum mapping to PlayableProxyModel 2016-01-18 02:50:19 +01:00
Dominik Schmidt
eab1ccee74 Fix #381: Show scrollbars for biography as needed 2016-01-18 00:21:26 +01:00
Dominik Schmidt
1025e77d3e Process events while parsing potentially very long script responses 2016-01-18 00:21:09 +01:00
Christian Muehlhaeuser
53de4319b6 Merge pull request #400 from tomahawk-player/fix-dropdownbutton
Fix DropDownButton in Playlist view
2016-01-15 18:26:57 +01:00
Dominik Schmidt
4eee01d8bf Fix DropDownButton in Playlist view 2016-01-15 18:10:28 +01:00
Dominik Schmidt
5bd69fe09c Automatically link against libc++ if found and compiler is clang 2016-01-15 04:12:12 +01:00
Dominik Schmidt
0585acc869 Merge pull request #389 from TheOneRing/snore_next
Use Snore 0.7
2016-01-11 14:50:35 +01:00
Dominik Schmidt
8c8cd88388 OSX: Try to fix HLS 2016-01-11 14:48:14 +01:00
Dominik Schmidt
9e91ad6549 Reenable a bunch of plugins in OSX nightlies 2016-01-11 01:19:41 +01:00
Hannah von Reth
192627a4ea Update nsis for snore 2016-01-08 15:09:28 +01:00
Dominik Schmidt
ee1f19c34e Merge pull request #337 from TheOneRing/window-preview
[Proof of Concept] Album art as Windows window preview
2016-01-08 01:02:34 +01:00
Hannah von Reth
a41af0ed7f Use snorenotify from git 2016-01-07 20:18:23 +01:00
Dominik Schmidt
feffda8339 Build libtomahawk instead of libTomahawk on OSX 2016-01-07 15:20:58 +01:00
Dominik Schmidt
4b7f8929fd Fix OSX deployment 2016-01-07 15:20:58 +01:00
Dominik Schmidt
acdd0e3b9f Add type (collection/resolver) to resolve / search javascript arguments 2016-01-07 02:14:51 +01:00
Dominik Schmidt
4ba32e0add Add resolvers to diagnostics dialog 2016-01-07 00:43:11 +01:00
Dominik Schmidt
9b1b7be207 Fix lastfm infoplugin for usage without account 2016-01-07 00:42:46 +01:00
Dominik Schmidt
d7e5ce6e4e Put crashreporter topLabel text into Tomahawk instead of libcrashreporter-qt 2016-01-06 05:05:52 +01:00
Dominik Schmidt
684e5f3a58 Update libcrashreporter-qt once more 2016-01-06 04:27:20 +01:00
Dominik Schmidt
3d0584a351 Add comment text edit to crash reporter 2016-01-06 03:42:09 +01:00
Dominik Schmidt
2a43e9aba5 Fix what's new menu on Qt 5 on OS X 2016-01-06 02:16:05 +01:00
Dominik Schmidt
cde395edf0 Differentiate between removable and deletable viewpages to allow showing whatsnew-page after removing it 2016-01-06 01:18:27 +01:00
Dominik Schmidt
5aa875c40a Stop JSResolverHelper on destruction and block any calls 2016-01-05 22:49:31 +01:00
Dominik Schmidt
e4146c98c7 ScriptEngine is already parented, don't track in a smart pointer 2016-01-05 22:49:31 +01:00
Dominik Schmidt
d2dc01e6a3 Update libcrashreporter to show crash id after upload 2016-01-04 22:49:16 +01:00
Jason Herskowitz
8fbd3b9872 Clean up nav icons in mac toolbar 2015-12-23 20:57:17 -05:00
Christian Muehlhaeuser
ac4debbe02 Merge pull request #358 from tomahawk-player/osx-fixes
[OSX] Various build adjustments
2015-12-22 19:12:48 +01:00
Dominik Schmidt
c4197374ca Fixed extracmakemodules instead of remove item hack 2015-12-22 19:02:37 +01:00
Dominik Schmidt
a36287c2ed Fix OSX bundle icon 2015-12-22 19:02:36 +01:00
Dominik Schmidt
054650743d No more Mr. Nice Guy, No more Mr. Cleaaaheean ... except today 2015-12-17 01:25:26 +01:00
Dominik Schmidt
ea55329c67 Add url click handler to push buttons in resolver configs 2015-12-17 01:07:12 +01:00
Dominik Schmidt
5a45bd0882 Small JS fixes 2015-12-15 01:11:04 +01:00
Dominik Schmidt
5395908cbe Merge pull request #339 from tomahawk-player/promisify-all-the-things
[WIP] Promisify JSResolver
2015-12-14 21:51:11 +01:00
Dominik Schmidt
213bf1cd08 Don't leak script jobs 2015-12-14 21:49:45 +01:00
Enno Gottschalk
efc31e6246 Fix Tomahawk.Collection.revision() stub function 2015-12-14 14:23:10 +01:00
Christian Muehlhaeuser
3d511b65e1 Better size for toolbar search-field on OS X. Not sure why the auto-sizing doesn't work as expected. 2015-12-13 10:18:44 +01:00
Enno Gottschalk
5a34bae757 Added stub Tomahawk.Collection.revision() function in tomahawk.js 2015-12-11 03:19:35 +01:00
Dominik Schmidt
0223fd5992 Ship hatchet account again. why was this disabled?! 2015-12-10 22:51:19 +01:00
Dominik Schmidt
66a2d3cb88 Fix snorenotify paths 2015-12-10 22:50:57 +01:00
Dominik Schmidt
75427a7af2 Fixup libs that were fixed up but aren't correct anymore (because they reference ../lib while we use ../Frameworks/ 2015-12-10 02:46:20 +01:00
Dominik Schmidt
92c66d7252 Fix snorenotify plugin lookup path 2015-12-10 02:45:47 +01:00
Dominik Schmidt
2afc578d7a Fix qt5 plugin lookup path 2015-12-10 02:45:36 +01:00
Dominik Schmidt
0791937032 Fixup last commit 2015-12-09 13:44:11 +01:00
Dominik Schmidt
45c96e7367 Update VLC plugin paths 2015-12-09 13:44:11 +01:00
Dominik Schmidt
4d351168da Don't compile 64x64 icon into OSX iconset 2015-12-09 13:44:11 +01:00
Dominik Schmidt
86f08b307e Fix typo 2015-12-09 13:44:11 +01:00
Dominik Schmidt
99fe5c8e8e Look for keg-only libvlc 2015-12-09 13:44:11 +01:00
Dominik Schmidt
daf3034d99 Only look for and link against libvlc, not libvlccore 2015-12-09 13:44:11 +01:00
Dominik Schmidt
e8d73fe0cc Merge pull request #317 from TheOneRing/snorenotify-0.6
Snorenotify 0.6
2015-12-09 13:20:42 +01:00
Christian Muehlhaeuser
de78768be6 Delay AudioControls repaint until we've updated all elements. Looks smoother. 2015-12-05 09:38:26 +01:00
Dominik Schmidt
d0c1d83f90 Remove more comp hacks 2015-11-21 16:36:10 +01:00
Hannah von Reth
ca379868dc Use qApp->applicationName() for default text 2015-11-21 01:18:09 +01:00
Hannah von Reth
cf7194407b Center text 2015-11-21 01:15:04 +01:00
Hannah von Reth
365a021a29 Remove linebreak 2015-11-21 01:04:37 +01:00
Hannah von Reth
c39750d814 Fix text 2015-11-21 01:01:36 +01:00
Hannah von Reth
6d12f03023 Outline elements and change font colour 2015-11-21 00:55:22 +01:00
Dominik Schmidt
03935f26a9 Use ordinary wrapper function for testConfig 2015-11-21 00:45:17 +01:00
Dominik Schmidt
e697303743 Properly log errors to javascript console 2015-11-21 00:45:03 +01:00
Hannah von Reth
e4f55da2b2 Update preview design 2015-11-21 00:18:49 +01:00
Dominik Schmidt
576c91eb19 Set *pretty* collection name as result friendly source 2015-11-21 00:13:36 +01:00
Dominik Schmidt
6823b57823 Show friendlySource in alternate sources box tooltips 2015-11-21 00:13:36 +01:00
Dominik Schmidt
88099eae0e Don't use collectionId from resolvers anymore 2015-11-21 00:13:35 +01:00
Dominik Schmidt
8679713dea Remove more leftovers from the old collection hack 2015-11-21 00:13:35 +01:00
Dominik Schmidt
a589e4f688 Expect javascript resolvers to return "tracks" instead of "results" 2015-11-21 00:13:31 +01:00
Hannah von Reth
f3021e4d71 Change preview look and update less often. 2015-11-20 22:36:52 +01:00
Hannah von Reth
c577073f57 Hacked together an album art preview for the thumb bar and alt+tab 2015-11-20 22:36:52 +01:00
Hannah von Reth
e746b4c3e9 Remove dead code 2015-11-20 22:36:52 +01:00
Dominik Schmidt
16946592b7 Allow script jobs to return object members directly if they are not functions 2015-11-20 22:15:26 +01:00
Dominik Schmidt
f3c8038c42 Remove legacy hack 2015-11-20 22:15:26 +01:00
Dominik Schmidt
4637f3ed23 Make collections able to resolve queries 2015-11-20 22:15:24 +01:00
Dominik Schmidt
dc32f7eeb1 Remove legacy hacks and add useful error 2015-11-19 13:38:23 +01:00
Dominik Schmidt
e997a194a4 Add nativeScriptJob errors 2015-11-19 06:20:28 +01:00
Dominik Schmidt
e8aa2e6de9 Make params for nativeScriptJobs optional 2015-11-19 06:20:09 +01:00
Dominik Schmidt
29aa9546a8 Allow instantaneous returns for nativeScriptJobs 2015-11-19 06:19:53 +01:00
Dominik Schmidt
17d71b413f Implement Tomahawk.ajax native implementation on behalf of NativeScriptJobs 2015-11-19 05:39:07 +01:00
Dominik Schmidt
3db8e91b51 Bye bye syncRequest 2015-11-19 05:36:05 +01:00
Dominik Schmidt
1b4efa8f4a Bye bye callOnResolver 2015-11-19 01:44:54 +01:00
Dominik Schmidt
90d6f0d4e5 Set collectionId on results from Tomahawk.Collection 2015-11-19 01:24:07 +01:00
Dominik Schmidt
dde7db616a Use getStreamUrl on ResultProviders instead of awkward custom iodevicefactories 2015-11-19 01:23:38 +01:00
Dominik Schmidt
30789bcb9b Add getStreamUrl translation step in AudioEngine 2015-11-17 03:12:12 +01:00
Dominik Schmidt
64f71fe453 Forward declare ResultProvider in Result 2015-11-17 02:50:00 +01:00
Dominik Schmidt
c1eadce374 Move lookupUrl stuff from JSResolverHelper to JSResolver, to be moved to own plugin type 2015-11-16 18:19:51 +01:00
Enno Gottschalk
73a1160ce3 Added empty default implementation of testConfig() to Tomahawk.Resolver in tomahawk.js 2015-11-13 16:37:56 +01:00
Dominik Schmidt
846699c03c Don't wait for timeouts on resolver errors 2015-11-13 14:12:12 +01:00
Dominik Schmidt
35d8945975 Port JSResolver::resolve() 2015-11-13 13:20:52 +01:00
Dominik Schmidt
186d98b263 Update shipped dependencies in NSIS 2015-11-05 16:29:35 +01:00
Dominik Schmidt
0b7d3846e5 There's no such thing MING 2015-11-05 16:28:00 +01:00
Hannah von Reth
84e0754bd7 Install snoresettings as "Notification Settings".
This enables the user to change the notification backend
and to configure the banckend.
Opening the shortcut will only work after Tomahawk was started
for a first time to initialize the settings.
An alternative would be to call snoresettings.exe from inside of
Tomahawk or to use the QWidget provided by libsnore.
2015-11-05 13:38:42 +01:00
Hannah von Reth
c838af6d17 Update to Snorenotify 0.6 2015-11-05 12:54:20 +01:00
155 changed files with 2966 additions and 1841 deletions

View File

@@ -307,11 +307,10 @@ else()
message(STATUS "${CMAKE_CXX_COMPILER} does not support C++11, please use a
different compiler")
endif()
if(LIBCPP_FOUND AND APPLE)
if(("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR APPLE) AND LIBCPP_FOUND)
tomahawk_add_cxx_flags( "-stdlib=libc++" )
endif()
macro_optional_find_package(Echonest 2.3.0)
macro_log_feature(ECHONEST_FOUND "Echonest" "Qt library for communicating with The Echo Nest" "http://projects.kde.org/libechonest" TRUE "" "libechonest 2.3.0 is needed for dynamic playlists and the infosystem")
@@ -404,12 +403,8 @@ if( WIN32 )
endif( WIN32 )
if( WIN32 OR APPLE )
if( TOMAHAWK_QT5 )
macro_optional_find_package(LibsnoreQt5 QUIET)
else()
macro_optional_find_package(Libsnore QUIET)
endif()
macro_log_feature(LIBSNORE_FOUND "Libsnore" "Library for notifications" "https://github.com/TheOneRing/Snorenotify" FALSE "" "")
macro_optional_find_package(LibsnoreQt5 0.6.0 QUIET)
macro_log_feature(LibsnoreQt5_FOUND "Libsnore" "Library for notifications" "https://projects.kde.org/projects/playground/libs/snorenotify" FALSE "" "")
endif()
find_package(LIBVLC REQUIRED 2.1.0)

View File

@@ -6,19 +6,16 @@ find_path(LIBVLC_INCLUDE_DIR vlc/vlc.h
HINTS
${PC_LIBVLC_INCLUDEDIR}
${PC_LIBVLC_INCLUDE_DIRS}
/usr/local/opt/vlc/include
)
find_library(LIBVLC_LIBRARY NAMES vlc libvlc
HINTS
${PC_LIBVLC_LIBDIR}
${PC_LIBVLC_LIBRARY_DIRS}
/usr/local/opt/vlc/lib
)
find_library(LIBVLCCORE_LIBRARY NAMES vlccore libvlccore
HINTS
${PC_LIBVLC_LIBDIR}
${PC_LIBVLC_LIBRARY_DIRS}
)
set(LIBVLC_VERSION ${PC_LIBVLC_VERSION})
@@ -32,8 +29,6 @@ int main(int argc, char *argv[]) {
HAVE_VLC_ALBUMARTIST)
find_package_handle_standard_args(LibVLC
REQUIRED_VARS LIBVLC_LIBRARY LIBVLCCORE_LIBRARY LIBVLC_INCLUDE_DIR
REQUIRED_VARS LIBVLC_LIBRARY LIBVLC_INCLUDE_DIR
VERSION_VAR LIBVLC_VERSION
)

View File

@@ -15,21 +15,24 @@
;-----------------------------------------------------------------------------
; Some paths.
;-----------------------------------------------------------------------------
!ifndef MING_PATH
!define MING_PATH "/usr/i686-w64-mingw32/sys-root/mingw"
!ifndef MINGW_ROOT
!define MINGW_ROOT "/usr/i686-w64-mingw32/sys-root/mingw"
!endif
!define APPLICATION_NAME "Tomahawk"
!define TARGET_NAME "tomahawk"
!define APPLICATION_NAME "@CPACK_PACKAGE_NAME@"
!define TARGET_NAME "@CPACK_PACKAGE_TARGET_NAME@"
;define app id needed for Windows 8 notifications
!define AppUserModelId @TOMAHAWK_APPLICATION_PACKAGE_NAME@
!define AppUserModelId "@TOMAHAWK_APPLICATION_PACKAGE_NAME@"
!define MINGW_BIN "${MINGW_ROOT}/bin"
!define MINGW_LIB "${MINGW_ROOT}/lib"
!define MINGW_SHARE "${MINGW_ROOT}/share"
!define MING_BIN "${MING_PATH}/bin"
!define MING_LIB "${MING_PATH}/lib"
!define BUILD_PATH "@CMAKE_BINARY_DIR@"
!define SOURCE_PATH "@CMAKE_SOURCE_DIR@"
!define QT_DLL_PATH "${MING_BIN}"
!define SQLITE_DLL_PATH "${MING_LIB}/qt5/plugins/sqldrivers"
!define IMAGEFORMATS_DLL_PATH "${MING_LIB}/qt5/plugins/imageformats"
!define QT_DLL_PATH "${MINGW_BIN}"
!define QT_QML_PATH "${MINGW_SHARE}/qt5/qml"
!define SQLITE_DLL_PATH "${MINGW_LIB}/qt5/plugins/sqldrivers"
!define IMAGEFORMATS_DLL_PATH "${MINGW_LIB}/qt5/plugins/imageformats"
; We use official release plugins
; mingw32-vlc from obs misses a lot and has even broken ones probably
@@ -330,7 +333,7 @@ Section "${APPLICATION_NAME}" SEC_TOMAHAWK_PLAYER
File "${QT_DLL_PATH}\Qt5WebKitWidgets.dll"
File "${QT_DLL_PATH}\Qt5Multimedia.dll"
File "${QT_DLL_PATH}\Qt5MultimediaWidgets.dll"
File "${QT_DLL_PATH}\Qt5Positioning.dll"
;Qt deps
File "${QT_DLL_PATH}\libpcre16-0.dll"
@@ -339,96 +342,112 @@ Section "${APPLICATION_NAME}" SEC_TOMAHAWK_PLAYER
File "${QT_DLL_PATH}\libEGL.dll"
File "${QT_DLL_PATH}\libGLESv2.dll"
File "${QT_DLL_PATH}\libwebp-5.dll"
File "${QT_DLL_PATH}\icuuc56.dll"
File "${QT_DLL_PATH}\icudata56.dll"
File "${QT_DLL_PATH}\icui18n56.dll"
;Boost fnord
File "${QT_DLL_PATH}\icuuc53.dll"
File "${QT_DLL_PATH}\icudata53.dll"
File "${QT_DLL_PATH}\icui18n53.dll"
;SQLite driver
SetOutPath "$INSTDIR\sqldrivers"
File "${SQLITE_DLL_PATH}\qsqlite.dll"
SetOutPath "$INSTDIR"
File "${MINGW_BIN}\libsqlite3-0.dll"
;SQLite driver
SetOutPath "$INSTDIR\sqldrivers"
File "${SQLITE_DLL_PATH}\qsqlite.dll"
SetOutPath "$INSTDIR"
File "${MING_BIN}\libsqlite3-0.dll"
;Qt platform plugins
SetOutPath "$INSTDIR\platforms"
File "${MINGW_LIB}/qt5/plugins/platforms/qwindows.dll"
SetOutPath "$INSTDIR"
;Qt platform plugins
SetOutPath "$INSTDIR\platforms"
File "${MING_LIB}/qt5/plugins/platforms/qwindows.dll"
SetOutPath "$INSTDIR"
;Image plugins
SetOutPath "$INSTDIR\imageformats"
File "${IMAGEFORMATS_DLL_PATH}\qgif.dll"
File "${IMAGEFORMATS_DLL_PATH}\qjpeg.dll"
File "${IMAGEFORMATS_DLL_PATH}\qsvg.dll"
SetOutPath "$INSTDIR"
;Image plugins
SetOutPath "$INSTDIR\imageformats"
File "${IMAGEFORMATS_DLL_PATH}\qgif.dll"
File "${IMAGEFORMATS_DLL_PATH}\qjpeg.dll"
File "${IMAGEFORMATS_DLL_PATH}\qsvg.dll"
SetOutPath "$INSTDIR"
;Qt qml plugins
SetOutPath "$INSTDIR\QtQuick.2"
File /r /x *.debug "${QT_QML_PATH}\QtQuick.2\*"
SetOutPath "$INSTDIR\QtQuick\Window.2"
File /r /x *.debug "${QT_QML_PATH}\QtQuick\Window.2\*"
SetOutPath "$INSTDIR"
;Cygwin/c++ stuff
File "${MING_BIN}\libgcc_s_sjlj-1.dll"
File "${MING_BIN}\libstdc++-6.dll"
;Cygwin/c++ stuff
File "${MINGW_BIN}\libgcc_s_sjlj-1.dll"
File "${MINGW_BIN}\libstdc++-6.dll"
;VLC
File "${VLC_BIN}\libvlc.dll"
File "${VLC_BIN}\libvlccore.dll"
SetOutPath "$INSTDIR\plugins"
File /r "${VLC_PLUGIN_PATH}\*.dll"
SetOutPath "$INSTDIR"
;VLC
File "${VLC_BIN}\libvlc.dll"
File "${VLC_BIN}\libvlccore.dll"
SetOutPath "$INSTDIR\plugins"
File /r "${VLC_PLUGIN_PATH}\*.dll"
SetOutPath "$INSTDIR"
; Other
File "${MING_BIN}\libtag.dll"
File "${MING_BIN}\libpng16-16.dll"
File "${MING_BIN}\libjpeg-8.dll"
File "${MING_BIN}\zlib1.dll"
; Other
File "${MINGW_BIN}\libtag.dll"
File "${MINGW_BIN}\libpng16-16.dll"
File "${MINGW_BIN}\libjpeg-8.dll"
File "${MINGW_BIN}\zlib1.dll"
File "${MINGW_BIN}\libfreetype-6.dll"
File "${MINGW_BIN}\libglib-2.0-0.dll"
File "${MINGW_BIN}\libharfbuzz-0.dll"
File "${MING_BIN}\libechonest5.dll"
File "${MING_BIN}\liblastfm5.dll"
File "${MING_BIN}\libquazip5.dll"
File "${MING_BIN}\libqt5keychain.dll"
; ANGLE
File "${MINGW_BIN}\D3DCompiler_43.dll"
; GnuTLS
File "${MING_BIN}\libgnutls-28.dll"
File "${MING_BIN}\libtasn1-6.dll"
File "${MING_BIN}\libgmp-10.dll"
File "${MING_BIN}\libhogweed-2-4.dll"
File "${MING_BIN}\libintl-8.dll"
File "${MING_BIN}\libnettle-4-6.dll"
File "${MING_BIN}\libp11-kit-0.dll"
File "${MING_BIN}\libffi-6.dll"
File "${MINGW_BIN}\libechonest5.dll"
File "${MINGW_BIN}\liblastfm5.dll"
File "${MINGW_BIN}\libquazip5.dll"
File "${MINGW_BIN}\libqt5keychain.dll"
; Snorenotify
File "${MING_BIN}\SnoreToast.exe"
File "${MING_BIN}\libsnore-qt5.dll"
File "${MING_LIB}\plugins\libsnore-qt5\libsnore_backend_growl.dll"
File "${MING_LIB}\plugins\libsnore-qt5\libsnore_backend_snarl.dll"
File "${MING_LIB}\plugins\libsnore-qt5\libsnore_backend_snore.dll"
File "${MING_LIB}\plugins\libsnore-qt5\libsnore_backend_snoretoast.dll"
; GnuTLS
File "${MINGW_BIN}\libgnutls-28.dll"
File "${MINGW_BIN}\libtasn1-6.dll"
File "${MINGW_BIN}\libgmp-10.dll"
File "${MINGW_BIN}\libhogweed-4-1.dll"
File "${MINGW_BIN}\libintl-8.dll"
File "${MINGW_BIN}\libnettle-6-1.dll"
File "${MINGW_BIN}\libp11-kit-0.dll"
File "${MINGW_BIN}\libffi-6.dll"
; Snoregrowl
File "${MING_BIN}\libsnoregrowl++.dll"
File "${MING_BIN}\libsnoregrowl.dll"
; Snorenotify
File "${MINGW_BIN}\SnoreToast.exe"
File "${MINGW_BIN}\libsnore-qt5.dll"
File "${MINGW_BIN}\libsnoresettings-qt5.dll"
File "${MINGW_BIN}\snoresettings.exe"
File "${MINGW_LIB}\plugins\libsnore-qt5\libsnore_backend_growl.dll"
File "${MINGW_LIB}\plugins\libsnore-qt5\libsnore_settings_backend_growl.dll"
File "${MINGW_LIB}\plugins\libsnore-qt5\libsnore_backend_snarl.dll"
File "${MINGW_LIB}\plugins\libsnore-qt5\libsnore_settings_backend_snarl.dll"
File "${MINGW_LIB}\plugins\libsnore-qt5\libsnore_backend_snore.dll"
File "${MINGW_LIB}\plugins\libsnore-qt5\libsnore_settings_backend_snore.dll"
File "${MINGW_LIB}\plugins\libsnore-qt5\libsnore_backend_windowstoast.dll"
; Snoregrowl
File "${MINGW_BIN}\libsnoregrowl++.dll"
File "${MINGW_BIN}\libsnoregrowl.dll"
; Jabber
File "${MING_BIN}\libjreen-qt5.dll"
File "${MING_BIN}\libidn-11.dll"
File "${MING_BIN}\libgsasl-7.dll"
File "${MING_BIN}\libqca-qt5.dll"
SetOutPath "$INSTDIR\crypto"
File "${MING_LIB}\qca-qt5\crypto\libqca-ossl.dll"
SetOutPath "$INSTDIR"
File "${MING_BIN}\libssl-10.dll"
File "${MING_BIN}\libcrypto-10.dll"
File "${MINGW_BIN}\libjreen-qt5.dll"
File "${MINGW_BIN}\libidn-11.dll"
File "${MINGW_BIN}\libgsasl-7.dll"
File "${MINGW_BIN}\libqca-qt5.dll"
SetOutPath "$INSTDIR\crypto"
File "${MINGW_LIB}\qca-qt5\crypto\libqca-ossl.dll"
SetOutPath "$INSTDIR"
File "${MINGW_BIN}\libssl-10.dll"
File "${MINGW_BIN}\libcrypto-10.dll"
; LucenePlusPlus
File "${MING_BIN}\liblucene++.dll"
File "${MING_BIN}\libboost_system-mt.dll"
File "${MING_BIN}\libboost_filesystem-mt.dll"
File "${MING_BIN}\libboost_iostreams-mt.dll"
File "${MING_BIN}\libboost_regex-mt.dll"
File "${MING_BIN}\libboost_thread-mt.dll"
File "${MING_BIN}\libbz2-1.dll"
; LucenePlusPlus
File "${MINGW_BIN}\liblucene++.dll"
File "${MINGW_BIN}\libboost_system-mt.dll"
File "${MINGW_BIN}\libboost_filesystem-mt.dll"
File "${MINGW_BIN}\libboost_iostreams-mt.dll"
File "${MINGW_BIN}\libboost_regex-mt.dll"
File "${MINGW_BIN}\libboost_thread-mt.dll"
File "${MINGW_BIN}\libbz2-1.dll"
File "${MING_BIN}\libqtsparkle-qt5.dll"
File "${MING_BIN}\libKF5Attica.dll"
File "${MINGW_BIN}\libqtsparkle-qt5.dll"
File "${MINGW_BIN}\libKF5Attica.dll"
SectionEnd
SectionGroup "Shortcuts"
@@ -443,7 +462,7 @@ SectionGroup "Shortcuts"
RMDir /r "$SMPROGRAMS\${APPLICATION_NAME}"
CreateDirectory "$SMPROGRAMS\${APPLICATION_NAME}"
CreateShortCut "$SMPROGRAMS\${APPLICATION_NAME}\LICENSE.lnk" "$INSTDIR\LICENSE.txt"
CreateShortCut "$SMPROGRAMS\${APPLICATION_NAME}\${APPLICATION_NAME}.lnk" "$INSTDIR\${APPLICATION_NAME}.exe"
CreateShortCut "$SMPROGRAMS\${APPLICATION_NAME}\Notification Settings.lnk" "$INSTDIR\snoresettings.exe" "-a ${APPLICATION_NAME}"
!insertmacro SnoreShortcut "$SMPROGRAMS\${APPLICATION_NAME}\${APPLICATION_NAME}.lnk" "$INSTDIR\${APPLICATION_NAME}.exe" "${AppUserModelId}"
CreateShortCut "$SMPROGRAMS\${APPLICATION_NAME}\Release notes.lnk" "$INSTDIR\NOTES.txt"
CreateShortCut "$SMPROGRAMS\${APPLICATION_NAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe"

View File

@@ -2,7 +2,7 @@ INCLUDE( InstallRequiredSystemLibraries )
SET( CPACK_PACKAGE_CONTACT "Dominik Schmidt <domme@tomahawk-player.org>" )
SET( CPACK_PACKAGE_FILE_NAME "${TOMAHAWK_APPLICATION_NAME}-${TOMAHAWK_VERSION}" ) # Package file name without extension. Also a directory of installer cmake-2.5.0-Linux-i686
SET( CPACK_PACKAGE_FILE_NAME "${TOMAHAWK_TARGET_NAME}-${TOMAHAWK_VERSION}" ) # Package file name without extension. Also a directory of installer cmake-2.5.0-Linux-i686
# CPACK_GENERATOR CPack generator to be used STGZ;TGZ;TZ
# CPACK_INCLUDE_TOPLEVEL_DIRECTORY Controls whether CPack adds a top-level directory, usually of the form ProjectName-Version-OS, to the top of package tree. 0 to disable, 1 to enable
@@ -13,7 +13,9 @@ SET( CPACK_PACKAGE_DESCRIPTION_SUMMARY ${TOMAHAWK_DESCRIPTION_SUMMARY} ) # Des
SET( CPACK_PACKAGE_INSTALL_DIRECTORY ${TOMAHAWK_APPLICATION_NAME} ) # Installation directory on the target system -> C:\Program Files\fellody
SET( CPACK_PACKAGE_INSTALL_REGISTRY_KEY ${TOMAHAWK_APPLICATION_NAME} ) # Registry key used when installing this project CMake 2.5.0
SET( CPACK_PACKAGE_NAME ${TOMAHAWK_APPLICATION_NAME} ) # Package name, defaults to the project name
SET( CPACK_PACKAGE_TARGET_NAME ${TOMAHAWK_TARGET_NAME} ) # Used to build library and executable names
SET( CPACK_PACKAGE_VENDOR ${TOMAHAWK_ORGANIZATION_NAME} ) # Package vendor name
SET( TOMAHAWK_APPLICATION_PACKAGE_NAME ${TOMAHAWK_APPLICATION_PACKAGE_NAME} )
SET( CPACK_PACKAGE_VERSION_MAJOR ${TOMAHAWK_VERSION_MAJOR} )
SET( CPACK_PACKAGE_VERSION_MINOR ${TOMAHAWK_VERSION_MINOR} )
SET( CPACK_PACKAGE_VERSION_PATCH ${TOMAHAWK_VERSION_PATCH} )

View File

@@ -5,23 +5,23 @@
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>Tomahawk</string>
<string>@TOMAHAWK_APPLICATION_NAME@</string>
<key>CFBundleIdentifier</key>
<string>org.tomahawk-player.Tomahawk</string>
<string>@TOMAHAWK_APPLICATION_PACKAGE_NAME@</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleVersion</key>
<string>TOMAHAWK_VERSION</string>
<string>@TOMAHAWK_VERSION@</string>
<key>CFBundleShortVersionString</key>
<string>TOMAHAWK_VERSION</string>
<string>@TOMAHAWK_VERSION@</string>
<key>CFBundleSignature</key>
<string>tomahawk</string>
<string>@TOMAHAWK_BASE_TARGET_NAME@</string>
<key>CFBundleIconFile</key>
<string>Tomahawk.icns</string>
<string>@TOMAHAWK_APPLICATION_NAME@.icns</string>
<key>CFBundleName</key>
<string>Tomahawk</string>
<string>@TOMAHAWK_APPLICATION_NAME@</string>
<key>LSMinimumSystemVersion</key>
<string>10.7.0</string>
<key>NSPrincipalClass</key>
@@ -29,7 +29,7 @@
<key>NSHighResolutionCapable</key>
<true />
<key>SUFeedURL</key>
<string>http://download.tomahawk-player.org/sparkle/update.php</string>
<string>@TOMAHAWK_SPARKLE_UPDATE_URL@</string>
<key>SUPublicDSAKeyFile</key>
<string>sparkle_pub.pem</string>
<key>SUEnableSystemProfiling</key>

View File

@@ -4,8 +4,9 @@
#
################################################################################
set -e
TARGET_NAME="Tomahawk"
set -e
function header {
echo -e "\033[0;34m==>\033[0;0;1m $1 \033[0;0m"
@@ -31,30 +32,28 @@ CERT_SIGNER=$2
################################################################################
header "Fixing and copying libraries"
$ROOT/../admin/mac/macdeploy.py Tomahawk.app quiet
$ROOT/../admin/mac/macdeploy.py "${TARGET_NAME}.app" quiet
cd Tomahawk.app
cd "${TARGET_NAME}.app"
cp $ROOT/../admin/mac/qt.conf Contents/Resources/qt.conf
# header "Copying Sparkle framework"
# cp -R /Library/Frameworks/Sparkle.framework Contents/Frameworks
header "Creating DMG"
cd ..
header "Fixing fonts"
mkdir "${ROOT}/${TARGET_NAME}.app/Contents/Resources/Fonts"
cp -R $ROOT/../data/fonts/*.ttf "${ROOT}/${TARGET_NAME}.app/Contents/Resources/Fonts"
header "Signing bundle"
# codesign -s "Developer ID Application: $CERT_SIGNER" -f -v ./Tomahawk.app
cd ..
if [ -f ~/sign_step.sh ];
then
~/sign_step.sh "$CERT_SIGNER" "Tomahawk.app" || true
~/sign_step.sh "$CERT_SIGNER" "${TARGET_NAME}.app" || true
fi
$ROOT/../admin/mac/create-dmg.sh Tomahawk.app
mv Tomahawk.dmg Tomahawk-$VERSION.dmg
header "Creating DMG"
$ROOT/../admin/mac/create-dmg.sh "${TARGET_NAME}.app"
mv "${TARGET_NAME}.dmg" "${TARGET_NAME}-$VERSION.dmg"
header "Creating signed Sparkle update"
$ROOT/../admin/mac/sign_bundle.rb $VERSION ~/tomahawk_sparkle_privkey.pem
# $ROOT/../admin/mac/sign_bundle.rb "${TARGET_NAME}" $VERSION ~/tomahawk_sparkle_privkey.pem
header "Done!"

View File

@@ -43,7 +43,7 @@ ln -s /Applications "$TMP/Applications"
cp -R "$IN" "$TMP"
# create
hdiutil makehybrid -hfs -hfs-volume-name Tomahawk -hfs-openfolder "$TMP" "$TMP" -o tmp.dmg
hdiutil makehybrid -hfs -hfs-volume-name "$NAME" -hfs-openfolder "$TMP" "$TMP" -o tmp.dmg
hdiutil convert -format UDZO -imagekey zlib-level=9 tmp.dmg -o "$OUT"
# cleanup

View File

@@ -22,171 +22,170 @@ import commands
import sys
import glob
TARGET_NAME="tomahawk"
FRAMEWORK_SEARCH_PATH=[
'/Library/Frameworks',
os.path.join(os.environ['HOME'], 'Library/Frameworks')
]
LIBRARY_SEARCH_PATH=['/usr/local/lib', '/usr/local/Cellar/gettext/0.19.2/lib', '.']
LIBRARY_SEARCH_PATH=['/usr/local/lib', '/usr/local/opt/vlc/lib', '/usr/local/Cellar/gettext/0.19.2/lib', '.']
VLC_PLUGINS=[
'access/libattachment_plugin.dylib',
#'access/libaccess_avio_plugin.dylib',
#'access/libaccess_fake_plugin.dylib',
'access/libftp_plugin.dylib',
'access/libhttp_plugin.dylib',
'access/libimem_plugin.dylib',
#'access/libaccess_mmap_plugin.dylib',
'access/libaccess_mms_plugin.dylib',
'access/libaccess_realrtsp_plugin.dylib',
'access/libtcp_plugin.dylib',
'access/libudp_plugin.dylib',
'access/libcdda_plugin.dylib',
'access/libfilesystem_plugin.dylib',
'access/libqtcapture_plugin.dylib',
'access/librtp_plugin.dylib',
'access/libzip_plugin.dylib',
'access_output/libaccess_output_dummy_plugin.dylib',
'access_output/libaccess_output_file_plugin.dylib',
'access_output/libaccess_output_http_plugin.dylib',
'access_output/libaccess_output_shout_plugin.dylib',
'access_output/libaccess_output_udp_plugin.dylib',
'audio_filter/liba52tofloat32_plugin.dylib',
'audio_filter/liba52tospdif_plugin.dylib',
'audio_filter/libaudio_format_plugin.dylib',
'audio_filter/libaudiobargraph_a_plugin.dylib',
'audio_filter/libchorus_flanger_plugin.dylib',
'libattachment_plugin.dylib',
#'libaccess_avio_plugin.dylib',
#'libaccess_fake_plugin.dylib',
'libftp_plugin.dylib',
'libhttp_plugin.dylib',
'libhttplive_plugin.dylib',
'libimem_plugin.dylib',
#'libaccess_mmap_plugin.dylib',
'libaccess_mms_plugin.dylib',
'libaccess_realrtsp_plugin.dylib',
'libtcp_plugin.dylib',
'libudp_plugin.dylib',
'libcdda_plugin.dylib',
'libfilesystem_plugin.dylib',
'libqtcapture_plugin.dylib',
'librtp_plugin.dylib',
'libzip_plugin.dylib',
'liba52tofloat32_plugin.dylib',
'liba52tospdif_plugin.dylib',
'libaudio_format_plugin.dylib',
'libaudiobargraph_a_plugin.dylib',
'libchorus_flanger_plugin.dylib',
#'libconverter_fixed_plugin.dylib',
'audio_filter/libdolby_surround_decoder_plugin.dylib',
'audio_filter/libdtstofloat32_plugin.dylib',
'audio_filter/libdtstospdif_plugin.dylib',
'audio_filter/libequalizer_plugin.dylib',
'audio_filter/libheadphone_channel_mixer_plugin.dylib',
'audio_filter/libmono_plugin.dylib',
'audio_filter/libmpgatofixed32_plugin.dylib',
'audio_filter/libnormvol_plugin.dylib',
'audio_filter/libparam_eq_plugin.dylib',
'audio_filter/libscaletempo_plugin.dylib',
'audio_filter/libsimple_channel_mixer_plugin.dylib',
'audio_filter/libspatializer_plugin.dylib',
'audio_filter/libtrivial_channel_mixer_plugin.dylib',
'audio_filter/libugly_resampler_plugin.dylib',
'audio_mixer/libfloat_mixer_plugin.dylib',
'libdolby_surround_decoder_plugin.dylib',
'libdtstofloat32_plugin.dylib',
'libdtstospdif_plugin.dylib',
'libequalizer_plugin.dylib',
'libheadphone_channel_mixer_plugin.dylib',
'libmono_plugin.dylib',
'libmpgatofixed32_plugin.dylib',
'libnormvol_plugin.dylib',
'libparam_eq_plugin.dylib',
'libscaletempo_plugin.dylib',
'libsimple_channel_mixer_plugin.dylib',
'libspatializer_plugin.dylib',
'libtrivial_channel_mixer_plugin.dylib',
'libugly_resampler_plugin.dylib',
'libfloat_mixer_plugin.dylib',
#'libspdif_mixer_plugin.dylib',
#'libtrivial_mixer_plugin.dylib',
#'libaout_file_plugin.dylib',
'audio_output/libauhal_plugin.dylib',
'codec/liba52_plugin.dylib',
'codec/libadpcm_plugin.dylib',
'codec/libaes3_plugin.dylib',
'codec/libaraw_plugin.dylib',
'codec/libavcodec_plugin.dylib',
'codec/libcc_plugin.dylib',
'codec/libcdg_plugin.dylib',
'codec/libdts_plugin.dylib',
'codec/libfaad_plugin.dylib',
'libauhal_plugin.dylib',
'liba52_plugin.dylib',
'libadpcm_plugin.dylib',
'libaes3_plugin.dylib',
'libaraw_plugin.dylib',
'libavcodec_plugin.dylib',
'libcc_plugin.dylib',
'libcdg_plugin.dylib',
'libdts_plugin.dylib',
'libfaad_plugin.dylib',
#'libfake_plugin.dylib',
'codec/libflac_plugin.dylib',
'libflac_plugin.dylib',
#'libfluidsynth_plugin.dylib',
#'libinvmem_plugin.dylib',
'codec/liblpcm_plugin.dylib',
'codec/libmpeg_audio_plugin.dylib',
'codec/libpng_plugin.dylib',
'codec/librawvideo_plugin.dylib',
'codec/libspeex_plugin.dylib',
'codec/libspudec_plugin.dylib',
'codec/libtheora_plugin.dylib',
'codec/libtwolame_plugin.dylib',
'codec/libvorbis_plugin.dylib',
#'control/libgestures_plugin.dylib',
'liblpcm_plugin.dylib',
'libmpeg_audio_plugin.dylib',
'libpng_plugin.dylib',
'librawvideo_plugin.dylib',
'libspeex_plugin.dylib',
'libspudec_plugin.dylib',
'libtheora_plugin.dylib',
'libtwolame_plugin.dylib',
'libvorbis_plugin.dylib',
#'libgestures_plugin.dylib',
#'libhotkeys_plugin.dylib',
#'libmotion_plugin.dylib',
#'libnetsync_plugin.dylib',
#'libsignals_plugin.dylib',
'demux/libaiff_plugin.dylib',
'demux/libasf_plugin.dylib',
'demux/libau_plugin.dylib',
'libaiff_plugin.dylib',
'libasf_plugin.dylib',
'libau_plugin.dylib',
#'libavformat_plugin.dylib',
'demux/libavi_plugin.dylib',
'demux/libdemux_cdg_plugin.dylib',
'demux/libdemuxdump_plugin.dylib',
'demux/libdiracsys_plugin.dylib',
'demux/libes_plugin.dylib',
'demux/libflacsys_plugin.dylib',
'access/liblive555_plugin.dylib',
'demux/libmkv_plugin.dylib',
'demux/libmod_plugin.dylib',
'demux/libmp4_plugin.dylib',
'demux/libmpc_plugin.dylib',
'demux/libmpgv_plugin.dylib',
'demux/libnsc_plugin.dylib',
'demux/libnsv_plugin.dylib',
'demux/libnuv_plugin.dylib',
'demux/libogg_plugin.dylib',
'demux/libplaylist_plugin.dylib',
'demux/libps_plugin.dylib',
'demux/libpva_plugin.dylib',
'demux/librawaud_plugin.dylib',
'demux/librawdv_plugin.dylib',
'demux/librawvid_plugin.dylib',
'demux/libreal_plugin.dylib',
'demux/libsmf_plugin.dylib',
'demux/libts_plugin.dylib',
'demux/libtta_plugin.dylib',
'demux/libty_plugin.dylib',
'demux/libvc1_plugin.dylib',
'demux/libvoc_plugin.dylib',
'demux/libwav_plugin.dylib',
'demux/libxa_plugin.dylib',
'meta_engine/libfolder_plugin.dylib',
'meta_engine/libtaglib_plugin.dylib',
'libavi_plugin.dylib',
'libdemux_cdg_plugin.dylib',
'libdemuxdump_plugin.dylib',
'libdiracsys_plugin.dylib',
'libes_plugin.dylib',
'libflacsys_plugin.dylib',
'liblive555_plugin.dylib',
'libmkv_plugin.dylib',
'libmod_plugin.dylib',
'libmp4_plugin.dylib',
'libmpc_plugin.dylib',
'libmpgv_plugin.dylib',
'libnsc_plugin.dylib',
'libnsv_plugin.dylib',
'libnuv_plugin.dylib',
'libogg_plugin.dylib',
'libplaylist_plugin.dylib',
'libps_plugin.dylib',
'libpva_plugin.dylib',
'librawaud_plugin.dylib',
'librawdv_plugin.dylib',
'librawvid_plugin.dylib',
'libreal_plugin.dylib',
'libsmf_plugin.dylib',
'libts_plugin.dylib',
'libtta_plugin.dylib',
'libty_plugin.dylib',
'libvc1_plugin.dylib',
'libvoc_plugin.dylib',
'libwav_plugin.dylib',
'libxa_plugin.dylib',
'libfolder_plugin.dylib',
'libtaglib_plugin.dylib',
#'libaudioscrobbler_plugin.dylib',
'control/libdummy_plugin.dylib',
'misc/libexport_plugin.dylib',
'libdummy_plugin.dylib',
'libexport_plugin.dylib',
#'libfreetype_plugin.dylib',
#'libgnutls_plugin.dylib',
'misc/liblogger_plugin.dylib',
'lua/liblua_plugin.dylib',
'liblogger_plugin.dylib',
'liblua_plugin.dylib',
#'libosd_parser_plugin.dylib',
#'libquartztext_plugin.dylib',
#'libstats_plugin.dylib',
'misc/libvod_rtsp_plugin.dylib',
'misc/libxml_plugin.dylib',
'libvod_rtsp_plugin.dylib',
'libxml_plugin.dylib',
#'libxtag_plugin.dylib',
'video_chroma/libi420_rgb_mmx_plugin.dylib',
'video_chroma/libi420_yuy2_mmx_plugin.dylib',
'video_chroma/libi422_yuy2_mmx_plugin.dylib',
'libi420_rgb_mmx_plugin.dylib',
'libi420_yuy2_mmx_plugin.dylib',
'libi422_yuy2_mmx_plugin.dylib',
#'libmemcpymmx_plugin.dylib',
#'libmemcpymmxext_plugin.dylib',
'mux/libmux_asf_plugin.dylib',
'mux/libmux_avi_plugin.dylib',
'mux/libmux_dummy_plugin.dylib',
'mux/libmux_mp4_plugin.dylib',
'mux/libmux_mpjpeg_plugin.dylib',
'mux/libmux_ogg_plugin.dylib',
'mux/libmux_ps_plugin.dylib',
'mux/libmux_ts_plugin.dylib',
'mux/libmux_wav_plugin.dylib',
'packetizer/libpacketizer_copy_plugin.dylib',
'packetizer/libpacketizer_dirac_plugin.dylib',
'packetizer/libpacketizer_flac_plugin.dylib',
'packetizer/libpacketizer_h264_plugin.dylib',
'packetizer/libpacketizer_mlp_plugin.dylib',
'packetizer/libpacketizer_mpeg4audio_plugin.dylib',
'packetizer/libpacketizer_mpeg4video_plugin.dylib',
'packetizer/libpacketizer_mpegvideo_plugin.dylib',
'packetizer/libpacketizer_vc1_plugin.dylib',
'video_chroma/libi420_rgb_sse2_plugin.dylib',
'video_chroma/libi420_yuy2_sse2_plugin.dylib',
'video_chroma/libi422_yuy2_sse2_plugin.dylib',
'stream_filter/libdecomp_plugin.dylib',
#'access/libstream_filter_rar_plugin.dylib',
'stream_filter/librecord_plugin.dylib',
'libmux_asf_plugin.dylib',
'libmux_avi_plugin.dylib',
'libmux_dummy_plugin.dylib',
'libmux_mp4_plugin.dylib',
'libmux_mpjpeg_plugin.dylib',
'libmux_ogg_plugin.dylib',
'libmux_ps_plugin.dylib',
'libmux_ts_plugin.dylib',
'libmux_wav_plugin.dylib',
'libpacketizer_copy_plugin.dylib',
'libpacketizer_dirac_plugin.dylib',
'libpacketizer_flac_plugin.dylib',
'libpacketizer_h264_plugin.dylib',
'libpacketizer_mlp_plugin.dylib',
'libpacketizer_mpeg4audio_plugin.dylib',
'libpacketizer_mpeg4video_plugin.dylib',
'libpacketizer_mpegvideo_plugin.dylib',
'libpacketizer_vc1_plugin.dylib',
'libi420_rgb_sse2_plugin.dylib',
'libi420_yuy2_sse2_plugin.dylib',
'libi422_yuy2_sse2_plugin.dylib',
'libdecomp_plugin.dylib',
#'libstream_filter_rar_plugin.dylib',
'librecord_plugin.dylib',
#'libvisual_plugin.dylib',
'libsecuretransport_plugin.dylib'
]
VLC_SEARCH_PATH=[
'/usr/local/lib/vlc/plugins/',
'/usr/local/opt/vlc/lib/vlc/plugins/',
]
QT_PLUGINS = [
@@ -201,39 +200,39 @@ QT_PLUGINS = [
]
SNORE_PLUGINS = [
# 'libsnore_backend_growl.so',
# 'libsnore_backend_osxnotificationcenter.so',
'libsnore_backend_growl.so',
'libsnore_backend_osxnotificationcenter.so',
]
TOMAHAWK_PLUGINS = [
'libtomahawk_account_xmpp.dylib',
'libtomahawk_account_google.so',
'libtomahawk_account_zeroconf.so',
# 'libtomahawk_account_hatchet.so',
'libtomahawk_infoplugin_adium.so',
'libtomahawk_infoplugin_charts.so',
'libtomahawk_infoplugin_discogs.so',
'libtomahawk_infoplugin_echonest.so',
'libtomahawk_infoplugin_hypem.so',
'libtomahawk_infoplugin_musicbrainz.so',
'libtomahawk_infoplugin_musixmatch.so',
'libtomahawk_infoplugin_newreleases.so',
'libtomahawk_infoplugin_rovi.so',
# 'libtomahawk_infoplugin_snorenotify.so',
'libtomahawk_infoplugin_spotify.so',
'libtomahawk_viewpage_dashboard.so',
# 'libtomahawk_viewpage_networkactivity.so',
'libtomahawk_viewpage_charts.so',
'libtomahawk_viewpage_newreleases.so',
'libtomahawk_viewpage_whatsnew_0_8.so',
'lib%s_account_xmpp.dylib' % TARGET_NAME,
'lib%s_account_google.so' % TARGET_NAME,
'lib%s_account_zeroconf.so' % TARGET_NAME,
'lib%s_account_hatchet.so' % TARGET_NAME,
'lib%s_infoplugin_adium.so' % TARGET_NAME,
'lib%s_infoplugin_charts.so' % TARGET_NAME,
# 'lib%s_infoplugin_discogs.so' % TARGET_NAME,
'lib%s_infoplugin_echonest.so' % TARGET_NAME,
'lib%s_infoplugin_hypem.so' % TARGET_NAME,
# 'lib%s_infoplugin_musicbrainz.so' % TARGET_NAME,
'lib%s_infoplugin_musixmatch.so' % TARGET_NAME,
'lib%s_infoplugin_newreleases.so' % TARGET_NAME,
# 'lib%s_infoplugin_rovi.so' % TARGET_NAME,
'lib%s_infoplugin_snorenotify.so' % TARGET_NAME,
'lib%s_infoplugin_spotify.so' % TARGET_NAME,
'lib%s_viewpage_dashboard.so' % TARGET_NAME,
# 'lib%s_viewpage_networkactivity.so' % TARGET_NAME,
'lib%s_viewpage_charts.so' % TARGET_NAME,
'lib%s_viewpage_newreleases.so' % TARGET_NAME,
'lib%s_viewpage_whatsnew_0_8.so' % TARGET_NAME,
]
QT_PLUGINS_SEARCH_PATH=[
'/usr/local/Cellar/qt5/5.4.0/plugins',
'/usr/local/opt/qt5/plugins',
]
SNORE_PLUGINS_SEARCH_PATH=[
'/usr/local/Cellar/snorenotify/HEAD/lib/libsnore',
'/usr/local/opt/snorenotify/lib/plugins/libsnore-qt5',
]
class Error(Exception):
@@ -247,6 +246,8 @@ class CouldNotFindQtPluginErrorFindFrameworkError(Error):
class InstallNameToolError(Error):
pass
class CouldNotFindFrameworkError(Error):
pass
class CouldNotFindQtPluginError(Error):
pass
@@ -308,7 +309,7 @@ def GetBrokenLibraries(binary):
continue # unix style system library
elif re.match(r'Breakpad', line):
continue # Manually added by cmake.
elif re.match(r'^\s*@executable_path', line) or re.match(r'^\s*@loader_path', line):
elif re.match(r'^\s*@executable_path', line) or re.match(r'^\s*@loader_path', line) and not re.match(r'^\s*@loader_path/../lib', line):
# Potentially already fixed library
if '.framework' in line:
relative_path = os.path.join(*line.split('/')[3:])
@@ -332,6 +333,11 @@ def FindFramework(path):
if os.path.exists(abs_path):
return abs_path
# replace rpath with /Library/Frameworks for Sparkle
abs_path = path.replace("@rpath/", "/Library/Frameworks/")
if os.path.exists(abs_path):
return abs_path
raise CouldNotFindFrameworkError(path)
def FindLibrary(path):
@@ -576,12 +582,12 @@ for plugin in TOMAHAWK_PLUGINS:
FixPlugin(plugin, '../MacOS')
for plugin in SNORE_PLUGINS:
FixPlugin(FindSnorePlugin(plugin), '../MacOS/libsnore')
FixPlugin(FindSnorePlugin(plugin), '../lib/plugins/libsnore-qt5')
try:
FixPlugin('tomahawk_crash_reporter', '../MacOS')
FixPlugin('%s_crash_reporter' % TARGET_NAME, '../MacOS')
except:
print 'Failed to find tomahawk_crash_reporter'
print 'Failed to find %s_crash_reporter' % TARGET_NAME
for plugin in QT_PLUGINS:
FixPlugin(FindQtPlugin(plugin), os.path.dirname(plugin))

View File

@@ -6,9 +6,9 @@ if ARGV.length < 2
exit
end
tarball = "tomahawk-#{ARGV[0]}.tar.bz2"
tarball = "#{ARGV[0].downcase}-#{ARGV[1]}.tar.bz2"
puts "Zipping: #{tarball}..."
`tar jcvf "#{tarball}" Tomahawk.app`
`tar jcvf "#{tarball}" #{ARGV[0]}.app`
puts "Signing..."
puts `openssl dgst -sha1 -binary < "#{tarball}" | openssl dgst -dss1 -sign "#{ARGV[1]}" | openssl enc -base64`
puts `openssl dgst -sha1 -binary < "#{tarball}" | openssl dgst -dss1 -sign "#{ARGV[2]}" | openssl enc -base64`

View File

@@ -9,19 +9,20 @@ fi
rm -rvf vlc/
VLC_TARBALL="vlc-2.2.3.tar.bz2"
echo "Download phonon archive..."
# wget -c "http://downloads.sourceforge.net/project/vlc/1.1.9/win32/vlc-1.1.9-win32.7z?r=http%3A%2F%2Fwww.videolan.org%2Fvlc%2Fdownload-windows.html&ts=1306272584&use_mirror=leaseweb"
# wget -c "http://download.tomahawk-player.org/tomahawk-vlc-0.1.zip"
# wget -c http://people.videolan.org/~jb/phonon/phonon-vlc-last.7z
# wget -c http://people.videolan.org/~jb/phonon/phonon_phonon-vlc_20111128.7z
wget -c http://download.tomahawk-player.org/test/vlc.tar.bz2
wget -c "http://download.tomahawk-player.org/test/$VLC_TARBALL"
echo "Extract binary..."
# 7z x phonon*.7z
# mv -v vlc-*/ vlc/
# unzip tomahawk-vlc-0.1.zip
tar xvjf vlc.tar.bz2
tar xvjf "$VLC_TARBALL"
# echo "Download phonon_vlc_no_video.dll..."
# wget -c http://people.videolan.org/~jb/phonon/phonon_vlc_no_video.dll
@@ -72,7 +73,9 @@ rm -rvf \
**/libi420* \
**/libi422* \
mux/ \
stream_filter/ \
stream_filter/*dash* \
stream_filter/*smooth* \
stream_filter/*record* \
**/libtheora_plugin* \
**/liblibbluray_plugin* \
**/libdtv_plugin* \

13
data/images/folder.svg Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="86px" height="86px" viewBox="0 0 86 86" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<title>folder</title>
<description>Created with Sketch (http://www.bohemiancoding.com/sketch)</description>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="folder" sketch:type="MSLayerGroup" transform="translate(0.000000, 13.000000)" fill="#000000">
<g id="Page-1" sketch:type="MSShapeGroup">
<path d="M84.1450038,10.4833376 C82.8248024,9.1092149 80.9427764,8.3195294 78.9789985,8.3195294 L72.233622,8.3195294 L34.4730823,8.3195294 L34.4730823,7.0184614 C34.4730823,3.1483061 31.3247762,0 27.4563602,0 L10.1441555,0 C6.27226058,0 3.12569415,3.1500456 3.12569415,7.0184614 L3.12569415,9.4918821 C1.2088801,10.761641 0,12.9237099 0,15.3362515 L0,54.4987445 C0,54.5057021 0,54.5178779 0.0017394,54.5265748 L0,54.5439688 C0,58.3897726 3.14656684,61.5154666 7.0184614,61.5154666 L72.2336242,61.5154666 C72.4736608,61.5154666 72.7102186,61.5050302 72.9432977,61.4789393 C72.9850432,61.4771999 73.028528,61.4650241 73.0702735,61.4615453 C73.2616071,61.4389332 73.4529406,61.4145816 73.6390559,61.3745755 C73.6686257,61.3693573 73.6947166,61.3606604 73.7242863,61.3519634 C73.9225774,61.3119573 74.1208685,61.2597754 74.3139414,61.200636 C74.3243778,61.1954178 74.3348142,61.191939 74.3452506,61.1884602 C76.756053,60.4300837 78.5998126,58.4123847 79.1059765,55.9198305 C79.1077159,55.912873 79.1077159,55.9024366 79.1077159,55.895479 C79.1512008,55.6937091 79.18251,55.4919392 79.2051221,55.2797329 C79.2103403,55.2240723 79.2155585,55.1684116 79.2190373,55.1092721 C79.2277343,55.0136054 79.2433888,54.9266356 79.2451282,54.8309688 L85.9539776,15.7937099 L85.9939837,15.4127822 C86.0705187,13.5587047 85.4147685,11.8086925 84.1450038,10.4833376 C84.1450038,10.4833376 85.4147685,11.8086925 84.1450038,10.4833376 L84.1450038,10.4833376" id="Shape"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

55
data/images/nav-back.svg Normal file
View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<title>nav-back</title>
<description>Created with Sketch (http://www.bohemiancoding.com/sketch)</description>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="nav-back" sketch:type="MSLayerGroup" transform="translate(4.000000, 6.000000)">
<g id="Layer_2"></g>
<g id="Layer_3"></g>
<g id="Layer_4"></g>
<g id="Layer_5"></g>
<g id="Layer_6"></g>
<g id="Layer_8"></g>
<g id="Layer_9"></g>
<g id="Layer_10"></g>
<g id="Layer_11"></g>
<g id="Layer_12"></g>
<g id="Layer_13"></g>
<g id="Layer_14"></g>
<g id="Layer_15"></g>
<g id="Layer_16"></g>
<g id="Layer_17"></g>
<g id="Layer_18"></g>
<g id="Layer_19"></g>
<g id="Layer_20"></g>
<g id="Layer_21"></g>
<g id="Layer_24"></g>
<g id="Layer_25"></g>
<g id="Layer_26"></g>
<g id="Layer_27"></g>
<g id="Layer_28"></g>
<g id="Layer_29"></g>
<g id="Layer_47"></g>
<g id="Layer_30"></g>
<g id="Layer_31"></g>
<g id="Layer_32"></g>
<g id="Layer_33"></g>
<g id="Layer_34"></g>
<g id="Layer_35"></g>
<g id="Layer_36"></g>
<g id="Layer_37"></g>
<g id="Layer_38" transform="translate(2.000000, 0.000000)" fill="#000000" sketch:type="MSShapeGroup">
<path d="M9.7,13.9 L-0.3,7 L9.7,0.1 L10.3,0.9 L1.5,7 L10.3,13.1 L9.7,13.9 Z" id="Shape"></path>
</g>
<g id="Layer_39"></g>
<g id="Layer_40"></g>
<g id="Layer_41"></g>
<g id="Layer_42"></g>
<g id="Layer_43"></g>
<g id="Layer_44"></g>
<g id="Layer_45"></g>
<g id="Layer_46"></g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<title>nav-forward</title>
<description>Created with Sketch (http://www.bohemiancoding.com/sketch)</description>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="nav-forward" sketch:type="MSLayerGroup" transform="translate(6.000000, 6.000000)">
<g id="Layer_2"></g>
<g id="Layer_3"></g>
<g id="Layer_4"></g>
<g id="Layer_5"></g>
<g id="Layer_6"></g>
<g id="Layer_8"></g>
<g id="Layer_9"></g>
<g id="Layer_10"></g>
<g id="Layer_11"></g>
<g id="Layer_12"></g>
<g id="Layer_13"></g>
<g id="Layer_14"></g>
<g id="Layer_15"></g>
<g id="Layer_16"></g>
<g id="Layer_17"></g>
<g id="Layer_18"></g>
<g id="Layer_19"></g>
<g id="Layer_20"></g>
<g id="Layer_21"></g>
<g id="Layer_24"></g>
<g id="Layer_25"></g>
<g id="Layer_26"></g>
<g id="Layer_27"></g>
<g id="Layer_28"></g>
<g id="Layer_29"></g>
<g id="Layer_47"></g>
<g id="Layer_30"></g>
<g id="Layer_31"></g>
<g id="Layer_32"></g>
<g id="Layer_33"></g>
<g id="Layer_34"></g>
<g id="Layer_35"></g>
<g id="Layer_36"></g>
<g id="Layer_37"></g>
<g id="Layer_38"></g>
<g id="Layer_39"></g>
<g id="Layer_40"></g>
<g id="Layer_41" transform="translate(2.000000, 0.000000)" fill="#000000" sketch:type="MSShapeGroup">
<path d="M0.5,13.7 L-0.1,12.9 L8.3,7 L-0.1,1.1 L0.5,0.3 L10.1,7 L0.5,13.7 Z" id="Shape"></path>
</g>
<g id="Layer_42"></g>
<g id="Layer_43"></g>
<g id="Layer_44"></g>
<g id="Layer_45"></g>
<g id="Layer_46"></g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -102,7 +102,7 @@ Tomahawk.InfoSystem.InfoPlugin = {
notInCache: function (infoType, criteria) {
var requestMethod = 'request' + this.infoTypeString(infoType);
return Promise.resolve(this[requestMethod](criteria));
return RSVP.Promise.resolve(this[requestMethod](criteria));
},
pushInfo: function (pushData) {
var pushMethod = 'push' + this.infoTypeString(pushData.type);
@@ -114,6 +114,6 @@ Tomahawk.InfoSystem.InfoPlugin = {
getInfo: function (type, infoHash) {
var getInfoMethod = 'get' + this.infoTypeString(type);
return Promise.resolve(this[getInfoMethod](infoHash));
return RSVP.Promise.resolve(this[getInfoMethod](infoHash));
}
};

View File

@@ -49,7 +49,7 @@ RSVP.on('error', function (reason) {
resolverName = Tomahawk.resolver.instance.settings.name + " - ";
}
if (reason) {
console.error(resolverName + 'Uncaught error:' + JSON.stringify(reason));
console.error(resolverName + 'Uncaught error:', reason);
} else {
console.error(resolverName + 'Uncaught error: error thrown from RSVP but it was empty');
}
@@ -173,7 +173,8 @@ var TomahawkUrlType = {
Playlist: 1,
Track: 2,
Album: 4,
Artist: 8
Artist: 8,
Xspf: 16
};
//Deprecated for 0.9 resolvers. Use Tomahawk.ConfigTestResultType instead.
@@ -236,7 +237,7 @@ var TomahawkResolver = {
collection: function () {
return {};
},
_testConfig: function (config) {
_adapter_testConfig: function (config) {
return RSVP.Promise.resolve(this.testConfig(config)).then(function () {
return {result: Tomahawk.ConfigTestResultType.Success};
});
@@ -267,81 +268,38 @@ Tomahawk.Resolver = {
},
newConfigSaved: function () {
},
testConfig: function () {
},
getStreamUrl: function (params) {
return params;
},
_convertUrls: function (results) {
var that = this;
return results.map(function (r) {
if (r && r.url) {
r.url = that._urlProtocol + '://' + r.url;
resolve: function() {
},
_adapter_resolve: function (params) {
return RSVP.Promise.resolve(this.resolve(params)).then(function (results) {
if(Array.isArray(results)) {
return {
'tracks': results
};
}
return r;
return results;
});
},
_adapter_resolve: function (qid, artist, album, title) {
var that = this;
var collectionPromises = [];
Tomahawk.collections.forEach(function (col) {
if (col.resolve) {
collectionPromises.push(col.resolve({artist: artist, album: album, track: title}));
_adapter_search: function (params) {
return RSVP.Promise.resolve(this.search(params)).then(function (results) {
if(Array.isArray(results)) {
return {
'tracks': results
};
}
});
RSVP.Promise.all(collectionPromises).then(function (collectionResults) {
var merged = [];
return merged.concat.apply(merged, collectionResults);
}).then(function (collectionResults) {
RSVP.Promise.resolve(that.resolve({
artist: artist,
album: album,
track: title
})).then(function (results) {
Tomahawk.addTrackResults({
'qid': qid,
'results': that._convertUrls(results.concat(collectionResults))
});
});
return results;
});
},
_adapter_init: function () {
this._urlProtocol = this.settings.name.replace(/[^a-zA-Z]/g, '').toLowerCase();
Tomahawk.addCustomUrlHandler(this._urlProtocol, '_adapter_getStreamUrl', true);
Tomahawk.log('Registered custom url handler for protocol "' + this._urlProtocol + '"');
this.init();
},
_adapter_getStreamUrl: function (params) {
params.url = params.url.slice(this._urlProtocol.length + 3);
RSVP.Promise.resolve(this.getStreamUrl(params)).then(function (result) {
Tomahawk.reportStreamUrl(params.qid, result.url, result.headers);
});
},
_adapter_search: function (qid, query) {
var that = this;
var collectionPromises = [];
Tomahawk.collections.forEach(function (col) {
if (col.search) {
collectionPromises.push(col.search({query: query}));
}
});
RSVP.Promise.all(collectionPromises).then(function (collectionResults) {
var merged = [];
return merged.concat.apply(merged, collectionResults);
}).then(function (collectionResults) {
RSVP.Promise.resolve(that.search({query: query})).then(function (results) {
Tomahawk.addTrackResults({
'qid': qid,
'results': that._convertUrls(results.concat(collectionResults))
});
});
});
},
_testConfig: function (config) {
_adapter_testConfig: function (config) {
return RSVP.Promise.resolve(this.testConfig(config)).then(function () {
return {result: Tomahawk.ConfigTestResultType.Success};
});
@@ -364,34 +322,6 @@ Tomahawk.valueForSubNode = function (node, tag) {
return element.textContent;
};
/**
* Do a synchronous HTTP(S) request. For further options see
* Tomahawk.asyncRequest
*/
Tomahawk.syncRequest = function (url, extraHeaders, options) {
// unpack options
var opt = options || {};
var method = opt.method || 'GET';
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open(method, url, false, opt.username, opt.password);
if (extraHeaders) {
for (var headerName in extraHeaders) {
xmlHttpRequest.setRequestHeader(headerName, extraHeaders[headerName]);
}
}
xmlHttpRequest.send(null);
if (httpSuccessStatuses.indexOf(xmlHttpRequest.status) != -1) {
return xmlHttpRequest.responseText;
} else {
Tomahawk.log("Failed to do GET request: to: " + url);
Tomahawk.log("Status Code was: " + xmlHttpRequest.status);
if (opt.hasOwnProperty('errorHandler')) {
opt.errorHandler.call(window, xmlHttpRequest);
}
}
};
/**
* Internal counter used to identify retrievedMetadata call back from native
* code.
@@ -487,6 +417,23 @@ Tomahawk.nativeAsyncRequestDone = function (reqId, xhr) {
delete Tomahawk.asyncRequestCallbacks[reqId];
};
/**
* This method is externalized from Tomahawk.asyncRequest, so that other clients
* (like tomahawk-android) can inject their own logic that determines whether or not to do a request
* natively.
*
* @returns boolean indicating whether or not to do a request with the given parameters natively
*/
var shouldDoNativeRequest = function (options) {
var extraHeaders = options.headers;
return (extraHeaders && (extraHeaders.hasOwnProperty("Referer")
|| extraHeaders.hasOwnProperty("referer")
|| extraHeaders.hasOwnProperty("User-Agent")));
};
/**
* Possible options:
* - method: The HTTP request method (default: GET)
@@ -496,59 +443,45 @@ Tomahawk.nativeAsyncRequestDone = function (reqId, xhr) {
* - data: body data included in POST requests
* - needCookieHeader: boolean indicating whether or not the request needs to be able to get the
* "Set-Cookie" response header
* - headers: headers set on the request
*/
Tomahawk.asyncRequest = function (url, callback, extraHeaders, options) {
// unpack options
var opt = options || {};
var method = opt.method || 'GET';
var doRequest = function(options) {
if (shouldDoNativeRequest(options)) {
return Tomahawk.NativeScriptJobManager.invoke('httpRequest', options).then(function(xhr) {
xhr.responseHeaders = xhr.responseHeaders || {};
xhr.getAllResponseHeaders = function() {
return this.responseHeaders;
};
xhr.getResponseHeader = function (header) {
return this.responseHeaders[header];
};
if (shouldDoNativeRequest(url, callback, extraHeaders, options)) {
// Assign a request Id to the callback so we can use it when we are
// returning from the native call.
var reqId = Tomahawk.asyncRequestIdCounter;
Tomahawk.asyncRequestIdCounter++;
Tomahawk.asyncRequestCallbacks[reqId] = {
callback: callback,
errorHandler: opt.errorHandler
};
Tomahawk.nativeAsyncRequest(reqId, url, extraHeaders, options);
return xhr;
});
} else {
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open(method, url, true, opt.username, opt.password);
if (extraHeaders) {
for (var headerName in extraHeaders) {
xmlHttpRequest.setRequestHeader(headerName, extraHeaders[headerName]);
}
}
xmlHttpRequest.onreadystatechange = function () {
if (xmlHttpRequest.readyState == 4
&& httpSuccessStatuses.indexOf(xmlHttpRequest.status) != -1) {
callback.call(window, xmlHttpRequest);
} else if (xmlHttpRequest.readyState === 4) {
Tomahawk.log("Failed to do " + method + " request: to: " + url);
Tomahawk.log("Status Code was: " + xmlHttpRequest.status);
if (opt.hasOwnProperty('errorHandler')) {
opt.errorHandler.call(window, xmlHttpRequest);
return new RSVP.Promise(function(resolve, reject) {
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open(options.method, options.url, true, options.username, options.password);
if (options.headers) {
for (var headerName in options.headers) {
xmlHttpRequest.setRequestHeader(headerName, options.headers[headerName]);
}
}
};
xmlHttpRequest.send(opt.data || null);
xmlHttpRequest.onreadystatechange = function () {
if (xmlHttpRequest.readyState == 4
&& httpSuccessStatuses.indexOf(xmlHttpRequest.status) != -1) {
resolve(xmlHttpRequest);
} else if (xmlHttpRequest.readyState === 4) {
Tomahawk.log("Failed to do " + options.method + " request: to: " + options.url);
Tomahawk.log("Status Code was: " + xmlHttpRequest.status);
reject(xmlHttpRequest);
}
};
xmlHttpRequest.send(options.data || null);
});
}
};
/**
* This method is externalized from Tomahawk.asyncRequest, so that other clients
* (like tomahawk-android) can inject their own logic that determines whether or not to do a request
* natively.
*
* @returns boolean indicating whether or not to do a request with the given parameters natively
*/
var shouldDoNativeRequest = function (url, callback, extraHeaders, options) {
return (extraHeaders && (extraHeaders.hasOwnProperty("Referer")
|| extraHeaders.hasOwnProperty("referer")
|| extraHeaders.hasOwnProperty("User-Agent")));
};
Tomahawk.ajax = function (url, settings) {
if (typeof url === "object") {
settings = url;
@@ -606,10 +539,7 @@ Tomahawk.ajax = function (url, settings) {
}
}
return new RSVP.Promise(function (resolve, reject) {
settings.errorHandler = reject;
Tomahawk.asyncRequest(settings.url, resolve, settings.headers, settings);
}).then(function (xhr) {
return doRequest(settings).then(function (xhr) {
if (settings.rawResponse) {
return xhr;
}
@@ -815,7 +745,9 @@ Tomahawk.base64Encode = function (b) {
return window.btoa(b);
};
Tomahawk.PluginManager = {
wrapperPrefix: '_adapter_',
objects: {},
objectCounter: 0,
identifyObject: function (object) {
@@ -827,10 +759,6 @@ Tomahawk.PluginManager = {
},
registerPlugin: function (type, object) {
this.objects[this.identifyObject(object)] = object;
if (type === 'collection') {
Tomahawk.collections.push(object);
}
Tomahawk.log("registerPlugin: " + type + " id: " + object.id);
Tomahawk.registerScriptPlugin(type, object.id);
},
@@ -844,14 +772,11 @@ Tomahawk.PluginManager = {
resolve: [],
invokeSync: function (requestId, objectId, methodName, params) {
if (!Tomahawk.resolver.instance.apiVersion || Tomahawk.resolver.instance.apiVersion < 0.9) {
if (methodName === 'artistAlbums') {
methodName = 'albums';
} else if (methodName === 'albumTracks') {
methodName = 'tracks';
}
if (this.objects[objectId][this.wrapperPrefix + methodName]) {
methodName = this.wrapperPrefix + methodName;
}
var pluginManager = this;
if (!this.objects[objectId]) {
Tomahawk.log("Object not found! objectId: " + objectId + " methodName: " + methodName);
@@ -861,52 +786,13 @@ Tomahawk.PluginManager = {
}
}
if (typeof this.objects[objectId][methodName] === 'function') {
if (!Tomahawk.resolver.instance.apiVersion
|| Tomahawk.resolver.instance.apiVersion < 0.9) {
if (methodName == 'artists') {
return new RSVP.Promise(function (resolve, reject) {
pluginManager.resolve[requestId] = resolve;
Tomahawk.resolver.instance.artists(requestId);
});
} else if (methodName == 'albums') {
return new RSVP.Promise(function (resolve, reject) {
pluginManager.resolve[requestId] = resolve;
Tomahawk.resolver.instance.albums(requestId, params.artist);
});
} else if (methodName == 'tracks') {
return new RSVP.Promise(function (resolve, reject) {
pluginManager.resolve[requestId] = resolve;
Tomahawk.resolver.instance.tracks(requestId, params.artist, params.album);
});
} else if (methodName == 'lookupUrl') {
return new RSVP.Promise(function (resolve, reject) {
pluginManager.resolve[params.url] = resolve;
Tomahawk.resolver.instance.lookupUrl(params.url);
});
} else if (methodName == 'getStreamUrl') {
return new RSVP.Promise(function (resolve, reject) {
pluginManager.resolve[requestId] = resolve;
Tomahawk.resolver.instance.getStreamUrl(requestId, params.url);
});
} else if (methodName == 'resolve') {
return new RSVP.Promise(function (resolve, reject) {
pluginManager.resolve[requestId] = resolve;
Tomahawk.resolver.instance.resolve(requestId, params.artist,
params.album, params.track);
});
} else if (methodName == 'search') {
return new RSVP.Promise(function (resolve, reject) {
pluginManager.resolve[requestId] = resolve;
Tomahawk.resolver.instance.search(requestId, params.query);
});
}
}
return this.objects[objectId][methodName](params);
if (typeof this.objects[objectId][methodName] !== 'function' && this.objects[objectId][methodName]) {
return this.objects[objectId][methodName];
} else if (typeof this.objects[objectId][methodName] !== 'function') {
throw new Error('\'' + methodName + '\' on ScriptObject ' + objectId + ' is not a function', typeof this.objects[objectId][methodName]);
}
return this.objects[objectId][methodName];
return this.objects[objectId][methodName](params);
},
invoke: function (requestId, objectId, methodName, params) {
@@ -925,21 +811,38 @@ Tomahawk.PluginManager = {
}
};
var encodeParamsToNativeFunctions = function(param) {
return param;
};
Tomahawk.NativeScriptJobManager = {
idCounter: 0,
deferreds: {},
invoke: function (methodName, params) {
params = params || {};
var requestId = this.idCounter++;
Tomahawk.invokeNativeScriptJob(requestId, methodName, JSON.stringify(params));
this.deferreds[requestId] = RSVP.defer();
return this.deferreds[requestId].promise;
var deferred = RSVP.defer();
this.deferreds[requestId] = deferred;
Tomahawk.invokeNativeScriptJob(requestId, methodName, encodeParamsToNativeFunctions(params));;
return deferred.promise;
},
reportNativeScriptJobResult: function (requestId, result) {
reportNativeScriptJobResult: function(requestId, result) {
var deferred = this.deferreds[requestId];
if (!deferred) {
Tomahawk.log("Deferred object with the given requestId is not present!");
}
deferred.resolve(result);
delete this.deferreds[requestId];
},
reportNativeScriptJobError: function(requestId, error) {
var deferred = this.deferreds[requestId];
if (!deferred) {
console.log("Deferred object with the given requestId is not present!");
}
deferred.reject(error);
delete this.deferreds[requestId];
}
};
@@ -1213,8 +1116,6 @@ Tomahawk.Country = {
LatinAmericaAndTheCaribbean: 246
};
Tomahawk.collections = [];
Tomahawk.Collection = {
BrowseCapability: {
Artists: 1,
@@ -1607,6 +1508,10 @@ Tomahawk.Collection = {
});
},
revision: function (params) {
return RSVP.resolve();
},
_fuzzyIndexIdsToTracks: function (resultIds, id) {
if (typeof id === 'undefined') {
id = this.settings.id;
@@ -1658,6 +1563,14 @@ Tomahawk.Collection = {
});
},
_adapter_resolve: function (params) {
return RSVP.Promise.resolve(this.resolve(params)).then(function (results) {
return {
'tracks': results
};
});
},
resolve: function (params) {
var resultIds = Tomahawk.resolveFromFuzzyIndex(params.artist, params.album, params.track);
return this._fuzzyIndexIdsToTracks(resultIds);
@@ -1665,7 +1578,12 @@ Tomahawk.Collection = {
search: function (params) {
var resultIds = Tomahawk.searchFuzzyIndex(params.query);
return this._fuzzyIndexIdsToTracks(resultIds);
return this._fuzzyIndexIdsToTracks(resultIds).then(function(tracks) {
return {
tracks: tracks
};
});
},
tracks: function (params, where) {
@@ -1709,7 +1627,7 @@ Tomahawk.Collection = {
);
return t.execDeferredStatements();
}).then(function (results) {
return {results: Tomahawk.resolver.instance._convertUrls(results[0])};
return {tracks: results[0]};
});
},
@@ -1883,71 +1801,13 @@ Tomahawk.Collection = {
Tomahawk.Collection.BrowseCapability.Albums,
Tomahawk.Collection.BrowseCapability.Tracks];
return this.settings;
},
getStreamUrl: function(params) {
if(this.resolver) {
return this.resolver.getStreamUrl(params);
}
return params;
}
};
// Legacy compability for 0.8 and before
Tomahawk.reportCapabilities = function (capabilities) {
if (capabilities & TomahawkResolverCapability.Browsable) {
Tomahawk.PluginManager.registerPlugin("collection", Tomahawk.resolver.instance);
}
Tomahawk.nativeReportCapabilities(capabilities);
};
Tomahawk.addArtistResults = Tomahawk.addAlbumResults = Tomahawk.addAlbumTrackResults
= function (result) {
Tomahawk.PluginManager.resolve[result.qid](result);
delete Tomahawk.PluginManager.resolve[result.qid];
};
Tomahawk.addTrackResults = function (result) {
Tomahawk.PluginManager.resolve[result.qid](result.results);
delete Tomahawk.PluginManager.resolve[result.qid];
};
Tomahawk.reportStreamUrl = function (qid, streamUrl, headers) {
Tomahawk.PluginManager.resolve[qid]({
url: streamUrl,
headers: headers
});
delete Tomahawk.PluginManager.resolve[qid];
};
Tomahawk.addUrlResult = function (url, result) {
/* Merge the whole mess into one consistent result which is independent of type
var cleanResult = {
type: result.type,
guid: result.guid,
info: result.info,
creator: result.creator,
linkUrl: result.url
};
if (cleanResult.type == "track") {
cleanResult.track = result.title;
cleanResult.artist = result.artist;
} else if (cleanResult.type == "artist") {
cleanResult.artist = result.name;
} else if (cleanResult.type == "album") {
cleanResult.album = result.name;
cleanResult.artist = result.artist;
} else if (cleanResult.type == "playlist") {
cleanResult.title = result.title;
} else if (cleanResult.type == "xspf-url") {
cleanResult.url = result.url;
}
if (result.tracks) {
cleanResult.tracks = [];
var i;
for (i=0;i<result.tracks.length;i++) {
var cleanTrack = {
track: result.tracks[i].title,
artist: result.tracks[i].artist
};
cleanResult.push(cleanTrack)
}
Tomahawk.PluginManager.resolve[url](cleanResult);
*/
Tomahawk.PluginManager.resolve[url](result);
delete Tomahawk.PluginManager.resolve[url];
};

BIN
data/sounds/silence.ogg Normal file

Binary file not shown.

View File

@@ -3,6 +3,7 @@
<file>data/images/collection_background.png</file>
<file>data/images/playlist_background.png</file>
<file>data/images/filter.svg</file>
<file>data/images/folder.svg</file>
<file>data/images/loved.svg</file>
<file>data/images/love.svg</file>
<file>data/images/not-loved.svg</file>
@@ -167,5 +168,8 @@
<file>data/images/repeat-one.svg</file>
<file>data/images/downloads.svg</file>
<file>data/images/downloadbutton.svg</file>
<file>data/images/nav-back.svg</file>
<file>data/images/nav-forward.svg</file>
<file>data/sounds/silence.ogg</file>
</qresource>
</RCC>

View File

@@ -24,13 +24,6 @@ include_directories(
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
if(APPLE)
# http://stackoverflow.com/questions/7226753/osx-lion-xcode-4-1-how-do-i-setup-a-c0x-project/7236451#7236451
add_definitions(-std=c++11 -stdlib=libc++ -U__STRICT_ANSI__)
set(PLATFORM_SPECIFIC_LINK_LIBRARIES "/usr/lib/libc++.dylib")
else()
add_definitions(-std=c++0x)
endif()
tomahawk_add_plugin(hatchet
TYPE account

View File

@@ -69,6 +69,13 @@ Tomahawk::InfoSystem::XmppInfoPlugin::init()
}
const QString
Tomahawk::InfoSystem::XmppInfoPlugin::friendlyName() const
{
return "xmpp";
}
void
Tomahawk::InfoSystem::XmppInfoPlugin::pushInfo( Tomahawk::InfoSystem::InfoPushData pushData )
{

View File

@@ -38,6 +38,8 @@ namespace Tomahawk {
XmppInfoPlugin(XmppSipPlugin* parent);
virtual ~XmppInfoPlugin();
const QString friendlyName() const override;
signals:
void publishTune( QUrl url, Tomahawk::InfoSystem::InfoStringHash trackInfo );

View File

@@ -194,9 +194,9 @@ InfoSystem::InfoPluginPtr
XmppSipPlugin::infoPlugin()
{
if ( m_infoPlugin.isNull() )
m_infoPlugin = QPointer< Tomahawk::InfoSystem::XmppInfoPlugin >( new Tomahawk::InfoSystem::XmppInfoPlugin( this ) );
m_infoPlugin = QSharedPointer< Tomahawk::InfoSystem::XmppInfoPlugin >( new Tomahawk::InfoSystem::XmppInfoPlugin( this ) );
return InfoSystem::InfoPluginPtr( m_infoPlugin.data() );
return m_infoPlugin;
}
@@ -285,7 +285,7 @@ XmppSipPlugin::onConnect()
// load XmppInfoPlugin
if ( infoPlugin() && Tomahawk::InfoSystem::InfoSystem::instance()->workerThread() )
{
infoPlugin().data()->moveToThread( Tomahawk::InfoSystem::InfoSystem::instance()->workerThread().data() );
infoPlugin()->moveToThread( Tomahawk::InfoSystem::InfoSystem::instance()->workerThread().data() );
Tomahawk::InfoSystem::InfoSystem::instance()->addInfoPlugin( infoPlugin() );
}
@@ -359,7 +359,6 @@ XmppSipPlugin::onDisconnect( Jreen::Client::DisconnectReason reason )
if ( !m_infoPlugin.isNull() )
{
Tomahawk::InfoSystem::InfoSystem::instance()->removeInfoPlugin( infoPlugin() );
delete m_infoPlugin;
}
}

View File

@@ -128,7 +128,7 @@ private:
int m_currentPort;
QString m_currentResource;
QPointer< Tomahawk::InfoSystem::XmppInfoPlugin > m_infoPlugin;
QSharedPointer< Tomahawk::InfoSystem::XmppInfoPlugin > m_infoPlugin;
Tomahawk::Accounts::Account::ConnectionState m_state;
// sort out

View File

@@ -174,7 +174,7 @@ int main( int argc, char* argv[] )
reporter.setLogo( QPixmap( CRASHREPORTER_ICON ) );
#endif
reporter.setWindowTitle( CRASHREPORTER_PRODUCT_NAME );
reporter.setText("<html><head/><body><p><span style=\" font-weight:600;\">Sorry!</span> " CRASHREPORTER_PRODUCT_NAME " crashed. Please tell us about it! " CRASHREPORTER_PRODUCT_NAME " has created an error report for you that can help improve the stability in the future. You can now send this report directly to the " CRASHREPORTER_PRODUCT_NAME " developers.</p></body></html>");
reporter.setText("<html><head/><body><p><span style=\"font-weight:600;\">Sorry!</span> " CRASHREPORTER_PRODUCT_NAME " crashed. Please tell us about it! " CRASHREPORTER_PRODUCT_NAME " has created an error report for you that can help improve the stability in the future. You can now send this report directly to the " CRASHREPORTER_PRODUCT_NAME " developers.</p><p>Can you tell us what you were doing when this happened?</p></body></html>");
reporter.setReportData( "BuildID", CRASHREPORTER_BUILD_ID );
reporter.setReportData( "ProductName", CRASHREPORTER_PRODUCT_NAME );

View File

@@ -3,17 +3,15 @@ include_directories(
${Boost_INCLUDE_DIR}
)
if(WIN32 OR APPLE)
if(BUILD_GUI AND LIBSNORE_FOUND)
if(BUILD_GUI AND LibsnoreQt5_FOUND)
SET(snore_srcs
snorenotify/SnoreNotifyPlugin.cpp
)
SET(SNORE_LINK_LIBRARIES ${LINK_LIBRARIES} ${LIBSNORE_LIBRARIES} )
tomahawk_add_plugin(snorenotify
TYPE infoplugin EXPORT_MACRO INFOPLUGINDLLEXPORT_PRO
SOURCES "${snore_srcs}" LINK_LIBRARIES "${SNORE_LINK_LIBRARIES}"
SOURCES "${snore_srcs}" LINK_LIBRARIES Snore::Libsnore
)
endif(BUILD_GUI AND LIBSNORE_FOUND)
endif()
endif(WIN32 OR APPLE)
list(APPEND simple_plugins

View File

@@ -1,6 +1,6 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2013-2014, Patrick von Reth <vonreth@kde.org>
* Copyright 2013-2015, Hannah von Reth <vonreth@kde.org>
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
*
@@ -27,27 +27,15 @@
#include "TomahawkVersion.h"
#include <snore/core/application.h>
#include <snore/core/notification/icon.h>
#include <libsnore/application.h>
#include <libsnore/notification/icon.h>
#include <QApplication>
#include <QImage>
#include <QPixmap>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
namespace Qt
{
inline QString escape( const QString &x )
{
return x.toHtmlEscaped();
}
}
#else
// QTextDocument provides Qt::escape()
#include <QTextDocument>
#endif
namespace Tomahawk
{
@@ -61,27 +49,13 @@ SnoreNotifyPlugin::SnoreNotifyPlugin()
tDebug( LOGVERBOSE ) << Q_FUNC_INFO;
m_supportedPushTypes << InfoNotifyUser << InfoNowPlaying << InfoTrackUnresolved << InfoNowStopped << InfoInboxReceived;
m_snore = new Snore::SnoreCore();
m_snore->loadPlugins( Snore::SnorePlugin::BACKEND );
QString backend = qgetenv( "SNORE_BACKEND" ).constData();
if( backend.isEmpty() )
{
m_snore->setPrimaryNotificationBackend();
}
else
{
if( !m_snore->setPrimaryNotificationBackend( backend ) )
{
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Ivalid or unavailible Snore backend: " << backend << " availible backens: " << m_snore->notificationBackends();
m_snore->setPrimaryNotificationBackend();
}
}
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << m_snore->primaryNotificationBackend();
Snore::SnoreCore &snore = Snore::SnoreCore::instance();
snore.loadPlugins( Snore::SnorePlugin::Backend | Snore::SnorePlugin::SecondaryBackend );
snore.setDefaultSettingsValue("Silent", true, Snore::LocalSetting );
m_application = Snore::Application( qApp->applicationName(), m_defaultIcon );
m_application.hints().setValue( "windows_app_id", TOMAHAWK_APPLICATION_PACKAGE_NAME );
m_application.hints().setValue( "use-markup", true );
m_application.hints().setValue( "windows-app-id", TOMAHAWK_APPLICATION_PACKAGE_NAME );
m_application.hints().setValue( "desktop-entry", TOMAHAWK_APPLICATION_NAME );
addAlert( InfoNotifyUser, tr( "Notify User" ) );
@@ -90,9 +64,10 @@ SnoreNotifyPlugin::SnoreNotifyPlugin()
addAlert( InfoNowStopped, tr( "Playback Stopped" ) );
addAlert( InfoInboxReceived, tr( "You received a Song recommendation" ) );
m_snore->registerApplication( m_application );
snore.registerApplication( m_application );
snore.setDefaultApplication( m_application );
connect( m_snore, SIGNAL( actionInvoked( Snore::Notification ) ), this, SLOT( slotActionInvoked( Snore::Notification ) ) );
connect( &snore, SIGNAL( actionInvoked( Snore::Notification ) ), this, SLOT( slotActionInvoked( Snore::Notification ) ) );
}
@@ -100,8 +75,7 @@ SnoreNotifyPlugin::~SnoreNotifyPlugin()
{
tDebug( LOGVERBOSE ) << Q_FUNC_INFO;
m_snore->deregisterApplication( m_application );
m_snore->deleteLater();
Snore::SnoreCore::instance().deregisterApplication( m_application );
}
void
@@ -111,25 +85,19 @@ SnoreNotifyPlugin::pushInfo( Tomahawk::InfoSystem::InfoPushData pushData )
if ( !TomahawkSettings::instance()->songChangeNotificationEnabled() )
return;
if( m_snore->primaryNotificationBackend().isNull() )
{
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "no notification backend set";
return;
}
switch ( pushData.type )
{
case Tomahawk::InfoSystem::InfoTrackUnresolved:
notifyUser( Tomahawk::InfoSystem::InfoTrackUnresolved, tr( "The current track could not be resolved. %applicationName will pick back up with the next resolvable track from this source." ) );
notifyUser( Tomahawk::InfoSystem::InfoTrackUnresolved, tr( "The current track could not be resolved. %applicationName will pick back up with the next resolvable track from this source." ), m_defaultIcon );
return;
case Tomahawk::InfoSystem::InfoNotifyUser:
notifyUser( Tomahawk::InfoSystem::InfoNotifyUser,pushData.infoPair.second.toString() );
notifyUser( Tomahawk::InfoSystem::InfoNotifyUser,pushData.infoPair.second.toString(), m_defaultIcon );
return;
case Tomahawk::InfoSystem::InfoNowStopped:
notifyUser( Tomahawk::InfoSystem::InfoNowStopped, tr( "%applicationName stopped playback." ) );
notifyUser( Tomahawk::InfoSystem::InfoNowStopped, tr( "%applicationName stopped playback." ), m_defaultIcon );
return;
case Tomahawk::InfoSystem::InfoNowPlaying:
@@ -157,15 +125,10 @@ SnoreNotifyPlugin::slotActionInvoked( Snore::Notification n )
void
SnoreNotifyPlugin::notifyUser( Tomahawk::InfoSystem::InfoType type, const QString& messageText, Snore::Icon icon )
{
if(!icon.isValid())
{
icon = m_defaultIcon;
}
const Snore::Alert &alert = m_alerts[ type ];
Snore::Notification n( m_application , alert, alert.name(), messageText, icon );
m_snore->broadcastNotification( n );
Snore::SnoreCore::instance().broadcastNotification( n );
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "showing notification:" << messageText;
}
void
@@ -194,41 +157,28 @@ SnoreNotifyPlugin::nowPlaying( const QVariant& input )
QString messageText;
// If the window manager supports notification styling then use it.
if ( m_snore->primaryBackendSupportsRichtext() )
{
// Remark: If using xml-based markup in notifications, the supplied strings need to be escaped.
QString album;
if ( !hash[ "album" ].isEmpty() )
album = QString( "<br><i>%1</i> %2" ).arg( tr( "on", "'on' is followed by an album name" ) ).arg( Qt::escape( hash[ "album" ] ) );
messageText = tr( "%1%4 %2%3.", "%1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing, %4 is the preposition used to link track and artist ('by' in english)" )
.arg( Qt::escape( hash[ "title" ] ) )
.arg( Qt::escape( hash[ "artist" ] ) )
.arg( album )
.arg( QString( "<br><i>%1</i>" ).arg( tr( "by", "preposition to link track and artist" ) ) );
// Remark: If using xml-based markup in notifications, the supplied strings need to be escaped.
QString album;
if ( !hash[ "album" ].isEmpty() )
album = QString( "<br><i>%1</i> %2" ).arg( tr( "on", "'on' is followed by an album name" ) ).arg( hash[ "album" ].toHtmlEscaped() );
// Dirty hack(TM) so that KNotify/QLabel recognizes the message as Rich Text
messageText = QString( "<i></i>%1" ).arg( messageText );
}
else
{
QString album;
if ( !hash[ "album" ].isEmpty() )
album = QString( " %1" ).arg( tr( "on \"%1\"", "%1 is an album name" ).arg( hash[ "album" ] ) );
messageText = tr( "%1%4 %2%3.", "%1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing, %4 is the preposition used to link track and artist ('by' in english)" )
.arg( hash[ "title" ].toHtmlEscaped() )
.arg( hash[ "artist" ].toHtmlEscaped() )
.arg( album )
.arg( QString( "<br><i>%1</i>" ).arg( tr( "by", "preposition to link track and artist" ) ) );
messageText = tr( "\"%1\" by %2%3.", "%1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing" )
.arg( hash[ "title" ] )
.arg( hash[ "artist" ] )
.arg( album );
}
// Dirty hack(TM) so that KNotify/QLabel recognizes the message as Rich Text
messageText = QString( "<i></i>%1" ).arg( messageText );
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "sending message" << messageText;
// If there is a cover availble use it, else use Tomahawk logo as default.
Snore::Icon image;
Snore::Icon image = m_defaultIcon;
if ( map.contains( "cover" ) && map[ "cover" ].canConvert< QImage >() )
{
image = Snore::Icon( map[ "cover" ].value< QImage >() );
image = Snore::Icon( QPixmap::fromImage( map[ "cover" ].value< QImage >() ) );
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << image;
}
notifyUser( InfoNowPlaying, messageText, image );
@@ -259,26 +209,15 @@ SnoreNotifyPlugin::inboxReceived( const QVariant& input )
return;
QString messageText;
// If the window manager supports notification styling then use it.
if ( m_snore->primaryBackendSupportsRichtext() )
{
// Remark: If using xml-based markup in notifications, the supplied strings need to be escaped.
messageText = tr( "%1 sent you\n%2%4 %3.", "%1 is a nickname, %2 is a title, %3 is an artist, %4 is the preposition used to link track and artist ('by' in english)" )
.arg( Qt::escape( src["friendlyname"] ) )
.arg( Qt::escape( hash[ "title" ] ) )
.arg( Qt::escape( hash[ "artist" ] ) )
.arg( QString( "\n<i>%1</i>" ).arg( tr( "by", "preposition to link track and artist" ) ) );
// Remark: If using xml-based markup in notifications, the supplied strings need to be escaped.
messageText = tr( "%1 sent you\n%2%4 %3.", "%1 is a nickname, %2 is a title, %3 is an artist, %4 is the preposition used to link track and artist ('by' in english)" )
.arg( src["friendlyname"].toHtmlEscaped() )
.arg( hash[ "title" ].toHtmlEscaped() )
.arg( hash[ "artist" ].toHtmlEscaped() )
.arg( QString( "\n<i>%1</i>" ).arg( tr( "by", "preposition to link track and artist" ) ) );
// Dirty hack(TM) so that KNotify/QLabel recognizes the message as Rich Text
messageText = QString( "<i></i>%1" ).arg( messageText );
}
else
{
messageText = tr( "%1 sent you \"%2\" by %3.", "%1 is a nickname, %2 is a title, %3 is an artist" )
.arg( src["friendlyname"] )
.arg( hash[ "title" ] )
.arg( hash[ "artist" ] );
}
// Dirty hack(TM) so that KNotify/QLabel recognizes the message as Rich Text
messageText = QString( "<i></i>%1" ).arg( messageText );
Snore::Icon icon( RESPATH "images/inbox-512x512.png" );
notifyUser( Tomahawk::InfoSystem::InfoInboxReceived, messageText, icon );

View File

@@ -1,6 +1,6 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2013-2014, Patrick von Reth <vonreth@kde.org>
* Copyright 2013-2015, Hannah von Reth <vonreth@kde.org>
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
* Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
*
@@ -24,7 +24,7 @@
#include "../../InfoPluginDllMacro.h"
#include "infosystem/InfoSystem.h"
#include <snore/core/snore.h>
#include <libsnore/snore.h>
namespace Tomahawk
{
@@ -63,9 +63,8 @@ protected slots:
void slotActionInvoked(Snore::Notification n);
private:
void notifyUser( InfoType type, const QString &messageText, Snore::Icon icon = Snore::Icon() );
void notifyUser( InfoType type, const QString &messageText, Snore::Icon icon );
void addAlert( Tomahawk::InfoSystem::InfoType type, const QString &title );
Snore::SnoreCore *m_snore;
Snore::Application m_application;
Snore::Icon m_defaultIcon;
QHash< Tomahawk::InfoSystem::InfoType, Snore::Alert > m_alerts;

View File

@@ -34,7 +34,7 @@ public:
* Creates a Playdar HTTP interface
* @param ha Address to listen on
* @param port Port to listen on with HTTP
* @param sport Pot to listen on with HTTPS
* @param sport Port to listen on with HTTPS
* @param parent
*/
explicit PlaydarApi( QHostAddress ha, qint16 port, qint16 sport, QObject *parent = 0 );

View File

@@ -62,6 +62,8 @@ ActionCollection::~ActionCollection()
void
ActionCollection::initActions()
{
// ATTENTION: Don't set ApplicationSpecificRole for submenu actions: they won't show up on OS X (Qt 5.5)
QAction *latchOn = new QAction( tr( "&Listen Along" ), this );
latchOn->setIcon( ImageRegistry::instance()->icon( RESPATH "images/headphones.svg" ) );
m_actionCollection[ "latchOn" ] = latchOn;
@@ -147,7 +149,6 @@ ActionCollection::initActions()
#endif
m_actionCollection[ "crashNow" ] = new QAction( "Crash now...", this );
m_actionCollection[ "whatsnew_0_8" ] = new QAction( tr( "%applicationName 0.8" ) , this );
m_actionCollection[ "whatsnew_0_8" ]->setMenuRole( QAction::ApplicationSpecificRole );
m_actionCollection[ "reportBug" ] = new QAction( tr( "Report a Bug" ) , this );
m_actionCollection[ "getSupport" ] = new QAction( tr( "Get Support" ) , this );
m_actionCollection[ "helpTranslate" ] = new QAction( tr( "Help Us Translate" ) , this );

View File

@@ -33,6 +33,7 @@
#include <QReadWriteLock>
#include <QPixmapCache>
#include <QCoreApplication>
using namespace Tomahawk;
@@ -75,6 +76,7 @@ Album::get( const Tomahawk::artist_ptr& artist, const QString& name, bool autoCr
}
album_ptr album = album_ptr( new Album( name, artist ), &Album::deleteLater );
album->moveToThread( QCoreApplication::instance()->thread() );
album->setWeakRef( album.toWeakRef() );
album->loadId( autoCreate );
s_albumsByName.insert( key, album );

View File

@@ -36,6 +36,7 @@
#include <QReadWriteLock>
#include <QPixmapCache>
#include <QCoreApplication>
using namespace Tomahawk;
@@ -109,6 +110,7 @@ Artist::get( unsigned int id, const QString& name )
}
artist_ptr a = artist_ptr( new Artist( id, name ), &Artist::deleteLater );
a->moveToThread( QCoreApplication::instance()->thread() );
a->setWeakRef( a.toWeakRef() );
s_artistsByName.insert( key, a );

View File

@@ -67,7 +67,7 @@ AtticaManager::AtticaManager( QObject* parent )
connect( &m_manager, SIGNAL( providerAdded( Attica::Provider ) ), this, SLOT( providerAdded( Attica::Provider ) ) );
// resolvers
// m_manager.addProviderFile( QUrl( "http://bakery.tomahawk-player.org/resolvers/providers.xml" ) );
// m_manager.addProviderFile( QUrl( "http://v09.bakery.tomahawk-player.org/resolvers/providers.xml" ) );
const QString url = QString( "%1/resolvers/providers.xml?version=%2" ).arg( hostname() ).arg( TomahawkUtils::appFriendlyVersion() );
QNetworkReply* reply = Tomahawk::Utils::nam()->get( QNetworkRequest( QUrl( url ) ) );
@@ -116,7 +116,7 @@ AtticaManager::fetchMissingIcons()
QString
AtticaManager::hostname() const
{
return "http://bakery.tomahawk-player.org";
return "http://v09.bakery.tomahawk-player.org";
}

View File

@@ -37,6 +37,7 @@ set( libGuiSources
jobview/ErrorStatusMessage.cpp
jobview/IndexingJobItem.cpp
jobview/InboxJobItem.cpp
jobview/ScriptErrorStatusMessage.cpp
playlist/InboxModel.cpp
playlist/InboxView.cpp
@@ -199,7 +200,6 @@ list(APPEND libSources
MetaPlaylistInterface.cpp
Query.cpp
Result.cpp
ResultProvider.cpp
Source.cpp
Track.cpp
TrackData.cpp
@@ -418,7 +418,7 @@ include_directories(
${Boost_INCLUDE_DIR}
${LIBPORTFWD_INCLUDE_DIR}
${QUAZIP_INCLUDE_DIR}
${QUAZIP_INCLUDE_DIRS}
${QTKEYCHAIN_INCLUDE_DIRS}
)
@@ -501,7 +501,7 @@ set_target_properties(
AUTOMOC TRUE
VERSION ${TOMAHAWK_VERSION_SHORT}
SOVERSION ${TOMAHAWK_VERSION_SHORT}
OUTPUT_NAME ${TOMAHAWK_TARGET_NAME}
OUTPUT_NAME ${TOMAHAWK_BASE_TARGET_NAME}
)
@@ -524,7 +524,6 @@ ENDIF( UNIX AND NOT APPLE )
TARGET_LINK_LIBRARIES( ${TOMAHAWK_LIBRARY}
LINK_PRIVATE
${LIBVLC_LIBRARY}
${LIBVLCCORE_LIBRARY}
# Thirdparty shipped with tomahawk
${LIBPORTFWD_LIBRARIES}

View File

@@ -37,6 +37,9 @@
#include "utils/ImageRegistry.h"
#include "utils/Logger.h"
#include <QDesktopServices>
#include <QFileInfo>
using namespace Tomahawk;
@@ -51,7 +54,7 @@ ContextMenu::ContextMenu( QWidget* parent )
m_sigmap = new QSignalMapper( this );
connect( m_sigmap, SIGNAL( mapped( int ) ), SLOT( onTriggered( int ) ) );
m_supportedActions = ActionPlay | ActionQueue | ActionPlaylist | ActionCopyLink | ActionLove | ActionStopAfter | ActionPage | ActionEditMetadata | ActionSend;
clear();
}
@@ -69,7 +72,7 @@ ContextMenu::clear()
m_albums.clear();
m_artists.clear();
m_supportedActions = ActionPlay | ActionQueue | ActionPlaylist | ActionCopyLink | ActionLove | ActionStopAfter | ActionPage | ActionEditMetadata | ActionSend;
m_supportedActions = ActionPlay | ActionQueue | ActionPlaylist | ActionCopyLink | ActionLove | ActionStopAfter | ActionPage | ActionEditMetadata | ActionSend | ActionOpenFileManager;
}
@@ -216,10 +219,24 @@ ContextMenu::setQueries( const QList<Tomahawk::query_ptr>& queries )
addSeparator();
if ( m_supportedActions & ActionCopyLink && itemCount() == 1 )
{
m_sigmap->setMapping( addAction( tr( "&Copy Track Link" ) ), ActionCopyLink );
}
if ( m_supportedActions & ActionOpenFileManager &&
queries.length() == 1 &&
queries.first()->numResults() &&
queries.first()->results().first()->resolvedByCollection() &&
queries.first()->results().first()->resolvedByCollection()->isLocal() )
{
m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/folder.svg" ),
tr( "Open Folder in File Manager..." ) ), ActionOpenFileManager );
}
if ( m_supportedActions & ActionEditMetadata && itemCount() == 1 )
{
m_sigmap->setMapping( addAction( tr( "Properties..." ) ), ActionEditMetadata );
}
addSeparator();
@@ -239,6 +256,8 @@ ContextMenu::setQueries( const QList<Tomahawk::query_ptr>& queries )
m_sigmap->setMapping( addAction( tr( "Mark as &Listened" ) ), ActionMarkListened );
}
addSeparator();
if ( m_supportedActions & ActionDelete )
m_sigmap->setMapping( addAction( queries.count() > 1 ? tr( "&Remove Items" ) : tr( "&Remove Item" ) ), ActionDelete );
@@ -394,6 +413,15 @@ ContextMenu::onTriggered( int action )
}
break;
case ActionOpenFileManager:
{
result_ptr result = m_queries.first()->results().first();
QString path = QFileInfo( result->url() ).path();
tLog() << Q_FUNC_INFO << "open directory" << path;
QDesktopServices::openUrl( path );
}
break;
default:
emit triggered( action );
}

View File

@@ -37,21 +37,22 @@ Q_OBJECT
public:
enum MenuActions
{
ActionPlay = 1,
ActionQueue = 2,
ActionDelete = 4,
ActionCopyLink = 8,
ActionLove = 16,
ActionStopAfter = 32,
ActionPage = 64,
ActionTrackPage = 128,
ActionArtistPage = 256,
ActionAlbumPage = 512,
ActionEditMetadata = 1024,
ActionPlaylist = 2048,
ActionSend = 4096,
ActionMarkListened = 8192,
ActionDownload = 16384
ActionPlay = 1,
ActionQueue = 2,
ActionDelete = 4,
ActionCopyLink = 8,
ActionLove = 16,
ActionStopAfter = 32,
ActionPage = 64,
ActionTrackPage = 128,
ActionArtistPage = 256,
ActionAlbumPage = 512,
ActionEditMetadata = 1024,
ActionPlaylist = 2048,
ActionSend = 4096,
ActionMarkListened = 8192,
ActionDownload = 16384,
ActionOpenFileManager = 32768
};
explicit ContextMenu( QWidget* parent = 0 );

View File

@@ -217,7 +217,7 @@ DownloadJob::download()
arguments[ "url" ] = m_format.url;
// HACK: *shrug* WIP.
Tomahawk::ScriptJob* job = collection->scriptObject()->invoke( "getStreamUrlPromise", arguments );
Tomahawk::ScriptJob* job = collection->scriptObject()->invoke( "getStreamUrl", arguments );
connect( job, SIGNAL( done(QVariantMap) ), SLOT( onUrlRetrieved(QVariantMap) ) );
job->start();
}

View File

@@ -175,7 +175,7 @@ DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType
// Check Scriptresolvers
foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
{
if ( resolver->canParseUrl( url, ExternalResolver::Playlist ) )
if ( resolver->canParseUrl( url, ExternalResolver::UrlTypePlaylist ) )
return true;
}
}
@@ -201,7 +201,7 @@ DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType
// Check Scriptresolvers
foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
{
if ( resolver->canParseUrl( url, ExternalResolver::Track ) )
if ( resolver->canParseUrl( url, ExternalResolver::UrlTypeTrack ) )
return true;
}
}
@@ -218,7 +218,7 @@ DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType
// Check Scriptresolvers
foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
{
if ( resolver->canParseUrl( url, ExternalResolver::Album ) )
if ( resolver->canParseUrl( url, ExternalResolver::UrlTypeAlbum ) )
return true;
}
}
@@ -235,7 +235,7 @@ DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType
// Check Scriptresolvers
foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
{
if ( resolver->canParseUrl( url, ExternalResolver::Artist ) )
if ( resolver->canParseUrl( url, ExternalResolver::UrlTypeArtist ) )
return true;
}
}
@@ -263,9 +263,14 @@ bool
DropJob::validateLocalFiles(const QString &paths, const QString &suffix)
{
QStringList filePaths = paths.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
for ( QStringList::iterator it = filePaths.begin(); it != filePaths.end(); ++it )
QStringList::iterator it = filePaths.begin();
while ( it != filePaths.end() )
{
if ( !validateLocalFile( *it, suffix ) )
filePaths.erase( it );
it = filePaths.erase( it );
else
++it;
}
return !filePaths.isEmpty();
}
@@ -306,7 +311,7 @@ DropJob::isDropType( DropJob::DropType desired, const QMimeData* data )
// Check Scriptresolvers
foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
{
if ( resolver->canParseUrl( url, ExternalResolver::Playlist ) )
if ( resolver->canParseUrl( url, ExternalResolver::UrlTypePlaylist ) )
{
tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Accepting current drop as a playlist" << resolver->name();
return true;
@@ -763,7 +768,7 @@ DropJob::handleTrackUrls( const QString& urls )
{
foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
{
if ( resolver->canParseUrl( track, ExternalResolver::Any ) )
if ( resolver->canParseUrl( track, ExternalResolver::UrlTypeAny ) )
{
ScriptCommand_LookupUrl* cmd = new ScriptCommand_LookupUrl( resolver, track );
connect( cmd, SIGNAL( information( QString, QSharedPointer<QObject> ) ), this, SLOT( informationForUrl( QString, QSharedPointer<QObject> ) ) );

View File

@@ -167,7 +167,7 @@ GlobalActionManager::openUrl( const QString& url )
QList< QPointer< ExternalResolver > > possibleResolvers;
foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
{
if ( resolver->canParseUrl( url, ExternalResolver::Any ) )
if ( resolver->canParseUrl( url, ExternalResolver::UrlTypeAny ) )
{
canParse = true;
possibleResolvers << resolver;

View File

@@ -149,6 +149,15 @@ Pipeline::removeResolver( Resolver* r )
}
QList< Tomahawk::Resolver* >
Pipeline::resolvers() const
{
Q_D( const Pipeline );
return d->resolvers;
}
void
Pipeline::addResolver( Resolver* r )
{
@@ -323,6 +332,13 @@ Pipeline::resolve( QID qid, bool prioritized, bool temporaryQuery )
}
void
Pipeline::reportError( QID qid, Tomahawk::Resolver* r )
{
reportResults( qid, r, QList< result_ptr>() );
}
void
Pipeline::reportResults( QID qid, Tomahawk::Resolver* r, const QList< result_ptr >& results )
{
@@ -333,7 +349,7 @@ Pipeline::reportResults( QID qid, Tomahawk::Resolver* r, const QList< result_ptr
{
if ( !results.isEmpty() )
{
ResultProvider* resolvedBy = results[0]->resolvedBy();
Resolver* resolvedBy = results[0]->resolvedBy();
if ( resolvedBy )
{
tDebug() << "Result arrived too late for:" << qid << "by" << resolvedBy->name();
@@ -579,8 +595,8 @@ Pipeline::shunt( const query_ptr& q )
// we get here if we disable a resolver while a query is resolving
// OR we are just out of resolvers while query is still resolving
//since we seem to at least tried to kick off all of the resolvers,
//remove the '.keep' entry
// since we seem to at least tried to kick off all of the resolvers,
// remove the '.keep' entry
decQIDState( q, nullptr );
return;
}
@@ -620,7 +636,7 @@ Pipeline::checkQIDState( const Tomahawk::query_ptr& query )
Q_D( Pipeline );
QMutexLocker lock( &d->mut );
tDebug() << Q_FUNC_INFO << " " << query->id() << " " << d->qidsState.count( query->id() );
tDebug() << Q_FUNC_INFO << query->id() << d->qidsState.count( query->id() );
if ( d->qidsState.contains( query->id() ) )
{

View File

@@ -54,6 +54,7 @@ public:
unsigned int pendingQueryCount() const;
unsigned int activeQueryCount() const;
void reportError( QID qid, Tomahawk::Resolver* r );
void reportResults( QID qid, Tomahawk::Resolver* r, const QList< result_ptr >& results );
void reportAlbums( QID qid, const QList< album_ptr >& albums );
void reportArtists( QID qid, const QList< artist_ptr >& artists );
@@ -65,6 +66,7 @@ public:
QList< QPointer< ExternalResolver > > scriptResolvers() const;
Tomahawk::ExternalResolver* resolverForPath( const QString& scriptPath );
QList< Resolver* > resolvers() const;
void addResolver( Resolver* r );
void removeResolver( Resolver* r );

View File

@@ -33,6 +33,7 @@
#include <QtAlgorithms>
#include <QDebug>
#include <QCoreApplication>
using namespace Tomahawk;
@@ -47,6 +48,7 @@ Query::get( const QString& artist, const QString& track, const QString& album, c
autoResolve = false;
query_ptr q = query_ptr( new Query( Track::get( artist, track, album ), qid, autoResolve ), &QObject::deleteLater );
q->moveToThread( QCoreApplication::instance()->thread() );
q->setWeakRef( q.toWeakRef() );
if ( autoResolve )
@@ -403,12 +405,17 @@ Query::resultSorter( const result_ptr& left, const result_ptr& right )
{
return false;
}
if ( !right->isPreview() )
if ( left->isPreview() != right->isPreview() )
{
return false;
return !left->isPreview();
}
return true;
if ( left->resolvedBy() != nullptr && right->resolvedBy() != nullptr )
{
return left->resolvedBy()->weight() > right->resolvedBy()->weight();
}
return left->id() > right->id();
}
if ( left->isPreview() != right->isPreview() )
@@ -636,6 +643,11 @@ Query::howSimilar( const Tomahawk::result_ptr& r )
qTrackname = queryTrack()->trackSortname();
}
//Cleanup symbols for minor naming differences
qArtistname.remove(QRegExp(QString::fromUtf8("[-`~!@#$%^&*()_—+=|:;<>«»,.?/{}\'\"\\[\\]\\\\]")));
qTrackname.remove(QRegExp(QString::fromUtf8("[-`~!@#$%^&*()_—+=|:;<>«»,.?/{}\'\"\\[\\]\\\\]")));
qAlbumname.remove(QRegExp(QString::fromUtf8("[-`~!@#$%^&*()_—+=|:;<>«»,.?/{}\'\"\\[\\]\\\\]")));
// normal edit distance
const int artdist = TomahawkUtils::levenshtein( qArtistname, rArtistname );
const int trkdist = TomahawkUtils::levenshtein( qTrackname, rTrackname );

View File

@@ -35,6 +35,8 @@
#include "Track.h"
#include "Typedefs.h"
#include <QCoreApplication>
using namespace Tomahawk;
static QHash< QString, result_wptr > s_results;
@@ -69,6 +71,7 @@ Result::get( const QString& url, const track_ptr& track )
}
result_ptr r = result_ptr( new Result( url, track ), &Result::deleteLater );
r->moveToThread( QCoreApplication::instance()->thread() );
r->setWeakRef( r.toWeakRef() );
s_results.insert( url, r );
@@ -132,11 +135,19 @@ Result::deleteLater()
void
Result::onResolverRemoved( Tomahawk::Resolver* resolver )
{
m_mutex.lock();
if ( m_resolver.data() == resolver )
{
m_resolver = 0;
m_mutex.unlock();
emit statusChanged();
}
else
{
m_mutex.unlock();
}
}
@@ -150,6 +161,8 @@ Result::resolvedByCollection() const
QString
Result::url() const
{
QMutexLocker lock( &m_mutex );
return m_url;
}
@@ -157,6 +170,8 @@ Result::url() const
bool
Result::checked() const
{
QMutexLocker lock( &m_mutex );
return m_checked;
}
@@ -164,6 +179,8 @@ Result::checked() const
bool
Result::isPreview() const
{
QMutexLocker lock( &m_mutex );
return m_isPreview;
}
@@ -171,6 +188,8 @@ Result::isPreview() const
QString
Result::mimetype() const
{
QMutexLocker lock( &m_mutex );
return m_mimetype;
}
@@ -178,6 +197,8 @@ Result::mimetype() const
RID
Result::id() const
{
QMutexLocker lock( &m_mutex );
if ( m_rid.isEmpty() )
m_rid = uuid();
@@ -194,6 +215,8 @@ Result::isOnline() const
}
else
{
QMutexLocker lock( &m_mutex );
return !m_resolver.isNull();
}
}
@@ -214,24 +237,26 @@ Result::playable() const
QVariant
Result::toVariant() const
{
track_ptr t = track();
QVariantMap m;
m.insert( "artist", m_track->artist() );
m.insert( "album", m_track->album() );
m.insert( "track", m_track->track() );
m.insert( "artist", t->artist() );
m.insert( "album", t->album() );
m.insert( "track", t->track() );
m.insert( "source", friendlySource() );
m.insert( "mimetype", mimetype() );
m.insert( "size", size() );
m.insert( "bitrate", bitrate() );
m.insert( "duration", m_track->duration() );
m.insert( "duration", t->duration() );
// m.insert( "score", score() );
m.insert( "sid", id() );
m.insert( "discnumber", m_track->discnumber() );
m.insert( "albumpos", m_track->albumpos() );
m.insert( "discnumber", t->discnumber() );
m.insert( "albumpos", t->albumpos() );
m.insert( "preview", isPreview() );
m.insert( "purchaseUrl", purchaseUrl() );
if ( !m_track->composer().isEmpty() )
m.insert( "composer", m_track->composer() );
if ( !t->composer().isEmpty() )
m.insert( "composer", t->composer() );
return m;
}
@@ -240,20 +265,25 @@ Result::toVariant() const
QString
Result::toString() const
{
if ( m_track )
m_mutex.lock();
track_ptr track = m_track;
QString url = m_url;
m_mutex.unlock();
if ( track )
{
return QString( "Result(%1) %2 - %3%4 (%5)" )
.arg( id() )
.arg( m_track->artist() )
.arg( m_track->track() )
.arg( m_track->album().isEmpty() ? QString() : QString( " on %1" ).arg( m_track->album() ) )
.arg( m_url );
.arg( track->artist() )
.arg( track->track() )
.arg( track->album().isEmpty() ? QString() : QString( " on %1" ).arg( track->album() ) )
.arg( url );
}
else
{
return QString( "Result(%1) (%2)" )
.arg( id() )
.arg( m_url );
.arg( url );
}
}
@@ -261,6 +291,8 @@ Result::toString() const
Tomahawk::query_ptr
Result::toQuery()
{
QMutexLocker l( &m_mutex );
if ( m_query.isNull() )
{
query_ptr query = Tomahawk::Query::get( m_track );
@@ -270,12 +302,15 @@ Result::toQuery()
m_query = query->weakRef();
QList<Tomahawk::result_ptr> rl;
rl << weakRef().toStrongRef();
rl << m_ownRef.toStrongRef();
m_mutex.unlock();
query->addResults( rl );
m_mutex.lock();
query->setResolveFinished( true );
return query;
}
return m_query.toStrongRef();
}
@@ -295,9 +330,10 @@ Result::onOffline()
void
Result::setResolvedByCollection( const Tomahawk::collection_ptr& collection , bool emitOnlineEvents )
Result::setResolvedByCollection( const Tomahawk::collection_ptr& collection, bool emitOnlineEvents )
{
m_collection = collection;
if ( emitOnlineEvents )
{
Q_ASSERT( !collection.isNull() );
@@ -311,6 +347,8 @@ Result::setResolvedByCollection( const Tomahawk::collection_ptr& collection , bo
void
Result::setFriendlySource( const QString& s )
{
QMutexLocker lock( &m_mutex );
m_friendlySource = s;
}
@@ -318,6 +356,8 @@ Result::setFriendlySource( const QString& s )
void
Result::setPreview( bool isPreview )
{
QMutexLocker lock( &m_mutex );
m_isPreview = isPreview;
}
@@ -325,6 +365,8 @@ Result::setPreview( bool isPreview )
void
Result::setPurchaseUrl( const QString& u )
{
QMutexLocker lock( &m_mutex );
m_purchaseUrl = u;
}
@@ -332,6 +374,8 @@ Result::setPurchaseUrl( const QString& u )
void
Result::setLinkUrl( const QString& u )
{
QMutexLocker lock( &m_mutex );
m_linkUrl = u;
}
@@ -339,6 +383,8 @@ Result::setLinkUrl( const QString& u )
void
Result::setChecked( bool checked )
{
QMutexLocker lock( &m_mutex );
m_checked = checked;
}
@@ -346,6 +392,8 @@ Result::setChecked( bool checked )
void
Result::setMimetype( const QString& mimetype )
{
QMutexLocker lock( &m_mutex );
m_mimetype = mimetype;
}
@@ -353,6 +401,8 @@ Result::setMimetype( const QString& mimetype )
void
Result::setBitrate( unsigned int bitrate )
{
QMutexLocker lock( &m_mutex );
m_bitrate = bitrate;
}
@@ -360,6 +410,8 @@ Result::setBitrate( unsigned int bitrate )
void
Result::setSize( unsigned int size )
{
QMutexLocker lock( &m_mutex );
m_size = size;
}
@@ -367,6 +419,8 @@ Result::setSize( unsigned int size )
void
Result::setModificationTime( unsigned int modtime )
{
QMutexLocker lock( &m_mutex );
m_modtime = modtime;
}
@@ -374,6 +428,8 @@ Result::setModificationTime( unsigned int modtime )
void
Result::setTrack( const track_ptr& track )
{
QMutexLocker lock( &m_mutex );
m_track = track;
}
@@ -381,6 +437,8 @@ Result::setTrack( const track_ptr& track )
unsigned int
Result::fileId() const
{
QMutexLocker lock( &m_mutex );
return m_fileId;
}
@@ -390,6 +448,8 @@ Result::friendlySource() const
{
if ( resolvedByCollection().isNull() )
{
QMutexLocker lock( &m_mutex );
return m_friendlySource;
}
else
@@ -400,6 +460,8 @@ Result::friendlySource() const
QString
Result::purchaseUrl() const
{
QMutexLocker lock( &m_mutex );
return m_purchaseUrl;
}
@@ -407,6 +469,8 @@ Result::purchaseUrl() const
QString
Result::linkUrl() const
{
QMutexLocker lock( &m_mutex );
return m_linkUrl;
}
@@ -416,6 +480,8 @@ Result::sourceIcon( TomahawkUtils::ImageMode style, const QSize& desiredSize ) c
{
if ( resolvedByCollection().isNull() )
{
//QMutexLocker lock( &m_mutex );
const ExternalResolver* resolver = qobject_cast< ExternalResolver* >( m_resolver.data() );
if ( !resolver )
{
@@ -466,6 +532,8 @@ Result::sourceIcon( TomahawkUtils::ImageMode style, const QSize& desiredSize ) c
unsigned int
Result::bitrate() const
{
QMutexLocker lock( &m_mutex );
return m_bitrate;
}
@@ -473,6 +541,8 @@ Result::bitrate() const
unsigned int
Result::size() const
{
QMutexLocker lock( &m_mutex );
return m_size;
}
@@ -480,6 +550,8 @@ Result::size() const
unsigned int
Result::modificationTime() const
{
QMutexLocker lock( &m_mutex );
return m_modtime;
}
@@ -487,13 +559,17 @@ Result::modificationTime() const
void
Result::setFileId( unsigned int id )
{
QMutexLocker lock( &m_mutex );
m_fileId = id;
}
Tomahawk::ResultProvider*
Tomahawk::Resolver*
Result::resolvedBy() const
{
QMutexLocker lock( &m_mutex );
if ( !m_collection.isNull() )
return m_collection.data();
@@ -504,12 +580,16 @@ Result::resolvedBy() const
void
Result::setResolvedByResolver( Tomahawk::Resolver* resolver )
{
QMutexLocker lock( &m_mutex );
m_resolver = QPointer< Tomahawk::Resolver >( resolver );
}
QPointer< Resolver > Result::resolvedByResolver() const
{
QMutexLocker lock( &m_mutex );
return m_resolver;
}
@@ -525,16 +605,29 @@ Result::doneEditing()
track_ptr
Result::track() const
{
QMutexLocker lock( &m_mutex );
return m_track;
}
QList< DownloadFormat >
Result::downloadFormats() const
{
QMutexLocker lock( &m_mutex );
return m_formats;
}
void
Result::setDownloadFormats( const QList<DownloadFormat>& formats )
{
if ( formats.isEmpty() )
return;
QMutexLocker lock( &m_mutex );
m_formats.clear();
foreach ( const DownloadFormat& format, formats )
{
@@ -562,7 +655,7 @@ Result::setDownloadFormats( const QList<DownloadFormat>& formats )
void
Result::onSettingsChanged()
{
if ( TomahawkSettings::instance()->downloadsPreferredFormat().toLower() != m_formats.first().extension.toLower() )
if ( TomahawkSettings::instance()->downloadsPreferredFormat().toLower() != downloadFormats().first().extension.toLower() )
{
setDownloadFormats( downloadFormats() );
emit updated();
@@ -599,6 +692,8 @@ Result::onDownloadJobStateChanged( DownloadJob::TrackState newState, DownloadJob
QWeakPointer<Result>
Result::weakRef()
{
QMutexLocker lock( &m_mutex );
return m_ownRef;
}
@@ -606,5 +701,7 @@ Result::weakRef()
void
Result::setWeakRef( QWeakPointer<Result> weakRef )
{
QMutexLocker lock( &m_mutex );
m_ownRef = weakRef;
}

View File

@@ -21,7 +21,6 @@
#ifndef RESULT_H
#define RESULT_H
#include "ResultProvider.h"
#include "DownloadJob.h"
#include "utils/TomahawkUtils.h"
#include "Typedefs.h"
@@ -32,6 +31,7 @@
#include <QPixmap>
#include <QPointer>
#include <QVariant>
#include <QMutex>
class MetadataEditor;
@@ -86,9 +86,9 @@ public:
void setResolvedByResolver( Tomahawk::Resolver* resolver );
/**
* This is very bad. ResultProvider is not a QObject and thus can not be tracked by a qt smart pointer ... :-(
* TODO: Make this a smart pointer
*/
ResultProvider* resolvedBy() const;
Resolver* resolvedBy() const;
RID id() const;
bool isOnline() const;
@@ -132,7 +132,7 @@ public:
track_ptr track() const;
QList<DownloadFormat> downloadFormats() const { return m_formats; }
QList< DownloadFormat > downloadFormats() const;
void setDownloadFormats( const QList<DownloadFormat>& formats );
downloadjob_ptr downloadJob() const { return m_downloadJob; }
@@ -162,6 +162,8 @@ private:
explicit Result( const QString& url, const Tomahawk::track_ptr& track );
explicit Result();
mutable QMutex m_mutex;
mutable RID m_rid;
collection_wptr m_collection;
QPointer< Tomahawk::Resolver > m_resolver;

View File

@@ -929,6 +929,20 @@ TomahawkSettings::setVolume( unsigned int volume )
}
bool
TomahawkSettings::muted() const
{
return value( "audio/muted" ).toBool();
}
void
TomahawkSettings::setMuted( bool muted )
{
setValue( "audio/muted", muted );
}
QString
TomahawkSettings::proxyHost() const
{

View File

@@ -107,6 +107,9 @@ public:
unsigned int volume() const;
void setVolume( unsigned int volume );
bool muted() const;
void setMuted( bool muted );
/// Playlist stuff
QByteArray playlistColumnSizes( const QString& playlistid ) const;
void setPlaylistColumnSizes( const QString& playlistid, const QByteArray& state );

View File

@@ -37,6 +37,7 @@
#include <QtAlgorithms>
#include <QDateTime>
#include <QReadWriteLock>
#include <QCoreApplication>
using namespace Tomahawk;
@@ -92,6 +93,7 @@ Track::get( const QString& artist, const QString& track, const QString& album, c
}
track_ptr t = track_ptr( new Track( artist, track, album, albumArtist, duration, composer, albumpos, discnumber ), &Track::deleteLater );
t->moveToThread( QCoreApplication::instance()->thread() );
t->setWeakRef( t.toWeakRef() );
s_tracksByName.insert( key, t );

View File

@@ -19,8 +19,7 @@
#include "TrackData.h"
#include <QtAlgorithms>
#include <QReadWriteLock>
#include "audio/AudioEngine.h"
#include "collection/Collection.h"
@@ -41,6 +40,10 @@
#include "PlaylistEntry.h"
#include "SourceList.h"
#include <QtAlgorithms>
#include <QReadWriteLock>
#include <QCoreApplication>
using namespace Tomahawk;
QHash< QString, trackdata_wptr > TrackData::s_trackDatasByName = QHash< QString, trackdata_wptr >();
@@ -84,6 +87,7 @@ TrackData::get( unsigned int id, const QString& artist, const QString& track )
}
trackdata_ptr t = trackdata_ptr( new TrackData( id, artist, track ), &TrackData::deleteLater );
t->moveToThread( QCoreApplication::instance()->thread() );
t->setWeakRef( t.toWeakRef() );
s_trackDatasByName.insert( key, t );

View File

@@ -253,7 +253,7 @@ namespace Tomahawk
typedef QHash< QString, QString > InfoStringHash;
typedef QPair< QVariantMap, QVariant > PushInfoPair;
typedef QPointer< InfoPlugin > InfoPluginPtr;
typedef QSharedPointer< InfoPlugin > InfoPluginPtr;
}
namespace Network

View File

@@ -76,6 +76,8 @@ public:
*/
virtual bool addPageItem() const;
virtual bool isRemovable() const { return false; }
/**
* This page is actually a constant page that will be shown on every
* restart of Tomahawk until the user selects it to be removed.

View File

@@ -79,7 +79,7 @@ ResolverAccountFactory::createFromPath( const QString& path )
}
Account*
ResolverAccount*
ResolverAccountFactory::createFromPath( const QString& path, const QString& factory, bool isAttica )
{
qDebug() << "Creating ResolverAccount from path:" << path << "is attica" << isAttica;
@@ -110,57 +110,8 @@ ResolverAccountFactory::createFromPath( const QString& path, const QString& fact
if ( pathInfo.suffix() == "axe" )
{
QString uniqueName = uuid();
QDir dir( TomahawkUtils::extractScriptPayload( pathInfo.filePath(),
uniqueName,
MANUALRESOLVERS_DIR ) );
if ( !( dir.exists() && dir.isReadable() ) ) //decompression fubar
if ( !installAxe( realPath, configuration ) )
{
displayError( tr( "Resolver installation error: cannot open bundle." ) );
return 0;
}
if ( !dir.cd( "content" ) ) //more fubar
{
displayError( tr( "Resolver installation error: incomplete bundle." ) );
return 0;
}
QString metadataFilePath = dir.absoluteFilePath( "metadata.json" );
configuration = metadataFromJsonFile( metadataFilePath );
configuration[ "bundleDir" ] = uniqueName;
if ( !configuration[ "pluginName" ].isNull() && !configuration[ "pluginName" ].toString().isEmpty() )
{
dir.cdUp();
if ( !dir.cdUp() ) //we're in MANUALRESOLVERS_DIR
return 0;
QString name = configuration[ "pluginName" ].toString();
QString namePath = dir.absoluteFilePath( name );
QFileInfo npI( namePath );
if ( npI.exists() && npI.isDir() )
{
TomahawkUtils::removeDirectory( namePath );
}
dir.rename( uniqueName, name );
configuration[ "bundleDir" ] = name;
if ( !dir.cd( QString( "%1/content" ).arg( name ) ) ) //should work if it worked once
return 0;
}
expandPaths( dir, configuration );
realPath = configuration[ "path" ].toString();
if ( realPath.isEmpty() )
{
displayError( tr( "Resolver installation error: bad metadata in bundle." ) );
return 0;
}
}
@@ -221,6 +172,71 @@ ResolverAccountFactory::createFromPath( const QString& path, const QString& fact
}
bool
ResolverAccountFactory::installAxe( QString& realPath, QVariantHash& configuration )
{
const QFileInfo pathInfo( realPath );
QString uniqueName = uuid();
QDir dir( TomahawkUtils::extractScriptPayload( pathInfo.filePath(),
uniqueName,
MANUALRESOLVERS_DIR ) );
if ( !( dir.exists() && dir.isReadable() ) ) //decompression fubar
{
JobStatusView::instance()->model()->addJob( new ErrorStatusMessage(
tr( "Resolver installation error: cannot open bundle." ) ) );
return 0;
}
if ( !dir.cd( "content" ) ) //more fubar
{
JobStatusView::instance()->model()->addJob( new ErrorStatusMessage(
tr( "Resolver installation error: incomplete bundle." ) ) );
return 0;
}
QString metadataFilePath = dir.absoluteFilePath( "metadata.json" );
configuration = metadataFromJsonFile( metadataFilePath );
configuration[ "bundleDir" ] = uniqueName;
if ( !configuration[ "pluginName" ].isNull() && !configuration[ "pluginName" ].toString().isEmpty() )
{
dir.cdUp();
if ( !dir.cdUp() ) //we're in MANUALRESOLVERS_DIR
return 0;
QString name = configuration[ "pluginName" ].toString();
QString namePath = dir.absoluteFilePath( name );
QFileInfo npI( namePath );
if ( npI.exists() && npI.isDir() )
{
TomahawkUtils::removeDirectory( namePath );
}
dir.rename( uniqueName, name );
configuration[ "bundleDir" ] = name;
if ( !dir.cd( QString( "%1/content" ).arg( name ) ) ) //should work if it worked once
return 0;
}
expandPaths( dir, configuration );
realPath = configuration[ "path" ].toString();
if ( realPath.isEmpty() )
{
JobStatusView::instance()->model()->addJob( new ErrorStatusMessage(
tr( "Resolver installation error: bad metadata in bundle." ) ) );
return 0;
}
return true;
}
QVariantHash
ResolverAccountFactory::metadataFromJsonFile( const QString& path )
{
@@ -517,14 +533,15 @@ ResolverAccount::removeBundle()
}
void ResolverAccount::testConfig()
void
ResolverAccount::testConfig()
{
// HACK: move to JSAccount once we have that properly
JSResolver* resolver = qobject_cast< Tomahawk::JSResolver* >( m_resolver );
if ( resolver )
{
QVariantMap data = resolver->loadDataFromWidgets();
ScriptJob* job = resolver->scriptObject()->invoke( "_testConfig", data );
ScriptJob* job = resolver->scriptObject()->invoke( "testConfig", data );
connect( job, SIGNAL( done( QVariantMap ) ), SLOT( onTestConfig( QVariantMap ) ) );
job->start();
}
@@ -535,6 +552,13 @@ void ResolverAccount::testConfig()
}
ExternalResolverGui*
ResolverAccount::resolver() const
{
return m_resolver;
}
void
ResolverAccount::onTestConfig( const QVariantMap& result )
{

View File

@@ -33,6 +33,8 @@ class ExternalResolverGui;
namespace Accounts {
class ResolverAccount;
class DLLEXPORT ResolverAccountFactory : public AccountFactory
{
Q_OBJECT
@@ -53,7 +55,10 @@ public:
Account* createFromPath( const QString& path ) override;
// Internal use
static Account* createFromPath( const QString& path, const QString& factoryId, bool isAttica );
static ResolverAccount* createFromPath( const QString& path, const QString& factoryId, bool isAttica );
// YES, non const parameters!
static bool installAxe( QString& realPath, QVariantHash& configuration );
private:
static void displayError( const QString& error );
@@ -101,6 +106,8 @@ public:
void testConfig() override;
ExternalResolverGui* resolver() const;
private slots:
void resolverChanged();
void onTestConfig( const QVariantMap& result );

View File

@@ -71,7 +71,7 @@ LastFmAccount::LastFmAccount( const QString& accountId )
if ( infoPlugin() && Tomahawk::InfoSystem::InfoSystem::instance()->workerThread() )
{
infoPlugin().data()->moveToThread( Tomahawk::InfoSystem::InfoSystem::instance()->workerThread().data() );
infoPlugin()->moveToThread( Tomahawk::InfoSystem::InfoSystem::instance()->workerThread().data() );
Tomahawk::InfoSystem::InfoSystem::instance()->addInfoPlugin( infoPlugin() );
}
}
@@ -82,7 +82,6 @@ LastFmAccount::~LastFmAccount()
if ( m_infoPlugin )
{
Tomahawk::InfoSystem::InfoSystem::instance()->removeInfoPlugin( infoPlugin() );
delete m_infoPlugin;
}
delete m_resolver.data();
@@ -168,9 +167,11 @@ InfoPluginPtr
LastFmAccount::infoPlugin()
{
if ( m_infoPlugin.isNull() )
m_infoPlugin = QPointer< LastFmInfoPlugin >( new LastFmInfoPlugin( this ) );
{
m_infoPlugin = QSharedPointer< LastFmInfoPlugin>( new LastFmInfoPlugin( this ) );
}
return InfoPluginPtr( m_infoPlugin.data() );
return m_infoPlugin;
}
bool

View File

@@ -105,7 +105,7 @@ private:
void hookupResolver();
QPointer<Tomahawk::ExternalResolverGui> m_resolver;
QPointer<Tomahawk::InfoSystem::LastFmInfoPlugin> m_infoPlugin;
QSharedPointer<Tomahawk::InfoSystem::LastFmInfoPlugin> m_infoPlugin;
QPointer<LastFmConfig> m_configWidget;
};

View File

@@ -65,13 +65,6 @@ LastFmInfoPlugin::init()
return;
}
lastfm::ws::ApiKey = "7194b85b6d1f424fe1668173a78c0c4a";
lastfm::ws::SharedSecret = "ba80f1df6d27ae63e9cb1d33ccf2052f";
lastfm::ws::Username = m_account.data()->username();
lastfm::setNetworkAccessManager( Tomahawk::Utils::nam() );
m_pw = m_account.data()->password();
//HACK work around a bug in liblastfm---it doesn't create its config dir, so when it
// tries to write the track cache, it fails silently. until we have a fixed version, do this
// code taken from Amarok (src/services/lastfm/ScrobblerAdapter.cpp)
@@ -86,6 +79,17 @@ LastFmInfoPlugin::init()
m_badUrls << QUrl( "http://cdn.last.fm/flatness/catalogue/noimage" );
lastfm::ws::ApiKey = "7194b85b6d1f424fe1668173a78c0c4a";
lastfm::ws::SharedSecret = "ba80f1df6d27ae63e9cb1d33ccf2052f";
lastfm::setNetworkAccessManager( Tomahawk::Utils::nam() );
if ( !m_account.isNull() )
{
lastfm::ws::Username = m_account->username();
m_pw = m_account->password();
}
QTimer::singleShot( 0, this, SLOT( settingsChanged() ) );
}

View File

@@ -116,6 +116,12 @@ SpotifyAccount::~SpotifyAccount()
{
clearUser();
if ( m_infoPlugin )
{
Tomahawk::InfoSystem::InfoSystem::instance()->removeInfoPlugin( infoPlugin() );
}
if ( m_spotifyResolver.isNull() )
return;
@@ -135,7 +141,7 @@ SpotifyAccount::init()
if ( infoPlugin() && Tomahawk::InfoSystem::InfoSystem::instance()->workerThread() )
{
infoPlugin().data()->moveToThread( Tomahawk::InfoSystem::InfoSystem::instance()->workerThread().data() );
infoPlugin()->moveToThread( Tomahawk::InfoSystem::InfoSystem::instance()->workerThread().data() );
Tomahawk::InfoSystem::InfoSystem::instance()->addInfoPlugin( infoPlugin() );
}
@@ -366,10 +372,10 @@ SpotifyAccount::infoPlugin()
{
if ( m_infoPlugin.isNull() )
{
m_infoPlugin = QPointer< InfoSystem::SpotifyInfoPlugin >( new InfoSystem::SpotifyInfoPlugin( this ) );
m_infoPlugin = QSharedPointer< InfoSystem::SpotifyInfoPlugin >( new InfoSystem::SpotifyInfoPlugin( this ) );
}
return InfoSystem::InfoPluginPtr( m_infoPlugin.data() );
return m_infoPlugin;
}

View File

@@ -170,7 +170,7 @@ private:
QPointer<SpotifyAccountConfig> m_configWidget;
QPointer<QWidget> m_aboutWidget;
QPointer<ScriptResolver> m_spotifyResolver;
QPointer< InfoSystem::SpotifyInfoPlugin > m_infoPlugin;
QSharedPointer< InfoSystem::SpotifyInfoPlugin > m_infoPlugin;
QMap<QString, QPair<QObject*, QString> > m_qidToSlotMap;
QMap<QString, QVariant > m_qidToExtraData;

View File

@@ -277,7 +277,7 @@ SpotifyAccountConfig::showLoggedIn()
m_ui->verticalLayout->insertWidget( 1, m_loggedInUser, 0, Qt::AlignCenter );
}
qDebug() << "Showing logged in withuserame:" << m_verifiedUsername;
qDebug() << "Showing logged in with username:" << m_verifiedUsername;
m_loggedInUser->show();
m_loggedInUser->setText( tr( "Logged in as %1" ).arg( m_verifiedUsername ) );

View File

@@ -32,6 +32,8 @@
#include "playlist/SingleTrackPlaylistInterface.h"
#include "utils/Closure.h"
#include "utils/Logger.h"
#include "utils/NetworkReply.h"
#include "utils/NetworkAccessManager.h"
#include "Album.h"
#include "Artist.h"
@@ -40,6 +42,7 @@
#include "SourceList.h"
#include "TomahawkSettings.h"
#include "UrlHandler.h"
#include "resolvers/ScriptJob.h"
#include <QDir>
@@ -162,12 +165,19 @@ AudioEngine::AudioEngine()
d->s_instance = this;
tDebug() << "Init AudioEngine";
d->audioOutput = new AudioOutput(this);
d->audioOutput = new AudioOutput( this );
connect( d->audioOutput, SIGNAL( initialized() ), this, SIGNAL( initialized() ) );
connect( d->audioOutput, SIGNAL( stateChanged( AudioOutput::AudioState, AudioOutput::AudioState ) ), d_func(), SLOT( onStateChanged( AudioOutput::AudioState, AudioOutput::AudioState ) ) );
connect( d->audioOutput, SIGNAL( tick( qint64 ) ), SLOT( timerTriggered( qint64 ) ) );
connect( d->audioOutput, SIGNAL( positionChanged( float ) ), SLOT( onPositionChanged( float ) ) );
connect( d->audioOutput, SIGNAL( volumeChanged( qreal ) ), SLOT( onVolumeChanged( qreal ) ) );
connect( d->audioOutput, SIGNAL( mutedChanged( bool ) ), SIGNAL( mutedChanged( bool ) ) );
if ( TomahawkSettings::instance()->muted() )
{
mute();
}
setVolume( TomahawkSettings::instance()->volume() );
qRegisterMetaType< AudioErrorCode >("AudioErrorCode");
@@ -180,6 +190,7 @@ AudioEngine::~AudioEngine()
tDebug() << Q_FUNC_INFO;
TomahawkSettings::instance()->setVolume( volume() );
TomahawkSettings::instance()->setMuted( isMuted() );
delete d_ptr;
}
@@ -286,8 +297,11 @@ AudioEngine::stop( AudioErrorCode errorCode )
if ( d->waitingOnNewTrack )
sendWaitingNotification();
Tomahawk::InfoSystem::InfoPushData pushData( s_aeInfoIdentifier, Tomahawk::InfoSystem::InfoNowStopped, QVariant(), Tomahawk::InfoSystem::PushNoFlag );
Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( pushData );
if ( d->audioOutput->isInitialized() )
{
Tomahawk::InfoSystem::InfoPushData pushData( s_aeInfoIdentifier, Tomahawk::InfoSystem::InfoNowStopped, QVariant(), Tomahawk::InfoSystem::PushNoFlag );
Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( pushData );
}
}
@@ -452,6 +466,8 @@ AudioEngine::mute()
{
Q_D( AudioEngine );
d->audioOutput->setMuted( true );
emit volumeChanged( volume() );
}
@@ -460,6 +476,8 @@ AudioEngine::toggleMute()
{
Q_D( AudioEngine );
d->audioOutput->setMuted( !d->audioOutput->isMuted() );
emit volumeChanged( volume() );
}
@@ -559,6 +577,12 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result )
Q_D( AudioEngine );
tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : result->url() );
if ( !d->audioOutput->isInitialized() )
{
return;
}
if ( !result )
{
stop();
@@ -574,16 +598,64 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result )
setCurrentTrack( result );
if ( !TomahawkUtils::isLocalResult( d->currentTrack->url() ) && !TomahawkUtils::isHttpResult( d->currentTrack->url() )
&& !TomahawkUtils::isRtmpResult( d->currentTrack->url() ) )
ScriptJob* job = result->resolvedBy()->getStreamUrl( result );
connect( job, SIGNAL( done( QVariantMap ) ), SLOT( gotStreamUrl( QVariantMap ) ) );
job->setProperty( "result", QVariant::fromValue( result ) );
job->start();
}
void
AudioEngine::gotStreamUrl( const QVariantMap& data )
{
QString streamUrl = data[ "url" ].toString();
QVariantMap headers = data[ "headers" ].toMap();
Tomahawk::result_ptr result = sender()->property( "result" ).value<result_ptr>();
if ( streamUrl.isEmpty() || !( TomahawkUtils::isHttpResult( streamUrl ) || TomahawkUtils::isHttpsResult( streamUrl ) || TomahawkUtils::isRtmpResult( streamUrl ) ) )
{
performLoadIODevice( d->currentTrack, d->currentTrack->url() );
// Not an http(s) or RTMP URL, get IO device
QSharedPointer< QIODevice > sp;
performLoadIODevice( result, streamUrl );
}
else
{
QSharedPointer< QIODevice > io;
performLoadTrack( result, result->url(), io );
// TODO: just make this part of the http(s) IoDeviceFactory (?)
QUrl url = QUrl::fromEncoded( streamUrl.toUtf8() );
QNetworkRequest req( url );
QMap<QString, QString> parsedHeaders;
foreach ( const QString& key, headers.keys() )
{
Q_ASSERT_X( headers[key].canConvert( QVariant::String ), Q_FUNC_INFO, "Expected a Map of string for additional headers" );
if ( headers[key].canConvert( QVariant::String ) )
{
parsedHeaders.insert( key, headers[key].toString() );
}
}
foreach ( const QString& key, parsedHeaders.keys() )
{
req.setRawHeader( key.toLatin1(), parsedHeaders[key].toLatin1() );
}
tDebug() << "Creating a QNetworkReply with url:" << req.url().toString();
NetworkReply* reply = new NetworkReply( Tomahawk::Utils::nam()->get( req ) );
NewClosure( reply, SIGNAL( finalUrlReached() ), this, SLOT( gotRedirectedStreamUrl( Tomahawk::result_ptr, NetworkReply* )), result, reply );
}
sender()->deleteLater();
}
void
AudioEngine::gotRedirectedStreamUrl( const Tomahawk::result_ptr& result, NetworkReply* reply )
{
// std::functions cannot accept temporaries as parameters
QSharedPointer< QIODevice > sp ( reply->reply(), &QObject::deleteLater );
QString url = reply->reply()->url().toString();
reply->disconnectFromReply();
reply->deleteLater();
performLoadTrack( result, url, sp );
}
@@ -618,7 +690,7 @@ AudioEngine::performLoadIODevice( const result_ptr& result, const QString& url )
void
AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString url, QSharedPointer< QIODevice > io )
AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString& url, QSharedPointer< QIODevice > io )
{
if ( QThread::currentThread() != thread() )
{
@@ -900,9 +972,9 @@ AudioEngine::playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk:
void
AudioEngine::playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk::query_ptr& query )
{
if ( query->resolvingFinished() )
if ( query->resolvingFinished() || query->numResults( true ) )
{
if ( query->numResults() && query->results().first()->isOnline() )
if ( query->numResults( true ) )
{
playItem( playlist, query->results().first(), query );
return;
@@ -918,7 +990,7 @@ AudioEngine::playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk:
{
Pipeline::instance()->resolve( query );
NewClosure( query.data(), SIGNAL( resolvingFinished( bool ) ),
NewClosure( query.data(), SIGNAL( resultsChanged() ),
const_cast<AudioEngine*>(this), SLOT( playItem( Tomahawk::playlistinterface_ptr, Tomahawk::query_ptr ) ), playlist, query );
}
}

View File

@@ -28,6 +28,7 @@
#include "DllMacro.h"
class NetworkReply;
class AudioEnginePrivate;
class DLLEXPORT AudioEngine : public QObject
@@ -148,6 +149,8 @@ public slots:
void setShuffled( bool enabled );
signals:
void initialized();
void loading( const Tomahawk::result_ptr track );
void started( const Tomahawk::result_ptr track );
void finished( const Tomahawk::result_ptr track );
@@ -180,8 +183,12 @@ signals:
private slots:
void loadTrack( const Tomahawk::result_ptr& result ); //async!
void gotStreamUrl( const QVariantMap& data );
void gotRedirectedStreamUrl( const Tomahawk::result_ptr& result, NetworkReply* reply );
void performLoadIODevice( const Tomahawk::result_ptr& result, const QString& url ); //only call from loadTrack kthxbi
void performLoadTrack( const Tomahawk::result_ptr result, const QString url, QSharedPointer< QIODevice > io ); //only call from loadTrack or performLoadIODevice kthxbi
void performLoadTrack( const Tomahawk::result_ptr result, const QString& url, QSharedPointer< QIODevice > io ); //only call from loadTrack or performLoadIODevice kthxbi
void loadPreviousTrack();
void loadNextTrack();

View File

@@ -25,11 +25,13 @@
#include "audio/MediaStream.h"
#include "utils/Logger.h"
#include "utils/TomahawkUtils.h"
#include <QApplication>
#include <QVarLengthArray>
#include <QFile>
#include <QDir>
#include <QTimer>
#include <vlc/libvlc.h>
#include <vlc/libvlc_media.h>
@@ -58,6 +60,7 @@ AudioOutput::AudioOutput( QObject* parent )
, m_currentTime( 0 )
, m_totalTime( 0 )
, m_justSeeked( false )
, m_initialized( false )
, dspPluginCallback( nullptr )
, m_vlcInstance( nullptr )
, m_vlcPlayer( nullptr )
@@ -120,6 +123,11 @@ AudioOutput::AudioOutput( QObject* parent )
libvlc_MediaPlayerTitleChanged,
libvlc_MediaPlayerSnapshotTaken,
//libvlc_MediaPlayerLengthChanged,
#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(2, 2, 2, 0))
libvlc_MediaPlayerAudioVolume,
libvlc_MediaPlayerMuted,
libvlc_MediaPlayerUnmuted,
#endif
libvlc_MediaPlayerVout
};
const int eventCount = sizeof(events) / sizeof( *events );
@@ -128,7 +136,24 @@ AudioOutput::AudioOutput( QObject* parent )
libvlc_event_attach( manager, events[ i ], &AudioOutput::vlcEventCallback, this );
}
tDebug() << Q_FUNC_INFO << "Init OK";
// HACK: play silent ogg file and set volume on that to workaround vlc not allowing to set volume before a file is played
m_silenceFile.setFileName( RESPATH "sounds/silence.ogg" );
Q_ASSERT( m_silenceFile.exists() );
Q_ASSERT( m_silenceFile.open( QIODevice::ReadOnly ) );
setCurrentSource( new MediaStream( &m_silenceFile, true ) );
libvlc_media_player_play( m_vlcPlayer );
#if QT_VERSION >= QT_VERSION_CHECK(5,4,0)
// if the silence file did not play for 15 secs, we pretend the AudioOutput is initialized, to allow proper error reporting
QTimer::singleShot( 15000, [&]()
{
if ( !m_initialized ) {
m_initialized = true;
emit initialized();
}
} );
#endif
}
@@ -154,6 +179,28 @@ AudioOutput::~AudioOutput()
}
void
AudioOutput::onInitVlcEvent( const libvlc_event_t* event )
{
switch ( event->type )
{
case libvlc_MediaPlayerTimeChanged:
setVolume( volume() );
setMuted( isMuted() );
m_initialized = true;
m_silenceFile.close();
tDebug() << Q_FUNC_INFO << "Init OK";
emit initialized();
break;
default:
break;
}
}
void
AudioOutput::setAutoDelete( bool ad )
{
@@ -302,6 +349,13 @@ AudioOutput::setCurrentSource( MediaStream* stream )
}
bool
AudioOutput::isInitialized() const
{
return m_initialized;
}
AudioOutput::AudioState
AudioOutput::state() const
{
@@ -466,11 +520,9 @@ AudioOutput::setMuted( bool m )
tDebug() << Q_FUNC_INFO;
m_muted = m;
if ( m_muted )
{
libvlc_audio_set_volume( m_vlcPlayer, 0 );
}
else
libvlc_audio_set_mute( m_vlcPlayer, m );
if ( !m_muted )
{
libvlc_audio_set_volume( m_vlcPlayer, m_volume * 100.0 );
}
@@ -487,7 +539,7 @@ AudioOutput::volume() const
void
AudioOutput::setVolume( qreal vol )
{
tDebug() << Q_FUNC_INFO;
tDebug() << Q_FUNC_INFO << vol << m_muted;
m_volume = vol;
if ( !m_muted )
@@ -531,6 +583,20 @@ AudioOutput::onVlcEvent( const libvlc_event_t* event )
// Don't call stop() here - it will deadlock libvlc
setState( Error );
break;
#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(2, 2, 2, 0))
case libvlc_MediaPlayerAudioVolume:
m_volume = event->u.media_player_audio_volume.volume;
emit volumeChanged( volume() );
break;
case libvlc_MediaPlayerMuted:
m_muted = true;
emit mutedChanged( true );
break;
case libvlc_MediaPlayerUnmuted:
m_muted = false;
emit mutedChanged( false );
break;
#endif
case libvlc_MediaPlayerNothingSpecial:
case libvlc_MediaPlayerOpening:
case libvlc_MediaPlayerBuffering:
@@ -556,7 +622,14 @@ AudioOutput::vlcEventCallback( const libvlc_event_t* event, void* opaque )
AudioOutput* that = reinterpret_cast < AudioOutput * > ( opaque );
Q_ASSERT( that );
that->onVlcEvent( event );
if ( !that->isInitialized() )
{
that->onInitVlcEvent( event );
}
else
{
that->onVlcEvent( event );
}
}

View File

@@ -25,6 +25,8 @@
#include "DllMacro.h"
#include "Typedefs.h"
#include <QFile>
#include <functional>
struct libvlc_instance_t;
@@ -44,6 +46,7 @@ public:
explicit AudioOutput( QObject* parent = nullptr );
~AudioOutput();
bool isInitialized() const;
AudioState state() const;
void setCurrentSource( const QUrl& stream );
@@ -72,11 +75,16 @@ public:
public slots:
signals:
void initialized();
void stateChanged( AudioOutput::AudioState, AudioOutput::AudioState );
void tick( qint64 );
void positionChanged( float );
void volumeChanged( qreal volume );
void mutedChanged( bool );
private:
void onInitVlcEvent( const libvlc_event_t* event );
void setState( AudioState state );
void setCurrentTime( qint64 time );
void setCurrentPosition( float position );
@@ -99,6 +107,9 @@ private:
qint64 m_totalTime;
bool m_justSeeked;
bool m_initialized;
QFile m_silenceFile;
std::function< void( int state, int frameNumber, float* samples, int nb_channels, int nb_samples ) > dspPluginCallback;
libvlc_instance_t* m_vlcInstance;

View File

@@ -41,12 +41,16 @@ MediaStream::MediaStream( const QUrl &url )
}
MediaStream::MediaStream( QIODevice* device )
MediaStream::MediaStream( QIODevice* device, bool bufferingFinished )
: QObject( nullptr )
, m_type( IODevice )
, m_ioDevice ( device )
, m_bufferingFinished( bufferingFinished )
{
QObject::connect( m_ioDevice, SIGNAL( readChannelFinished() ), this, SLOT( bufferingFinished() ) );
if ( !bufferingFinished )
{
QObject::connect( m_ioDevice, SIGNAL( readChannelFinished() ), this, SLOT( bufferingFinished() ) );
}
}

View File

@@ -42,7 +42,7 @@ public:
MediaStream( QObject* parent = nullptr );
explicit MediaStream( const QUrl &url );
explicit MediaStream( QIODevice* device );
explicit MediaStream( QIODevice* device, bool bufferingFinished = false );
virtual ~MediaStream();
MediaType type() const;

View File

@@ -35,7 +35,7 @@ using namespace Tomahawk;
Collection::Collection( const source_ptr& source, const QString& name, QObject* parent )
: QObject( parent )
: Resolver( parent )
, m_name( name )
, m_lastmodified( 0 )
, m_changed( false )

View File

@@ -33,7 +33,7 @@
#include "collection/ArtistsRequest.h"
#include "collection/AlbumsRequest.h"
#include "collection/TracksRequest.h"
#include "../ResultProvider.h"
#include "../resolvers/Resolver.h"
#include "DllMacro.h"
@@ -46,7 +46,7 @@
namespace Tomahawk
{
class DLLEXPORT Collection : public QObject, public ResultProvider
class DLLEXPORT Collection : public Resolver
{
Q_OBJECT

View File

@@ -251,3 +251,32 @@ DatabaseCollection::stationCreated( const source_ptr& source, const QVariantList
}
/*
* Resolver interface
*
* We implement searching the database in the DatabaseResolver which avoids a n+1 query here.
* We can't simply let ScriptCollection inherit Collection and Resolver because both are QObjects,
* although Resolver doesn't need to be a QObject atm, blocking adding signals/slots to Resolver
* in future seems to me worse than violating Liskov's law here. ~ domme
*/
unsigned int
DatabaseCollection::timeout() const
{
return 0;
}
unsigned int
DatabaseCollection::weight() const
{
return 0;
}
void
DatabaseCollection::resolve( const Tomahawk::query_ptr& query )
{
Q_UNUSED( query );
Q_ASSERT(false);
}

View File

@@ -65,6 +65,11 @@ public:
int trackCount() const override;
QPixmap icon( const QSize& size ) const override;
// Resolver interface
unsigned int weight() const override;
unsigned int timeout() const override;
void resolve( const Tomahawk::query_ptr& query ) override;
public slots:
virtual void addTracks( const QList<QVariant>& newitems );
virtual void removeTracks( const QDir& dir );

View File

@@ -277,8 +277,8 @@ InfoSystem::addInfoPlugin( Tomahawk::InfoSystem::InfoPluginPtr plugin )
if ( plugin.data()->thread() != m_infoSystemWorkerThreadController->worker()->thread() )
{
tLog() << Q_FUNC_INFO << "The object must be moved to the worker thread first, see InfoSystem::workerThread(): " << plugin->friendlyName();
Q_ASSERT( false );
tDebug() << Q_FUNC_INFO << "The object must be moved to the worker thread first, see InfoSystem::workerThread()";
return;
}

View File

@@ -58,13 +58,7 @@ InfoSystemWorker::InfoSystemWorker()
InfoSystemWorker::~InfoSystemWorker()
{
tDebug() << Q_FUNC_INFO << " beginning";
Q_FOREACH( InfoPluginPtr plugin, m_plugins )
{
if( plugin )
delete plugin.data();
}
tDebug() << Q_FUNC_INFO << " finished";
tDebug() << Q_FUNC_INFO;
}
@@ -136,29 +130,6 @@ InfoSystemWorker::addInfoPlugin( Tomahawk::InfoSystem::InfoPluginPtr plugin )
emit updatedSupportedGetTypes( QSet< InfoType >::fromList( m_infoGetMap.keys() ) );
emit updatedSupportedPushTypes( QSet< InfoType >::fromList( m_infoPushMap.keys() ) );
connect( plugin.data(), SIGNAL( destroyed( QObject* ) ), SLOT( onInfoPluginDeleted() ) );
}
void
InfoSystemWorker::onInfoPluginDeleted()
{
foreach( const InfoPluginPtr& plugin, m_plugins )
{
if ( plugin.isNull() )
{
m_plugins.removeOne( plugin );
foreach( InfoType type, m_infoGetMap.keys() )
{
m_infoGetMap[type].removeOne( plugin );
}
foreach( InfoType type, m_infoPushMap.keys() )
{
m_infoPushMap[type].removeOne( plugin );
}
}
}
}

View File

@@ -72,7 +72,6 @@ public slots:
private slots:
void checkTimeoutsTimerFired();
void onInfoPluginDeleted();
private:
void registerInfoTypes( const InfoPluginPtr &plugin, const QSet< InfoType > &getTypes, const QSet< InfoType > &pushTypes );

View File

@@ -33,6 +33,12 @@ JobStatusItem::~JobStatusItem()
}
void
JobStatusItem::activated()
{
}
bool
JobStatusItem::allowMultiLine() const
{

View File

@@ -53,6 +53,8 @@ public:
virtual QString mainText() const = 0;
virtual QString rightColumnText() const { return QString(); };
virtual void activated();
/**
* If collapse item is true, sending multiple items of the same type will "collapse" them into one
* instead of showing each individually. In this case, the right column from the item will be ignored

View File

@@ -118,6 +118,7 @@ JobStatusView::setModel( JobStatusSortModel* m )
connect( m_view->model(), SIGNAL( customDelegateJobInserted( int, JobStatusItem* ) ), this, SLOT( customDelegateJobInserted( int, JobStatusItem* ) ) );
connect( m_view->model(), SIGNAL( customDelegateJobRemoved( int ) ), this, SLOT( customDelegateJobRemoved( int ) ) );
connect( m_view->model(), SIGNAL( refreshDelegates() ), this, SLOT( refreshDelegates() ) );
connect( m_view, SIGNAL( activated( QModelIndex ) ), this, SLOT( onItemActivated( QModelIndex ) ) );
foreach ( const QPointer<JobStatusItem> item, s_jobItems )
{
@@ -184,6 +185,21 @@ JobStatusView::refreshDelegates()
}
void
JobStatusView::onItemActivated( const QModelIndex& index )
{
QVariant itemVar = index.data( JobStatusModel::JobDataRole );
if ( !itemVar.canConvert< JobStatusItem* >() || !itemVar.value< JobStatusItem* >() )
{
tLog() << Q_FUNC_INFO << "unable to fetch JobStatusItem*";
return;
}
JobStatusItem* item = itemVar.value< JobStatusItem* >();
item->activated();
}
void
JobStatusView::checkCount()
{

View File

@@ -59,6 +59,7 @@ private slots:
void customDelegateJobInserted( int row, JobStatusItem* item );
void customDelegateJobRemoved( int row );
void refreshDelegates();
void onItemActivated( const QModelIndex& index );
private:
QListView* m_view;

View File

@@ -1,10 +1,10 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright (C) 2015 Dominik Schmidt <domme@tomahawk-player.org>
* Copyright 2016, Dominik Schmidt <domme@tomahawk-player.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
@@ -15,10 +15,23 @@
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ResultProvider.h"
using namespace Tomahawk;
#include "ScriptErrorStatusMessage.h"
#include "../utils/Logger.h"
ResultProvider::~ResultProvider()
ScriptErrorStatusMessage::ScriptErrorStatusMessage( const QString& message, Tomahawk::ScriptAccount* account )
: ErrorStatusMessage( tr( "Script Error: %1" ).arg( message ) )
, m_account( account )
{
}
void
ScriptErrorStatusMessage::activated()
{
if ( m_account.isNull() )
return;
tDebug() << "ScriptErrorStatusMessage clicked: " << mainText() << m_account->name();
m_account->showDebugger();
}

View File

@@ -1,10 +1,10 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright (C) 2015 Dominik Schmidt <domme@tomahawk-player.org>
* Copyright 2016, Dominik Schmidt <domme@tomahawk-player.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
@@ -15,28 +15,25 @@
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef TOMAHAWK_RESULTPROVIDER_H
#define TOMAHAWK_RESULTPROVIDER_H
#ifndef SCRIPTERRORSTATUSMESSAGE_H
#define SCRIPTERRORSTATUSMESSAGE_H
#include "ErrorStatusMessage.h"
#include "../resolvers/ScriptAccount.h"
#include "DllMacro.h"
class QPixmap;
class QString;
class QSize;
namespace Tomahawk
{
class DLLEXPORT ResultProvider
class DLLEXPORT ScriptErrorStatusMessage : public ErrorStatusMessage
{
Q_OBJECT
public:
virtual ~ResultProvider();
explicit ScriptErrorStatusMessage( const QString& scriptErrorMessage, Tomahawk::ScriptAccount* );
virtual QString name() const = 0;
virtual QPixmap icon( const QSize& size ) const = 0;
void activated() override;
private:
QPointer< Tomahawk::ScriptAccount > m_account;
};
}
#endif // TOMAHAWK_RESULTPROVIDER_H
#endif // SCRIPTERRORSTATUSMESSAGE_H

View File

@@ -118,6 +118,10 @@ ColumnView::setModel( QAbstractItemModel* model )
void
ColumnView::setTreeModel( TreeModel* model )
{
// HACK: without setting a new preview widget, the old preview widget is shown in the first column while loading the artists.
m_previewWidget = new ColumnViewPreviewWidget( this );
setPreviewWidget( m_previewWidget );
m_model = model;
if ( m_proxyModel )

View File

@@ -96,8 +96,10 @@ ContextView::ContextView( QWidget* parent, const QString& caption )
connect( m_captionLabel, SIGNAL( clicked() ), SIGNAL( closeClicked() ) );
connect( m_trackView, SIGNAL( querySelected( Tomahawk::query_ptr ) ), SLOT( onQuerySelected( Tomahawk::query_ptr ) ) );
connect( m_trackView, SIGNAL( querySelected( Tomahawk::query_ptr ) ), m_detailView, SLOT( setQuery( Tomahawk::query_ptr ) ) );
connect( m_trackView, SIGNAL( modelChanged() ), SLOT( onModelChanged() ) );
connect( m_trackView, SIGNAL( querySelected( Tomahawk::query_ptr ) ), m_detailView, SLOT( setQuery( Tomahawk::query_ptr ) ) );
connect( m_detailView, SIGNAL( downloadAll() ), SLOT( onDownloadAll() ) );
connect( m_detailView, SIGNAL( downloadCancel() ), SLOT( onDownloadCancel() ) );
TomahawkUtils::fixMargins( this );
}

View File

@@ -20,11 +20,15 @@
#include "GridItemDelegate.h"
#include <QApplication>
#include <QDesktopServices>
#include <QPainter>
#include <QAbstractItemView>
#include <QMouseEvent>
#include <QTimeLine>
#include "DownloadManager.h"
#include "DownloadJob.h"
#include "Artist.h"
#include "Query.h"
#include "Result.h"
@@ -35,12 +39,14 @@
#include "playlist/PlayableItem.h"
#include "playlist/PlayableProxyModel.h"
#include "widgets/HoverControls.h"
#include "widgets/DropDownButton.h"
#include "widgets/ImageButton.h"
#include "utils/TomahawkStyle.h"
#include "utils/TomahawkUtilsGui.h"
#include "utils/PixmapDelegateFader.h"
#include "utils/Closure.h"
#include "utils/AnimatedSpinner.h"
#include "utils/WebPopup.h"
#include "utils/DpiScaler.h"
#include "utils/Logger.h"
@@ -55,6 +61,7 @@ GridItemDelegate::GridItemDelegate( QAbstractItemView* parent, PlayableProxyMode
, m_model( proxy )
, m_itemWidth( 0 )
, m_showPosition( false )
, m_showBuyButtons( false )
, m_wordWrapping( false )
, m_margin( TomahawkUtils::DpiScaler::scaledY( parent, 32 ) )
{
@@ -93,7 +100,8 @@ GridItemDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelInde
if ( !m_wordWrapping )
return QSize( m_itemWidth, m_itemWidth + fm.height() + m_margin * 0.8 );
return QSize( m_itemWidth, m_itemWidth + fm.height() + fms.height() + m_margin * 0.8 );
const int buyButtonHeight = m_showBuyButtons ? 40 : 0;
return QSize( m_itemWidth, m_itemWidth + fm.height() + fms.height() + buyButtonHeight + m_margin * 0.8 );
}
}
@@ -275,6 +283,11 @@ GridItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option,
painter->setFont( f );
}
if ( m_showBuyButtons && !item->query().isNull() )
{
textRect.adjust( 0, 0, 0, -40 );
}
to.setAlignment( Qt::AlignLeft | Qt::AlignTop );
text = painter->fontMetrics().elidedText( top, Qt::ElideRight, textRect.width() - m_margin / 4 );
painter->drawText( textRect, text, to );
@@ -286,6 +299,7 @@ GridItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option,
}
painter->restore();
painter->save();
painter->setOpacity( 0.6 );
painter->setFont( m_smallFont );
@@ -304,6 +318,48 @@ GridItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option,
// Calculate rect of artist on-hover button click area
m_artistNameRects[ index ] = painter->fontMetrics().boundingRect( textRect, Qt::AlignLeft | Qt::AlignBottom, text );
painter->restore();
if ( m_showBuyButtons && !item->query().isNull() )
{
QRect r = textRect;
r.setY( textRect.y() + textRect.height() + 8 );
r.setHeight( 32 );
m_buyButtonRects[ index ] = r;
QString text;
if ( item->result() &&
( ( !item->result()->downloadFormats().isEmpty() && !DownloadManager::instance()->localFileForDownload( item->result()->downloadFormats().first().url.toString() ).isEmpty() ) ||
( item->result()->downloadJob() && item->result()->downloadJob()->state() == DownloadJob::Finished ) ) )
{
text = tr( "View in Finder" );
}
else if ( item->query() && item->query()->numResults( true ) && !item->query()->results().first()->downloadFormats().isEmpty() )
{
text = tr( "Download %1" ).arg( item->query()->results().first()->downloadFormats().first().extension.toUpper() );
}
else if ( item->query()->numResults( true ) && !item->query()->results().first()->purchaseUrl().isEmpty() )
{
text = tr( "Buy" );
}
if ( !item->result() || !item->result()->downloadJob() )
{
if ( !text.isEmpty() )
DropDownButton::drawPrimitive( painter, r, text, m_hoveringOverBuyButton == index, false );
}
else
{
painter->setPen( TomahawkStyle::PLAYLIST_PROGRESS_FOREGROUND.darker() );
painter->setBrush( TomahawkStyle::PLAYLIST_PROGRESS_BACKGROUND );
painter->drawRect( r.adjusted( 2, 2, -2, -2 ) );
painter->setPen( TomahawkStyle::PLAYLIST_PROGRESS_FOREGROUND );
painter->setBrush( TomahawkStyle::PLAYLIST_PROGRESS_FOREGROUND );
QRect fillp = r.adjusted( 3, 3, -3, -3 );
fillp.setWidth( float(fillp.width()) * ( float(item->result()->downloadJob()->progressPercentage()) / 100.0 ) );
painter->drawRect( fillp );
}
}
}
painter->restore();
@@ -362,6 +418,7 @@ GridItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const Q
const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
bool hoveringArtist = false;
bool hoveringAlbum = false;
bool hoveringBuyButton = false;
if ( m_artistNameRects.contains( index ) )
{
const QRect artistNameRect = m_artistNameRects[ index ];
@@ -372,6 +429,11 @@ GridItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const Q
const QRect albumNameRect = m_albumNameRects[ index ];
hoveringAlbum = albumNameRect.contains( ev->pos() );
}
if ( m_buyButtonRects.contains( index ) )
{
const QRect buyButtonRect = m_buyButtonRects[ index ];
hoveringBuyButton = buyButtonRect.contains( ev->pos() );
}
QRect coverRect = m_view->visualRect( index );
coverRect.setHeight( coverRect.width() );
@@ -379,7 +441,7 @@ GridItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const Q
if ( event->type() == QEvent::MouseMove )
{
if ( hoveringArtist || hoveringAlbum )
if ( hoveringArtist || hoveringAlbum || hoveringBuyButton )
m_view->setCursor( Qt::PointingHandCursor );
else
m_view->setCursor( Qt::ArrowCursor );
@@ -435,6 +497,17 @@ GridItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const Q
emit updateIndex( index );
}
if ( m_hoveringOverBuyButton != index || ( !hoveringBuyButton && m_hoveringOverBuyButton.isValid() ) )
{
emit updateIndex( m_hoveringOverBuyButton );
if ( hoveringBuyButton )
m_hoveringOverBuyButton = index;
else
m_hoveringOverBuyButton = QPersistentModelIndex();
emit updateIndex( index );
}
if ( m_hoverIndex != index || !hoveringCover )
{
@@ -544,6 +617,27 @@ GridItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const Q
}
}
if ( hoveringBuyButton )
{
if ( event->type() == QEvent::MouseButtonRelease )
{
PlayableItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( index ) );
if ( !item )
return false;
if ( item->query() && item->query()->numResults( true ) && !item->query()->results().first()->downloadFormats().isEmpty() )
{
m_view->edit( index );
return true;
}
else
{
WebPopup* popup = new WebPopup( item->query()->results().first()->purchaseUrl(), QSize( 400, 800 ) );
connect( item->query()->results().first().data(), SIGNAL( destroyed() ), popup, SLOT( close() ) );
}
}
}
return false;
}
@@ -555,6 +649,7 @@ GridItemDelegate::modelChanged()
m_albumNameRects.clear();
m_hoveringOverArtist = QPersistentModelIndex();
m_hoveringOverAlbum = QPersistentModelIndex();
m_hoveringOverBuyButton = QPersistentModelIndex();
m_hoverIndex = QPersistentModelIndex();
clearButtons();
@@ -687,6 +782,10 @@ GridItemDelegate::resetHoverIndex()
idx = m_hoveringOverAlbum;
m_hoveringOverAlbum = QPersistentModelIndex();
doUpdateIndex( idx );
idx = m_hoveringOverBuyButton;
m_hoveringOverBuyButton = QPersistentModelIndex();
doUpdateIndex( idx );
}
@@ -758,3 +857,97 @@ GridItemDelegate::eventFilter( QObject* obj, QEvent* event )
else
return QObject::eventFilter( obj, event );
}
QWidget*
GridItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
PlayableItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) );
Q_ASSERT( item );
if ( item->result() && !item->result()->downloadFormats().isEmpty() &&
!DownloadManager::instance()->localFileForDownload( item->result()->downloadFormats().first().url.toString() ).isEmpty() )
{
QDesktopServices::openUrl( QUrl::fromLocalFile( QFileInfo( DownloadManager::instance()->localFileForDownload( item->result()->downloadFormats().first().url.toString() ) ).absolutePath() ) );
}
else if ( item->result() && item->result()->downloadJob() && item->result()->downloadJob()->state() == DownloadJob::Finished )
{
QDesktopServices::openUrl( QUrl::fromLocalFile( QFileInfo( item->result()->downloadJob()->localFile() ).absolutePath() ) );
}
else if ( item->result() &&
!item->result()->downloadFormats().isEmpty() && !item->result()->downloadJob() )
{
QStringList formats;
foreach ( const DownloadFormat& format, item->result()->downloadFormats() )
{
formats << tr( "Download %1" ).arg( format.extension.toUpper() );
}
DropDownButton* editor = new DropDownButton( parent );
editor->addItems( formats );
NewClosure( editor, SIGNAL( clicked() ),
const_cast<GridItemDelegate*>(this), SLOT( addDownloadJob( const QModelIndex&, QWidget* ) ), index, (QWidget*)editor );
NewClosure( editor, SIGNAL( activated( int ) ),
const_cast<GridItemDelegate*>(this), SLOT( addDownloadJob( const QModelIndex&, QWidget* ) ), index, (QWidget*)editor );
return editor;
}
return 0;
}
void
GridItemDelegate::addDownloadJob( const QModelIndex& index, QWidget* editor )
{
PlayableItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) );
Q_ASSERT( item );
m_view->closePersistentEditor( index );
DropDownButton* cb = static_cast< DropDownButton* >(editor);
if ( !item->result()->downloadFormats().isEmpty() )
DownloadManager::instance()->addJob( item->result()->toDownloadJob( item->result()->downloadFormats().at( cb->currentIndex() ) ) );
}
void
GridItemDelegate::closeEditor( const QModelIndex& index, QWidget* editor )
{
PlayableItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) );
Q_ASSERT( item );
m_view->closePersistentEditor( index );
DropDownButton* cb = static_cast< DropDownButton* >(editor);
editor->deleteLater();
}
void
GridItemDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
QStyledItemDelegate::updateEditorGeometry( editor, option, index );
DropDownButton* comboBox = static_cast<DropDownButton*>(editor);
comboBox->resize( option.rect.size() - QSize( 8, 0 ) );
comboBox->move( option.rect.x() + 4, option.rect.y() );
if ( m_downloadDropDownRects.contains( index ) )
{
editor->setGeometry( m_downloadDropDownRects.value( index ) );
}
if ( !comboBox->property( "shownPopup" ).toBool() )
{
comboBox->showPopup();
comboBox->setProperty( "shownPopup", true );
}
}
void
GridItemDelegate::setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const
{
}

View File

@@ -48,6 +48,7 @@ public:
QSize itemSize() const;
void setItemWidth( int width ) { m_itemWidth = width; }
void setShowBuyButtons( bool enabled ) { m_showBuyButtons = enabled; }
public slots:
void resetHoverIndex();
@@ -61,6 +62,10 @@ protected:
bool editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index );
bool eventFilter( QObject* obj, QEvent* event );
QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
void setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const;
signals:
void updateIndex( const QModelIndex& idx );
@@ -82,6 +87,9 @@ private slots:
void fadingFrameChanged( const QPersistentModelIndex& );
void fadingFrameFinished( const QPersistentModelIndex& );
void closeEditor( const QModelIndex& index, QWidget* editor );
void addDownloadJob( const QModelIndex& index, QWidget* editor );
private:
QTimeLine* createTimeline( QTimeLine::Direction direction, int startFrame = 0 );
void clearButtons();
@@ -90,15 +98,19 @@ private:
PlayableProxyModel* m_model;
int m_itemWidth;
bool m_showPosition;
bool m_showBuyButtons;
bool m_wordWrapping;
mutable QHash< QPersistentModelIndex, QRect > m_artistNameRects;
mutable QHash< QPersistentModelIndex, QRect > m_albumNameRects;
mutable QHash< QPersistentModelIndex, QRect > m_buyButtonRects;
mutable QHash< QPersistentModelIndex, QRect > m_downloadDropDownRects;
mutable QHash< QPersistentModelIndex, QSharedPointer< Tomahawk::PixmapDelegateFader > > m_covers;
QPersistentModelIndex m_hoverIndex;
QPersistentModelIndex m_hoveringOverArtist;
QPersistentModelIndex m_hoveringOverAlbum;
QPersistentModelIndex m_hoveringOverBuyButton;
mutable QHash< QPersistentModelIndex, QWidget* > m_spinner;
mutable QHash< QPersistentModelIndex, HoverControls* > m_hoverControls;

View File

@@ -255,6 +255,8 @@ void GridView::wheelEvent( QWheelEvent* e )
// ^ scroll step is 1/8 of the estimated row height
QListView::wheelEvent( e );
m_delegate->resetHoverIndex();
}

View File

@@ -119,8 +119,9 @@ int
PlayableModel::columnCount( const QModelIndex& parent ) const
{
Q_UNUSED( parent );
Q_D( const PlayableModel );
return 13;
return d->header.length();
}
@@ -444,14 +445,21 @@ PlayableModel::flags( const QModelIndex& index ) const
if ( index.isValid() )
{
Qt::ItemFlags returnFlags = defaultFlags;
if ( index.column() == 0 )
{
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
returnFlags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
else if ( index.column() == PlayableModel::Download )
{
return Qt::ItemIsEditable | defaultFlags;
returnFlags |= Qt::ItemIsEditable | defaultFlags;
}
if ( areAllColumnsEditable() ) {
returnFlags |= Qt::ItemIsEditable;
}
return returnFlags;
}
return Qt::ItemIsDropEnabled | defaultFlags;
@@ -1019,7 +1027,7 @@ PlayableModel::appendAlbums( const QList< Tomahawk::album_ptr >& albums )
void
PlayableModel::appendAlbums( const Tomahawk::collection_ptr& collection )
{
emit loadingStarted();
startLoading();
insertAlbums( collection, rowCount( QModelIndex() ) );
}
@@ -1034,7 +1042,7 @@ PlayableModel::appendQueries( const QList< Tomahawk::query_ptr >& queries )
void
PlayableModel::appendTracks( const QList< Tomahawk::track_ptr >& tracks, const QList< Tomahawk::PlaybackLog >& logs )
{
emit loadingStarted();
startLoading();
QList< Tomahawk::query_ptr > queries;
foreach ( const track_ptr& track, tracks )
{
@@ -1048,7 +1056,7 @@ PlayableModel::appendTracks( const QList< Tomahawk::track_ptr >& tracks, const Q
void
PlayableModel::appendTracks( const Tomahawk::collection_ptr& collection )
{
emit loadingStarted();
startLoading();
insertTracks( collection, rowCount( QModelIndex() ) );
}
@@ -1171,6 +1179,22 @@ PlayableModel::setIcon( const QPixmap& pixmap )
}
void
PlayableModel::setAllColumnsEditable( bool editable )
{
Q_D( PlayableModel );
d->areAllColumnsEditable = editable;
}
bool
PlayableModel::areAllColumnsEditable() const
{
Q_D( const PlayableModel );
return d->areAllColumnsEditable;
}
int
PlayableModel::trackCount() const
{

View File

@@ -96,6 +96,11 @@ public:
virtual QPixmap icon() const;
virtual void setIcon( const QPixmap& pixmap );
// HACK: we need to set column 0 editable for DropDownButton in TrackView
void setAllColumnsEditable( bool editable );
bool areAllColumnsEditable() const;
virtual int trackCount() const;
virtual int itemCount() const;

View File

@@ -38,6 +38,7 @@ public:
, rootItem( new PlayableItem( 0 ) )
, readOnly( true )
, loading( _loading )
, areAllColumnsEditable( false )
{
}
@@ -58,6 +59,7 @@ private:
QStringList header;
bool loading;
bool areAllColumnsEditable;
};
#endif // PLAYABLEMODEL_P_H

View File

@@ -607,25 +607,7 @@ PlayableProxyModel::columnCount( const QModelIndex& parent ) const
{
Q_UNUSED( parent );
switch ( m_style )
{
case SingleColumn:
return 1;
break;
case Collection:
return 10;
break;
case Locker:
return 11;
break;
case Detailed:
default:
return 12;
break;
}
return m_headerStyle[ m_style ].length();
}
@@ -733,6 +715,13 @@ PlayableProxyModel::setFilter( const QString& pattern )
}
int
PlayableProxyModel::mapSourceColumnToColumn( PlayableModel::Columns column )
{
return m_headerStyle[ m_style ].indexOf( column );
}
void
PlayableProxyModel::setCurrentIndex( const QModelIndex& index )
{

View File

@@ -98,6 +98,8 @@ public:
virtual void setFilter( const QString& pattern );
virtual void updateDetailedInfo( const QModelIndex& index );
int mapSourceColumnToColumn( PlayableModel::Columns column );
signals:
void filterChanged( const QString& filter );

View File

@@ -23,6 +23,7 @@
#include <QAbstractTextDocumentLayout>
#include <QApplication>
#include <QDateTime>
#include <QDesktopServices>
#include <QMouseEvent>
#include <QPainter>
#include <QDesktopServices>
@@ -51,6 +52,7 @@
#include "utils/Closure.h"
#include "utils/TomahawkStyle.h"
#include "utils/TomahawkUtilsGui.h"
#include "utils/WebPopup.h"
#include "utils/Logger.h"
using namespace Tomahawk;
@@ -123,16 +125,16 @@ PlaylistItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem&
PlayableItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) );
Q_ASSERT( item );
if ( index.column() == PlayableModel::Download && item->result() && !item->result()->downloadFormats().isEmpty() &&
if ( /*index.column() == PlayableModel::Download &&*/ item->result() && !item->result()->downloadFormats().isEmpty() &&
!DownloadManager::instance()->localFileForDownload( item->result()->downloadFormats().first().url.toString() ).isEmpty() )
{
QDesktopServices::openUrl( QUrl::fromLocalFile( QFileInfo( DownloadManager::instance()->localFileForDownload( item->result()->downloadFormats().first().url.toString() ) ).absolutePath() ) );
}
else if ( index.column() == PlayableModel::Download && item->result() && item->result()->downloadJob() && item->result()->downloadJob()->state() == DownloadJob::Finished )
else if ( /*index.column() == PlayableModel::Download &&*/ item->result() && item->result()->downloadJob() && item->result()->downloadJob()->state() == DownloadJob::Finished )
{
QDesktopServices::openUrl( QUrl::fromLocalFile( QFileInfo( item->result()->downloadJob()->localFile() ).absolutePath() ) );
}
else if ( index.column() == PlayableModel::Download && item->result() &&
else if ( /*index.column() == PlayableModel::Download &&*/ item->result() &&
!item->result()->downloadFormats().isEmpty() && !item->result()->downloadJob() )
{
QStringList formats;
@@ -192,9 +194,14 @@ PlaylistItemDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionV
comboBox->resize( option.rect.size() - QSize( 8, 0 ) );
comboBox->move( option.rect.x() + 4, option.rect.y() );
if ( m_downloadDropDownRects.contains( index ) )
{
editor->setGeometry( m_downloadDropDownRects.value( index ) );
}
if ( !comboBox->property( "shownPopup" ).toBool() )
{
// comboBox->showPopup();
comboBox->showPopup();
comboBox->setProperty( "shownPopup", true );
}
}
@@ -291,7 +298,8 @@ PlaylistItemDelegate::paintDetailed( QPainter* painter, const QStyleOptionViewIt
}
else if ( !item->result()->downloadJob() )
{
DropDownButton::drawPrimitive( painter, optc.rect, optc.currentText );
DropDownButton::drawPrimitive( painter, optc.rect, optc.currentText, hoveringOver() == index, true );
/* QApplication::style()->drawComplexControl( QStyle::CC_ComboBox, &optc, painter, 0 );
optc.rect.adjust( 4, 0, 0, 0 );
QApplication::style()->drawControl( QStyle::CE_ComboBoxLabel, &optc, painter, 0 );*/
@@ -604,6 +612,7 @@ QRect
PlaylistItemDelegate::drawTrack( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QRect& rect, PlayableItem* item ) const
{
const track_ptr track = item->query()->track();
const bool hasOnlineResults = ( item->query()->numResults( true ) > 0 );
painter->save();
painter->setRenderHint( QPainter::TextAntialiasing );
@@ -630,21 +639,87 @@ PlaylistItemDelegate::drawTrack( QPainter* painter, const QStyleOptionViewItem&
QRect numberRect = QRect( r.x(), r.y(), numberWidth, r.height() );
QRect extraRect = QRect( r.x() + r.width() - durationWidth, r.y(), durationWidth, r.height() );
QRect stateRect;
if ( option.state & QStyle::State_Selected || hoveringOver() == index )
QRect stateRect = extraRect.adjusted( 0, 0, 0, 0 );
if ( option.state & QStyle::State_Selected || hoveringOver() == index )
{
int h = extraRect.height() / 3;
if ( track->loved() )
{
painter->save();
painter->setOpacity( 0.5 );
int h = extraRect.height() / 3;
stateRect = extraRect.adjusted( -16, extraRect.height() / 2 - h / 2, 0, 0 );
stateRect.setHeight( h );
stateRect.setWidth( stateRect.height() );
painter->drawPixmap( stateRect, ImageRegistry::instance()->pixmap( RESPATH "images/love.svg", stateRect.size() ) );
QRect r = stateRect.adjusted( -16, extraRect.height() / 2 - h / 2, 0, 0 );
r.setHeight( h );
r.setWidth( r.height() );
painter->drawPixmap( r, ImageRegistry::instance()->pixmap( RESPATH "images/love.svg", r.size() ) );
painter->restore();
stateWidth = stateRect.width() + 16;
stateWidth += r.width() + 16;
}
if ( hasOnlineResults && !item->query()->results().first()->purchaseUrl().isEmpty() )
{
QRect r = stateRect.adjusted( -stateWidth -144, 6, 0, -6 );
r.setWidth( 144 );
DropDownButton::drawPrimitive( painter, r, tr( "Buy" ), m_hoveringOverBuyButton == index, false );
m_buyButtonRects[ index ] = r;
stateWidth += r.width() + 16;
}
}
if ( hasOnlineResults && !item->query()->results().first()->downloadFormats().isEmpty() )
{
painter->save();
QStyleOptionComboBox optc;
optc.rect = stateRect.adjusted( -stateWidth -144, 6, 0, -6 );
optc.rect.setWidth( 144 );
m_downloadDropDownRects[ index ] = optc.rect;
stateWidth += optc.rect.width() + 16;
optc.editable = false;
optc.currentText = tr( "Download %1" ).arg( item->query()->results().first()->downloadFormats().first().extension.toUpper() );
optc.palette = m_view->palette();
if ( option.state & QStyle::State_Selected && option.state & QStyle::State_Active )
optc.state = QStyle::State_Active | QStyle::State_Selected | QStyle::State_Enabled;
else
optc.state = QStyle::State_Active | QStyle::State_Enabled;
if ( !DownloadManager::instance()->localFileForDownload( item->query()->results().first()->downloadFormats().first().url.toString() ).isEmpty() )
{
painter->setPen( optc.palette.text().color() );
const QString text = painter->fontMetrics().elidedText( tr( "View in Finder" ), Qt::ElideRight, optc.rect.width() - 3 );
painter->drawText( optc.rect, text, QTextOption( Qt::AlignCenter ) );
}
else if ( !item->query()->results().first()->downloadJob() )
{
DropDownButton::drawPrimitive( painter, optc.rect, optc.currentText, hoveringOver() == index, true );
}
else
{
if ( item->query()->results().first()->downloadJob()->state() == DownloadJob::Finished )
{
painter->setPen( optc.palette.text().color() );
const QString text = painter->fontMetrics().elidedText( tr( "View in Finder" ), Qt::ElideRight, optc.rect.width() - 3 );
painter->drawText( optc.rect, text, QTextOption( Qt::AlignCenter ) );
}
else
{
painter->setPen( TomahawkStyle::PLAYLIST_PROGRESS_FOREGROUND.darker() );
painter->setBrush( TomahawkStyle::PLAYLIST_PROGRESS_BACKGROUND );
painter->drawRect( optc.rect.adjusted( 2, 2, -2, -2 ) );
painter->setPen( TomahawkStyle::PLAYLIST_PROGRESS_FOREGROUND );
painter->setBrush( TomahawkStyle::PLAYLIST_PROGRESS_FOREGROUND );
QRect fillp = optc.rect.adjusted( 3, 3, -3, -3 );
fillp.setWidth( float(fillp.width()) * ( float(item->query()->results().first()->downloadJob()->progressPercentage()) / 100.0 ) );
painter->drawRect( fillp );
}
}
painter->restore();
}
const int remWidth = r.width() - numberWidth - durationWidth;
QRect titleRect = QRect( numberRect.x() + numberRect.width(), r.y(), (double)remWidth * 0.5, r.height() );
@@ -708,6 +783,7 @@ PlaylistItemDelegate::drawTrack( QPainter* painter, const QStyleOptionViewItem&
int h = extraRect.height() / 2;
QRect playIconRect = extraRect.adjusted( extraRect.width() - h - 8, h / 2, -8, -h / 2 );
playIconRect.setWidth( playIconRect.height() );
painter->drawPixmap( playIconRect, ImageRegistry::instance()->pixmap( RESPATH "images/play.svg", playIconRect.size() ) );
double duration = (double)AudioEngine::instance()->currentTrackTotalTime();
@@ -759,8 +835,11 @@ PlaylistItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, con
bool hoveringArtist = false;
bool hoveringInfo = false;
bool hoveringLove = false;
bool hoveringBuy = false;
bool hoveringDownloadDropDown = false;
Tomahawk::source_ptr hoveredAvatar;
QRect hoveredAvatarRect;
if ( m_infoButtonRects.contains( index ) )
{
const QRect infoRect = m_infoButtonRects[ index ];
@@ -779,6 +858,18 @@ PlaylistItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, con
const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
hoveringLove = loveRect.contains( ev->pos() );
}
if ( m_buyButtonRects.contains( index ) )
{
const QRect buyRect = m_buyButtonRects[ index ];
const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
hoveringBuy = buyRect.contains( ev->pos() );
}
if ( m_downloadDropDownRects.contains( index ) )
{
const QRect downloadDropDownRect = m_downloadDropDownRects[ index ];
const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
hoveringDownloadDropDown = downloadDropDownRect.contains( ev->pos() );
}
if ( m_avatarBoxRects.contains( index ) )
{
const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
@@ -796,7 +887,7 @@ PlaylistItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, con
if ( event->type() == QEvent::MouseMove )
{
if ( hoveringInfo || hoveringLove || hoveringArtist )
if ( hoveringInfo || hoveringLove || hoveringArtist || hoveringBuy )
m_view->setCursor( Qt::PointingHandCursor );
else
m_view->setCursor( Qt::ArrowCursor );
@@ -820,6 +911,23 @@ PlaylistItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, con
emit updateIndex( m_hoveringOverArtist );
m_hoveringOverArtist = QModelIndex();
}
if ( hoveringBuy && m_hoveringOverBuyButton != index )
{
QPersistentModelIndex ti = m_hoveringOverBuyButton;
m_hoveringOverBuyButton = index;
PlayableItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( ti ) );
item->requestRepaint();
emit updateIndex( m_hoveringOverBuyButton );
}
if ( !hoveringBuy && m_hoveringOverBuyButton.isValid() )
{
QPersistentModelIndex ti = m_hoveringOverBuyButton;
m_hoveringOverBuyButton = QModelIndex();
PlayableItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( ti ) );
item->requestRepaint();
}
if ( m_hoveringOver != index )
{
@@ -849,6 +957,11 @@ PlaylistItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, con
{
item->query()->queryTrack()->setLoved( !item->query()->queryTrack()->loved() );
}
else if ( hoveringBuy )
{
WebPopup* popup = new WebPopup( item->query()->results().first()->purchaseUrl(), QSize( 400, 800 ) );
connect( item->query()->results().first().data(), SIGNAL( destroyed() ), popup, SLOT( close() ) );
}
else if ( hoveringInfo )
{
if ( m_model->style() == PlayableProxyModel::SingleColumn )
@@ -883,9 +996,11 @@ PlaylistItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, con
}
}
}
else if ( m_view->proxyModel()->style() == PlayableProxyModel::Locker && index.column() == PlayableModel::Download )
else if ( ( m_view->proxyModel()->style() == PlayableProxyModel::Locker && index.column() == PlayableModel::Download ) || hoveringDownloadDropDown )
{
m_model->sourceModel()->setAllColumnsEditable( true );
m_view->edit( index );
m_model->sourceModel()->setAllColumnsEditable( false );
return true;
}
@@ -900,15 +1015,17 @@ PlaylistItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, con
void
PlaylistItemDelegate::resetHoverIndex()
{
if ( !m_model )
if ( !m_model || !m_hoveringOver.isValid() )
return;
QPersistentModelIndex idx = m_hoveringOver;
m_hoveringOver = QModelIndex();
m_hoveringOverArtist = QModelIndex();
m_hoveringOverBuyButton = QModelIndex();
m_infoButtonRects.clear();
m_loveButtonRects.clear();
m_buyButtonRects.clear();
m_artistNameRects.clear();
QModelIndex itemIdx = m_model->mapToSource( idx );
@@ -920,7 +1037,6 @@ PlaylistItemDelegate::resetHoverIndex()
}
emit updateIndex( idx );
m_view->repaint();
}

View File

@@ -116,10 +116,13 @@ private:
mutable QHash< QPersistentModelIndex, QSharedPointer< Tomahawk::PixmapDelegateFader > > m_pixmaps;
mutable QHash< QPersistentModelIndex, QRect > m_infoButtonRects;
mutable QHash< QPersistentModelIndex, QRect > m_loveButtonRects;
mutable QHash< QPersistentModelIndex, QRect > m_buyButtonRects;
mutable QHash< QPersistentModelIndex, QRect > m_downloadDropDownRects;
mutable QHash< QPersistentModelIndex, QRect > m_artistNameRects;
mutable QHash< QPersistentModelIndex, QHash< Tomahawk::source_ptr, QRect > > m_avatarBoxRects;
QPersistentModelIndex m_hoveringOver;
QPersistentModelIndex m_hoveringOverArtist;
QPersistentModelIndex m_hoveringOverBuyButton;
mutable QPersistentModelIndex m_nowPlaying;
TrackView* m_view;

View File

@@ -19,6 +19,7 @@
#include "TrackDetailView.h"
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QSizePolicy>
#include <QVBoxLayout>
@@ -35,6 +36,7 @@
#include "utils/ImageRegistry.h"
#include "utils/TomahawkUtilsGui.h"
#include "utils/Closure.h"
#include "utils/WebPopup.h"
#include "utils/Logger.h"
using namespace Tomahawk;
@@ -42,6 +44,7 @@ using namespace Tomahawk;
TrackDetailView::TrackDetailView( QWidget* parent )
: QWidget( parent )
, DpiScaler( this )
, m_buyButtonVisible( false )
{
setFixedWidth( scaledX( 200 ) );
setContentsMargins( 0, 0, 0, 0 );
@@ -118,11 +121,21 @@ TrackDetailView::TrackDetailView( QWidget* parent )
TomahawkStyle::styleScrollBar( m_resultsScrollArea->verticalScrollBar() );
m_resultsScrollArea->hide();
m_buyButton = new QPushButton;
m_buyButton->setStyleSheet( "QPushButton:hover { font-size: 12px; color: #ffffff; background: #000000; border-style: solid; border-radius: 0px; border-width: 2px; border-color: #2b2b2b; }"
"QPushButton { font-size: 12px; color: #ffffff; background-color: #000000; border-style: solid; border-radius: 0px; border-width: 0px; }" );
m_buyButton->setMinimumHeight( 30 );
m_buyButton->setText( tr( "Buy Album" ) );
m_buyButton->setVisible( false );
connect( m_buyButton, SIGNAL( clicked() ), SLOT( onBuyButtonClicked() ) );
QVBoxLayout* layout = new QVBoxLayout;
TomahawkUtils::unmarginLayout( layout );
layout->addWidget( m_playableCover );
layout->addSpacerItem( new QSpacerItem( 0, 8, QSizePolicy::Minimum, QSizePolicy::Fixed ) );
layout->addWidget( m_nameLabel );
layout->addSpacerItem( new QSpacerItem( 0, 4, QSizePolicy::Minimum, QSizePolicy::Fixed ) );
layout->addWidget( m_buyButton );
layout->addWidget( m_dateLabel );
layout->addWidget( m_infoBox );
layout->addSpacerItem( new QSpacerItem( 0, 32, QSizePolicy::Minimum, QSizePolicy::Fixed ) );
@@ -134,6 +147,9 @@ TrackDetailView::TrackDetailView( QWidget* parent )
setLayout( layout );
setQuery( query_ptr() );
connect( DownloadManager::instance(), SIGNAL( stateChanged( DownloadManager::DownloadManagerState, DownloadManager::DownloadManagerState ) ),
SLOT( onDownloadManagerStateChanged( DownloadManager::DownloadManagerState, DownloadManager::DownloadManagerState ) ) );
}
@@ -154,6 +170,10 @@ TrackDetailView::setQuery( const Tomahawk::query_ptr& query )
{
if ( m_query )
{
if ( m_query->track()->albumPtr() && !m_query->track()->albumPtr()->name().isEmpty() )
{
disconnect( m_query->track()->albumPtr().data(), SIGNAL( updated() ), this, SLOT( onAlbumUpdated() ) );
}
disconnect( m_query->track().data(), SIGNAL( updated() ), this, SLOT( onCoverUpdated() ) );
disconnect( m_query->track().data(), SIGNAL( socialActionsLoaded() ), this, SLOT( onSocialActionsLoaded() ) );
disconnect( m_query.data(), SIGNAL( resultsChanged() ), this, SLOT( onResultsChanged() ) );
@@ -164,6 +184,7 @@ TrackDetailView::setQuery( const Tomahawk::query_ptr& query )
onResultsChanged();
setSocialActions();
onCoverUpdated();
onAlbumUpdated();
if ( !query )
{
@@ -174,20 +195,85 @@ TrackDetailView::setQuery( const Tomahawk::query_ptr& query )
m_dateLabel->setText( tr( "Unknown Release-Date" ) );
connect( m_query->track().data(), SIGNAL( updated() ), SLOT( onCoverUpdated() ) );
connect( m_query->track().data(), SIGNAL( socialActionsLoaded() ), SLOT( onSocialActionsLoaded() ) );
connect( m_query.data(), SIGNAL( resultsChanged() ), SLOT( onResultsChanged() ) );
connect( m_query.data(), SIGNAL( resultsChanged() ), SLOT( onAlbumUpdated() ) );
}
void
TrackDetailView::onAlbumUpdated()
{
if ( !m_query )
return;
if ( m_query->track()->albumPtr() && !m_query->track()->albumPtr()->name().isEmpty() )
{
m_nameLabel->setType( QueryLabel::Album );
m_nameLabel->setAlbum( m_query->track()->albumPtr() );
connect( m_query->track()->albumPtr().data(), SIGNAL( updated() ), SLOT( onAlbumUpdated() ), Qt::UniqueConnection );
if ( m_buyButtonVisible )
{
if ( m_query->track()->albumPtr()->purchased() )
{
m_buyButton->setText( tr( "Download Album" ) );
m_buyButton->setVisible( true );
}
else
{
m_buyButton->setText( tr( "Buy Album" ) );
m_buyButton->setVisible( !m_query->track()->albumPtr()->purchaseUrl().isEmpty() );
}
}
}
else
{
m_nameLabel->setType( QueryLabel::Artist );
m_nameLabel->setArtist( m_query->track()->artistPtr() );
m_buyButton->setVisible( false );
}
}
void
TrackDetailView::onBuyButtonClicked()
{
if ( DownloadManager::instance()->state() == DownloadManager::Running )
{
emit downloadCancel();
return;
}
connect( m_query->track().data(), SIGNAL( updated() ), SLOT( onCoverUpdated() ) );
connect( m_query->track().data(), SIGNAL( socialActionsLoaded() ), SLOT( onSocialActionsLoaded() ) );
connect( m_query.data(), SIGNAL( resultsChanged() ), SLOT( onResultsChanged() ) );
if ( m_query && m_query->track()->albumPtr() )
{
if ( m_query->track()->albumPtr()->purchased() )
{
emit downloadAll();
}
else
{
WebPopup* popup = new WebPopup( m_query->track()->albumPtr()->purchaseUrl(), QSize( 400, 800 ) );
connect( m_query->track()->albumPtr().data(), SIGNAL( destroyed() ), popup, SLOT( close() ) );
}
}
}
void
TrackDetailView::onDownloadManagerStateChanged( DownloadManager::DownloadManagerState newState, DownloadManager::DownloadManagerState oldState )
{
tDebug() << Q_FUNC_INFO;
if ( newState == DownloadManager::Running )
{
m_buyButton->setText( tr( "Cancel Download" ) );
}
else
{
onAlbumUpdated();
}
}
@@ -262,8 +348,15 @@ TrackDetailView::onResultsChanged()
resolverLabel->setFont( f );
resolverLabel->setStyleSheet( "QLabel { color: rgba( 0, 0, 0, 50% ) }" );
resolverLabel->setText( QString( "%1 - %2" ).arg( result->track()->track() ).arg( result->track()->artist() ) );
resolverLabel->setToolTip( QString( "%1 by %2%3" ).arg( result->track()->track() ).arg( result->track()->artist() )
.arg( !result->track()->album().isEmpty() ? QString( " " ) + tr( "on %1" ).arg( result->track()->album() ) : QString() ) );
resolverLabel->setToolTip(
QString( "%1 by %2%3 (%4)" )
.arg( result->track()->track() )
.arg( result->track()->artist() )
.arg( !result->track()->album().isEmpty() ? QString( " " ) + tr( "on %1" ).arg( result->track()->album() ) : QString() )
.arg( result->friendlySource() )
);
;
resolverLabel->setFixedWidth( width() - 32 - 4 );
NewClosure( resolverLabel, SIGNAL( clicked() ), const_cast< AudioEngine* >( AudioEngine::instance() ),
@@ -297,3 +390,10 @@ TrackDetailView::onResultsChanged()
m_resultsScrollArea->hide();
}
}
void
TrackDetailView::setBuyButtonVisible( bool visible )
{
m_buyButtonVisible = visible;
}

View File

@@ -23,6 +23,7 @@
#include "Query.h"
#include "utils/DpiScaler.h"
#include "DownloadManager.h"
#include "DllMacro.h"
class QLabel;
@@ -30,6 +31,7 @@ class CaptionLabel;
class PlayableCover;
class QueryLabel;
class QScrollArea;
class QPushButton;
class DLLEXPORT TrackDetailView : public QWidget, private TomahawkUtils::DpiScaler
{
@@ -39,21 +41,29 @@ public:
explicit TrackDetailView( QWidget* parent = 0 );
~TrackDetailView();
void setBuyButtonVisible( bool visible );
public slots:
virtual void setQuery( const Tomahawk::query_ptr& query );
void setPlaylistInterface( const Tomahawk::playlistinterface_ptr& playlistInterface );
signals:
void downloadAll();
void downloadCancel();
protected:
protected slots:
private slots:
void onAlbumUpdated();
void onCoverUpdated();
void onSocialActionsLoaded();
void onResultsChanged();
void onBuyButtonClicked();
void onDownloadManagerStateChanged( DownloadManager::DownloadManagerState newState, DownloadManager::DownloadManagerState oldState );
private:
void setSocialActions();
@@ -65,6 +75,8 @@ private:
QLabel* m_lovedIcon;
QLabel* m_lovedLabel;
CaptionLabel* m_resultsBoxLabel;
QPushButton* m_buyButton;
bool m_buyButtonVisible;
QWidget* m_infoBox;
QWidget* m_resultsBox;

View File

@@ -23,7 +23,9 @@
#include "PlayableModel.h"
#include "PlayableProxyModel.h"
#include "PlayableItem.h"
#include "DownloadManager.h"
#include "DropJob.h"
#include "Result.h"
#include "Source.h"
#include "TomahawkSettings.h"
#include "audio/AudioEngine.h"
@@ -45,6 +47,9 @@
#include <QScrollBar>
#include <QDrag>
// HACK
#include <QTableView>
#define SCROLL_TIMEOUT 280
using namespace Tomahawk;
@@ -83,6 +88,13 @@ TrackView::TrackView( QWidget* parent )
setEditTriggers( NoEditTriggers );
setHeader( m_header );
// HACK: enable moving of first column: QTBUG-33974 / https://github.com/qtproject/qtbase/commit/e0fc088c0c8bc61dbcaf5928b24986cd61a22777
QTableView unused;
unused.setVerticalHeader( header() );
header()->setParent( this );
unused.setVerticalHeader( new QHeaderView( Qt::Horizontal, &unused ) );
setSortingEnabled( true );
sortByColumn( -1 );
setContextMenuPolicy( Qt::CustomContextMenu );
@@ -677,7 +689,6 @@ TrackView::wheelEvent( QWheelEvent* event )
QTreeView::wheelEvent( event );
m_delegate->resetHoverIndex();
repaint();
}
@@ -764,6 +775,44 @@ TrackView::onCustomContextMenu( const QPoint& pos )
m_contextMenu->setSupportedActions( m_contextMenu->supportedActions() | ContextMenu::ActionMarkListened
| ContextMenu::ActionDelete );
if ( proxyModel()->style() != PlayableProxyModel::Collection )
{
bool allDownloaded = true;
bool noneDownloadable = true;
bool downloadable = false;
foreach ( const QModelIndex& index, selectedIndexes() )
{
if ( index.column() )
continue;
PlayableItem* item = proxyModel()->itemFromIndex( proxyModel()->mapToSource( index ) );
if( item->query()->results().isEmpty() )
continue;
downloadable = !item->query()->results().first()->downloadFormats().isEmpty();
if ( downloadable )
{
noneDownloadable = false;
}
if ( downloadable && DownloadManager::instance()->localFileForDownload( item->query()->results().first()->downloadFormats().first().url.toString() ).isEmpty() )
{
allDownloaded = false;
}
if ( !allDownloaded || !noneDownloadable )
{
break;
}
}
if ( !allDownloaded || !noneDownloadable )
{
m_contextMenu->setSupportedActions( m_contextMenu->supportedActions() | ContextMenu::ActionDownload );
}
}
QList<query_ptr> queries;
foreach ( const QModelIndex& index, selectedIndexes() )
{
@@ -795,6 +844,10 @@ TrackView::onMenuTriggered( int action )
deleteSelectedItems();
break;
case ContextMenu::ActionDownload:
downloadSelectedItems();
break;
default:
break;
}
@@ -870,6 +923,30 @@ TrackView::deleteSelectedItems()
}
void
TrackView::downloadSelectedItems()
{
foreach ( const QModelIndex& index, selectedIndexes() )
{
if ( index.column() )
continue;
PlayableItem* item = proxyModel()->itemFromIndex( proxyModel()->mapToSource( index ) );
if ( !item )
continue;
if ( item->query()->results().isEmpty() || item->query()->results().first()->downloadFormats().isEmpty() )
continue;
if ( !DownloadManager::instance()->localFileForDownload( item->query()->results().first()->downloadFormats().first().url.toString() ).isEmpty() )
continue;
DownloadManager::instance()->addJob( item->result()->toDownloadJob( item->result()->downloadFormats().first() ) );
}
}
void
TrackView::verifySize()
{

View File

@@ -89,6 +89,7 @@ public:
public slots:
virtual void onItemActivated( const QModelIndex& index );
virtual void downloadSelectedItems();
virtual void deleteSelectedItems();
void playItem();

View File

@@ -68,11 +68,12 @@ public:
enum UrlType
{
Any = 0x00,
Playlist = 0x01,
Track = 0x02,
Album = 0x04,
Artist = 0x08
UrlTypeAny = 0x00,
UrlTypePlaylist = 0x01,
UrlTypeTrack = 0x02,
UrlTypeAlbum = 0x04,
UrlTypeArtist = 0x08,
UrlTypeXspf = 0x10
};
Q_DECLARE_FLAGS( UrlTypes, UrlType )
Q_FLAGS( UrlTypes )

View File

@@ -30,6 +30,8 @@
#include <QWidget>
#include <QUiLoader>
#include <QBoxLayout>
#include <QPushButton>
#include <QDesktopServices>
Tomahawk::ExternalResolverGui::ExternalResolverGui(const QString& filePath)
: Tomahawk::ExternalResolver(filePath)
@@ -82,6 +84,30 @@ Tomahawk::ExternalResolverGui::addChildProperties( QObject* widget, QVariantMap&
}
void
Tomahawk::ExternalResolverGui::setupClickHandlerOnUrlButtons( QObject* widget )
{
if( !widget || !widget->isWidgetType() )
return;
if( qstrcmp( widget->metaObject()->className(), "QPushButton" ) == 0 && !widget->property( "url" ).isNull() )
{
QPushButton* button = qobject_cast< QPushButton* >( widget );
Q_ASSERT( button );
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
connect( button, &QPushButton::clicked, [=]() {
QDesktopServices::openUrl( widget->property( "url" ).toUrl() );
});
#endif
}
// and recurse
foreach( QObject* child, widget->children() )
setupClickHandlerOnUrlButtons( child );
}
AccountConfigWidget*
Tomahawk::ExternalResolverGui::widgetFromData( QByteArray& data, QWidget* parent )
{
@@ -94,6 +120,8 @@ Tomahawk::ExternalResolverGui::widgetFromData( QByteArray& data, QWidget* parent
QBuffer b( &data );
QWidget* w = l.load( &b, configWidget );
setupClickHandlerOnUrlButtons( w );
// HACK: proper way would be to create a designer plugin for this widget type
configWidget->setLayout( new QBoxLayout( QBoxLayout::TopToBottom ) );
configWidget->layout()->addWidget( w );

View File

@@ -50,7 +50,8 @@ protected:
QByteArray fixDataImagePaths( const QByteArray& data, bool compressed, const QVariantMap& images );
private:
void addChildProperties( QObject* parent, QVariantMap& m );
void addChildProperties( QObject* widget, QVariantMap& m );
void setupClickHandlerOnUrlButtons( QObject* widget );
};
}; //ns

View File

@@ -67,6 +67,14 @@ JSAccount::scriptPluginFactory( const QString& type, const scriptobject_ptr& obj
}
void
JSAccount::showDebugger()
{
tLog() << Q_FUNC_INFO << name() << "Show debugger";
m_engine->showWebInspector();
}
QString
JSAccount::serializeQVariantMap( const QVariantMap& map )
{
@@ -170,6 +178,41 @@ JSAccount::syncInvoke( const scriptobject_ptr& scriptObject, const QString& meth
return evaluateJavaScriptWithResult( eval );
}
void
JSAccount::reportNativeScriptJobResult( int resultId, const QVariantMap& result )
{
QString eval = QString(
"Tomahawk.NativeScriptJobManager.reportNativeScriptJobResult("
"%1," // requestId
"%2" // results
");"
).arg( resultId )
.arg( serializeQVariantMap( result ) );
// Remove when new scripting api turned out to work reliably
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << eval;
evaluateJavaScript( eval );
}
void
JSAccount::reportNativeScriptJobError( int resultId, const QVariantMap& error )
{
QString eval = QString(
"Tomahawk.NativeScriptJobManager.reportNativeScriptJobError("
"%1," // requestId
"%2" // results
");"
).arg( resultId )
.arg( serializeQVariantMap( error ) );
// Remove when new scripting api turned out to work reliably
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << eval;
evaluateJavaScript( eval );
}
QVariant
JSAccount::evaluateJavaScriptInternal( const QString& scriptSource )

View File

@@ -69,15 +69,20 @@ public:
void setResolver( JSResolver* resolver );
void scriptPluginFactory( const QString& type, const scriptobject_ptr& object ) override;
void showDebugger() override;
static QString serializeQVariantMap(const QVariantMap& map);
void reportNativeScriptJobResult( int resultId, const QVariantMap& result ) override;
void reportNativeScriptJobError( int resultId, const QVariantMap& error ) override;
private:
/**
* Wrap the pure evaluateJavaScript call in here, while the threadings guards are in public methods
*/
QVariant evaluateJavaScriptInternal( const QString& scriptSource );
std::unique_ptr<ScriptEngine> m_engine;
ScriptEngine* m_engine;
// HACK: the order of initializen is flawed, tbr
JSResolver* m_resolver;
};

View File

@@ -44,6 +44,13 @@
#include "Track.h"
#include "ScriptInfoPlugin.h"
#include "JSAccount.h"
#include "ScriptJob.h"
// lookupUrl stuff
#include "playlist/PlaylistTemplate.h"
#include "playlist/XspfPlaylistTemplate.h"
#include "database/Database.h"
#include "database/DatabaseImpl.h"
#include <QDir>
#include <QFile>
@@ -250,12 +257,11 @@ JSResolver::init()
d->scriptAccount->loadScript( filePath() );
// HACK: register resolver object
d->scriptAccount->evaluateJavaScript( "Tomahawk.PluginManager.registerPlugin('resolver', Tomahawk.resolver.instance);" )
;
d->scriptAccount->evaluateJavaScript( "Tomahawk.PluginManager.registerPlugin('resolver', Tomahawk.resolver.instance);" );
// init resolver
resolverInit();
scriptObject()->syncInvoke( "init" );
QVariantMap m = resolverSettings();
QVariantMap m = scriptObject()->syncInvoke( "settings" ).toMap();
d->name = m.value( "name" ).toString();
d->weight = m.value( "weight", 0 ).toUInt();
d->timeout = m.value( "timeout", 25 ).toUInt() * 1000;
@@ -305,6 +311,8 @@ JSResolver::start()
Q_D( JSResolver );
d->stopped = false;
d->resolverHelper->start();
if ( d->ready )
Tomahawk::Pipeline::instance()->addResolver( this );
else
@@ -319,20 +327,13 @@ JSResolver::canParseUrl( const QString& url, UrlType type )
{
Q_D( const JSResolver );
// FIXME: How can we do this?
/*if ( QThread::currentThread() != thread() )
{
QMetaObject::invokeMethod( this, "canParseUrl", Qt::QueuedConnection,
Q_ARG( QString, url ) );
return;
}*/
if ( d->capabilities.testFlag( UrlLookup ) )
{
QString eval = QString( "canParseUrl( '%1', %2 )" )
.arg( JSAccount::escape( QString( url ) ) )
.arg( (int) type );
return callOnResolver( eval ).toBool();
QVariantMap arguments;
arguments["url"] = url;
arguments["type"] = (int) type;
return scriptObject()->syncInvoke( "canParseUrl", arguments ).toBool();
}
else
{
@@ -345,34 +346,185 @@ JSResolver::canParseUrl( const QString& url, UrlType type )
void
JSResolver::lookupUrl( const QString& url )
{
if ( QThread::currentThread() != thread() )
{
QMetaObject::invokeMethod( this, "lookupUrl", Qt::QueuedConnection,
Q_ARG( QString, url ) );
return;
}
Q_D( const JSResolver );
if ( !d->capabilities.testFlag( UrlLookup ) )
{
emit informationFound( url, QSharedPointer<QObject>() );
return;
}
QString eval = QString( "lookupUrl( '%1' )" )
.arg( JSAccount::escape( QString( url ) ) );
QVariantMap arguments;
arguments["url"] = url;
Tomahawk::ScriptJob* job = scriptObject()->invoke( "lookupUrl", arguments );
connect( job, SIGNAL( done( QVariantMap ) ), SLOT( onLookupUrlRequestDone( QVariantMap ) ) );
job->setProperty( "url", url );
job->start();
}
QVariantMap m = callOnResolver( eval ).toMap();
if ( m.isEmpty() )
void
JSResolver::onLookupUrlRequestDone( const QVariantMap& result )
{
sender()->deleteLater();
QString url = sender()->property( "url" ).toString();
tLog() << "ON LOOKUP URL REQUEST DONE" << url << result;
// It may seem a bit weird, but currently no slot should do anything
// more as we starting on a new URL and not task are waiting for it yet.
m_pendingUrl = QString();
m_pendingAlbum = album_ptr();
UrlTypes type = (UrlTypes) result.value( "type" ).toInt();
if ( type == UrlTypeArtist )
{
// if the resolver doesn't return anything, async api is used
return;
QString name = result.value( "name" ).toString();
Q_ASSERT( !name.isEmpty() );
emit informationFound( url, Artist::get( name, true ).objectCast<QObject>() );
}
else if ( type == UrlTypeAlbum )
{
QString name = result.value( "name" ).toString();
QString artist = result.value( "artist" ).toString();
album_ptr album = Album::get( Artist::get( artist, true ), name );
m_pendingUrl = url;
m_pendingAlbum = album;
connect( album.data(), SIGNAL( tracksAdded( QList<Tomahawk::query_ptr>, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ),
SLOT( tracksAdded( QList<Tomahawk::query_ptr>, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ) );
if ( !album->tracks().isEmpty() )
{
emit informationFound( url, album.objectCast<QObject>() );
}
}
else if ( type == UrlTypeTrack )
{
Tomahawk::query_ptr query = parseTrack( result );
if ( query.isNull() )
{
// A valid track result shoud have non-empty title and artist.
tLog() << Q_FUNC_INFO << name() << "Got empty track information for " << url;
emit informationFound( url, QSharedPointer<QObject>() );
}
else
{
emit informationFound( url, query.objectCast<QObject>() );
}
}
else if ( type == UrlTypePlaylist )
{
QString guid = result.value( "guid" ).toString();
Q_ASSERT( !guid.isEmpty() );
// Append nodeid to guid to make it globally unique.
guid += instanceUUID();
// Do we already have this playlist loaded?
{
playlist_ptr playlist = Playlist::get( guid );
if ( !playlist.isNull() )
{
emit informationFound( url, playlist.objectCast<QObject>() );
return;
}
}
// Get all information to build a new playlist but do not build it until we know,
// if it is really handled as a playlist and not as a set of tracks.
Tomahawk::source_ptr source = SourceList::instance()->getLocal();
const QString title = result.value( "title" ).toString();
const QString info = result.value( "info" ).toString();
const QString creator = result.value( "creator" ).toString();
QList<query_ptr> queries;
foreach( QVariant track, result.value( "tracks" ).toList() )
{
query_ptr query = parseTrack( track.toMap() );
if ( !query.isNull() )
{
queries << query;
}
}
tLog( LOGVERBOSE ) << Q_FUNC_INFO << name() << "Got playlist for " << url;
playlisttemplate_ptr pltemplate( new PlaylistTemplate( source, guid, title, info, creator, false, queries ) );
emit informationFound( url, pltemplate.objectCast<QObject>() );
}
else if ( type == UrlTypeXspf )
{
QString xspfUrl = result.value( "url" ).toString();
Q_ASSERT( !xspfUrl.isEmpty() );
QString guid = QString( "xspf-%1-%2" ).arg( xspfUrl.toUtf8().toBase64().constData() ).arg( instanceUUID() );
// Do we already have this playlist loaded?
{
playlist_ptr playlist = Playlist::get( guid );
if ( !playlist.isNull() )
{
emit informationFound( url, playlist.objectCast<QObject>() );
return;
}
}
// Get all information to build a new playlist but do not build it until we know,
// if it is really handled as a playlist and not as a set of tracks.
Tomahawk::source_ptr source = SourceList::instance()->getLocal();
QSharedPointer<XspfPlaylistTemplate> pltemplate( new XspfPlaylistTemplate( xspfUrl, source, guid ) );
NewClosure( pltemplate, SIGNAL( tracksLoaded( QList< Tomahawk::query_ptr > ) ),
this, SLOT( pltemplateTracksLoadedForUrl( QString, Tomahawk::playlisttemplate_ptr ) ),
url, pltemplate.objectCast<Tomahawk::PlaylistTemplate>() );
tLog( LOGVERBOSE ) << Q_FUNC_INFO << name() << "Got playlist for " << url;
pltemplate->load();
}
else
{
tLog( LOGVERBOSE ) << Q_FUNC_INFO << name() << "No usable information found for " << url;
emit informationFound( url, QSharedPointer<QObject>() );
}
}
query_ptr
JSResolver::parseTrack( const QVariantMap& track )
{
QString title = track.value( "track" ).toString();
QString artist = track.value( "artist" ).toString();
QString album = track.value( "album" ).toString();
if ( title.isEmpty() || artist.isEmpty() )
{
return query_ptr();
}
QString errorMessage = tr( "Script Resolver Warning: API call %1 returned data synchronously." ).arg( eval );
JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( errorMessage ) );
tDebug() << errorMessage << m;
Tomahawk::query_ptr query = Tomahawk::Query::get( artist, title, album );
QString resultHint = track.value( "hint" ).toString();
if ( !resultHint.isEmpty() )
{
query->setResultHint( resultHint );
query->setSaveHTTPResultHint( true );
}
return query;
}
void
JSResolver::tracksAdded( const QList<query_ptr>&, const ModelMode, const collection_ptr&)
{
// Check if we still are actively waiting
if ( m_pendingAlbum.isNull() || m_pendingUrl.isNull() )
return;
emit informationFound( m_pendingUrl, m_pendingAlbum.objectCast<QObject>() );
m_pendingAlbum = album_ptr();
m_pendingUrl = QString();
}
void
JSResolver::pltemplateTracksLoadedForUrl( const QString& url, const playlisttemplate_ptr& pltemplate )
{
tLog() << Q_FUNC_INFO;
emit informationFound( url, pltemplate.objectCast<QObject>() );
}
@@ -388,38 +540,59 @@ JSResolver::error() const
void
JSResolver::resolve( const Tomahawk::query_ptr& query )
{
if ( QThread::currentThread() != thread() )
{
QMetaObject::invokeMethod( this, "resolve", Qt::QueuedConnection, Q_ARG(Tomahawk::query_ptr, query) );
return;
}
ScriptJob* job = scriptAccount()->resolve( scriptObject(), query, "resolver" );
connect( job, SIGNAL( done( QVariantMap ) ), SLOT( onResolveRequestDone( QVariantMap ) ) );
QString eval;
if ( !query->isFullTextQuery() )
job->start();
}
void
JSResolver::onResolveRequestDone( const QVariantMap& data )
{
Q_ASSERT( QThread::currentThread() == thread() );
Q_D( JSResolver );
ScriptJob* job = qobject_cast< ScriptJob* >( sender() );
QID qid = job->property( "qid" ).toString();
if ( job->error() )
{
eval = QString( "resolve( '%1', '%2', '%3', '%4' )" )
.arg( JSAccount::escape( query->id() ) )
.arg( JSAccount::escape( query->queryTrack()->artist() ) )
.arg( JSAccount::escape( query->queryTrack()->album() ) )
.arg( JSAccount::escape( query->queryTrack()->track() ) );
Tomahawk::Pipeline::instance()->reportError( qid, this );
}
else
{
eval = QString( "search( '%1', '%2' )" )
.arg( JSAccount::escape( query->id() ) )
.arg( JSAccount::escape( query->fullTextQuery() ) );
if ( !data.value( "artists" ).isNull() )
{
QList< artist_ptr > artists = scriptAccount()->parseArtistVariantList( data.value( "artists" ).toList() );
Tomahawk::Pipeline::instance()->reportArtists( qid, artists );
}
if ( !data.value( "albums" ).isNull() )
{
QList< album_ptr > albums = scriptAccount()->parseAlbumVariantList( data.value( "albums" ).toList() );
Tomahawk::Pipeline::instance()->reportAlbums( qid, albums );
}
QList< Tomahawk::result_ptr > results = scriptAccount()->parseResultVariantList( data.value( "tracks" ).toList() );
foreach( const result_ptr& result, results )
{
result->setResolvedByResolver( this );
result->setFriendlySource( name() );
}
Tomahawk::Pipeline::instance()->reportResults( qid, this, results );
}
QVariantMap m = callOnResolver( eval ).toMap();
sender()->deleteLater();
}
void
JSResolver::stop()
{
Q_D( JSResolver );
d->stopped = true;
d->resolverHelper->stop();
scriptAccount()->stop();
@@ -433,7 +606,7 @@ JSResolver::loadUi()
{
Q_D( JSResolver );
QVariantMap m = callOnResolver( "getConfigUi()" ).toMap();
QVariantMap m = scriptObject()->syncInvoke( "getConfigUi" ).toMap();
bool compressed = m.value( "compressed", "false" ).toBool();
qDebug() << "Resolver has a preferences widget! compressed?" << compressed;
@@ -485,7 +658,7 @@ JSResolver::saveConfig()
// qDebug() << Q_FUNC_INFO << saveData;
d->resolverHelper->setResolverConfig( saveData.toMap() );
callOnResolver( "saveUserConfig()" );
scriptObject()->syncInvoke( "saveUserConfig" );
}
@@ -516,39 +689,25 @@ JSResolver::onCapabilitiesChanged( Tomahawk::ExternalResolver::Capabilities capa
}
QVariantMap
JSResolver::resolverSettings()
{
return callOnResolver( "settings" ).toMap();
}
QVariantMap
JSResolver::resolverUserConfig()
{
return callOnResolver( "getUserConfig()" ).toMap();
return scriptObject()->syncInvoke( "getUserConfig" ).toMap();
}
QVariantMap
JSResolver::resolverInit()
QString
JSResolver::instanceUUID()
{
return callOnResolver( "init()" ).toMap();
return Tomahawk::Database::instance()->impl()->dbid();
}
QVariant
JSResolver::callOnResolver( const QString& scriptSource )
ScriptJob*
JSResolver::getStreamUrl( const result_ptr& result )
{
Q_D( JSResolver );
QVariantMap arguments;
arguments["url"] = result->url();
QString propertyName = scriptSource.split('(').first();
return d->scriptAccount->evaluateJavaScriptWithResult( QString(
"if(Tomahawk.resolver.instance['_adapter_%1']) {"
" Tomahawk.resolver.instance._adapter_%2;"
"} else {"
" Tomahawk.resolver.instance.%2"
"}"
).arg( propertyName ).arg( scriptSource ) );
return scriptObject()->invoke( "getStreamUrl", arguments );
}

View File

@@ -75,6 +75,8 @@ public:
ScriptAccount* scriptAccount() const;
ScriptJob* getStreamUrl( const result_ptr& result ) override;
public slots:
void resolve( const Tomahawk::query_ptr& query ) override;
void stop() override;
@@ -89,6 +91,10 @@ signals:
protected:
QVariant callOnResolver( const QString& scriptSource );
private slots:
void onResolveRequestDone(const QVariantMap& data);
void onLookupUrlRequestDone(const QVariantMap& data);
private:
void init();
@@ -96,12 +102,20 @@ private:
void onCapabilitiesChanged( Capabilities capabilities );
// encapsulate javascript calls
QVariantMap resolverSettings();
QVariantMap resolverUserConfig();
QVariantMap resolverInit();
Q_DECLARE_PRIVATE( JSResolver )
QScopedPointer<JSResolverPrivate> d_ptr;
// TODO: move lookupUrl stuff to its own plugin type
QString instanceUUID();
static Tomahawk::query_ptr parseTrack( const QVariantMap& track );
QString m_pendingUrl;
Tomahawk::album_ptr m_pendingAlbum;
private slots:
void tracksAdded( const QList<Tomahawk::query_ptr>& tracks, const Tomahawk::ModelMode, const Tomahawk::collection_ptr& collection );
void pltemplateTracksLoadedForUrl( const QString& url, const Tomahawk::playlisttemplate_ptr& pltemplate );
};
} // ns: Tomahawk

View File

@@ -21,10 +21,6 @@
#include "JSResolverHelper.h"
#include "database/Database.h"
#include "database/DatabaseImpl.h"
#include "playlist/PlaylistTemplate.h"
#include "playlist/XspfPlaylistTemplate.h"
#include "resolvers/ScriptEngine.h"
#include "network/Servent.h"
#include "utils/Closure.h"
@@ -51,6 +47,8 @@
#include <QMap>
#include <QWebFrame>
#include <QLocale>
#include <QNetworkReply>
#include <taglib/asffile.h>
#include <taglib/flacfile.h>
#include <taglib/id3v2framefactory.h>
@@ -76,11 +74,25 @@ JSResolverHelper::JSResolverHelper( const QString& scriptPath, JSResolver* paren
: QObject( parent )
, m_resolver( parent )
, m_scriptPath( scriptPath )
, m_urlCallbackIsAsync( false )
, m_stopped( false )
{
}
void
JSResolverHelper::start()
{
m_stopped = false;
}
void
JSResolverHelper::stop()
{
m_stopped = true;
}
QByteArray
JSResolverHelper::readRaw( const QString& fileName )
{
@@ -140,55 +152,6 @@ JSResolverHelper::log( const QString& message )
}
void
JSResolverHelper::addTrackResults( const QVariantMap& results )
{
Q_ASSERT( results["results"].toMap().isEmpty() );
QList< Tomahawk::result_ptr > tracks = m_resolver->scriptAccount()->parseResultVariantList( results.value( "results" ).toList() );
foreach( const result_ptr& track, tracks )
{
track->setResolvedByResolver( m_resolver );
track->setFriendlySource( m_resolver->name() );
}
QString qid = results.value("qid").toString();
Tomahawk::Pipeline::instance()->reportResults( qid, m_resolver, tracks );
}
query_ptr
JSResolverHelper::parseTrack( const QVariantMap& track )
{
QString title = track.value( "title" ).toString();
QString artist = track.value( "artist" ).toString();
QString album = track.value( "album" ).toString();
if ( title.isEmpty() || artist.isEmpty() )
{
return query_ptr();
}
Tomahawk::query_ptr query = Tomahawk::Query::get( artist, title, album );
QString resultHint = track.value( "hint" ).toString();
if ( !resultHint.isEmpty() )
{
query->setResultHint( resultHint );
query->setSaveHTTPResultHint( true );
}
return query;
}
QString
JSResolverHelper::instanceUUID()
{
return Tomahawk::Database::instance()->impl()->dbid();
}
QString
JSResolverHelper::uuid() const
{
@@ -483,123 +446,13 @@ JSResolverHelper::currentCountry() const
}
void
JSResolverHelper::addUrlResult( const QString& url, const QVariantMap& result )
{
// It may seem a bit weird, but currently no slot should do anything
// more as we starting on a new URL and not task are waiting for it yet.
m_pendingUrl = QString();
m_pendingAlbum = album_ptr();
QString type = result.value( "type" ).toString();
if ( type == "artist" )
{
QString name = result.value( "name" ).toString();
Q_ASSERT( !name.isEmpty() );
emit m_resolver->informationFound( url, Artist::get( name, true ).objectCast<QObject>() );
}
else if ( type == "album" )
{
QString name = result.value( "name" ).toString();
QString artist = result.value( "artist" ).toString();
album_ptr album = Album::get( Artist::get( artist, true ), name );
m_pendingUrl = url;
m_pendingAlbum = album;
connect( album.data(), SIGNAL( tracksAdded( QList<Tomahawk::query_ptr>, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ),
SLOT( tracksAdded( QList<Tomahawk::query_ptr>, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ) );
if ( !album->tracks().isEmpty() )
{
emit m_resolver->informationFound( url, album.objectCast<QObject>() );
}
}
else if ( type == "track" )
{
Tomahawk::query_ptr query = parseTrack( result );
if ( query.isNull() )
{
// A valid track result shoud have non-empty title and artist.
tLog() << Q_FUNC_INFO << m_resolver->name() << "Got empty track information for " << url;
emit m_resolver->informationFound( url, QSharedPointer<QObject>() );
}
else
{
emit m_resolver->informationFound( url, query.objectCast<QObject>() );
}
}
else if ( type == "playlist" )
{
QString guid = result.value( "guid" ).toString();
Q_ASSERT( !guid.isEmpty() );
// Append nodeid to guid to make it globally unique.
guid += instanceUUID();
// Do we already have this playlist loaded?
{
playlist_ptr playlist = Playlist::get( guid );
if ( !playlist.isNull() )
{
emit m_resolver->informationFound( url, playlist.objectCast<QObject>() );
return;
}
}
// Get all information to build a new playlist but do not build it until we know,
// if it is really handled as a playlist and not as a set of tracks.
Tomahawk::source_ptr source = SourceList::instance()->getLocal();
const QString title = result.value( "title" ).toString();
const QString info = result.value( "info" ).toString();
const QString creator = result.value( "creator" ).toString();
QList<query_ptr> queries;
foreach( QVariant track, result.value( "tracks" ).toList() )
{
query_ptr query = parseTrack( track.toMap() );
if ( !query.isNull() )
{
queries << query;
}
}
tLog( LOGVERBOSE ) << Q_FUNC_INFO << m_resolver->name() << "Got playlist for " << url;
playlisttemplate_ptr pltemplate( new PlaylistTemplate( source, guid, title, info, creator, false, queries ) );
emit m_resolver->informationFound( url, pltemplate.objectCast<QObject>() );
}
else if ( type == "xspf-url" )
{
QString xspfUrl = result.value( "url" ).toString();
Q_ASSERT( !xspfUrl.isEmpty() );
QString guid = QString( "xspf-%1-%2" ).arg( xspfUrl.toUtf8().toBase64().constData() ).arg( instanceUUID() );
// Do we already have this playlist loaded?
{
playlist_ptr playlist = Playlist::get( guid );
if ( !playlist.isNull() )
{
emit m_resolver->informationFound( url, playlist.objectCast<QObject>() );
return;
}
}
// Get all information to build a new playlist but do not build it until we know,
// if it is really handled as a playlist and not as a set of tracks.
Tomahawk::source_ptr source = SourceList::instance()->getLocal();
QSharedPointer<XspfPlaylistTemplate> pltemplate( new XspfPlaylistTemplate( xspfUrl, source, guid ) );
NewClosure( pltemplate, SIGNAL( tracksLoaded( QList< Tomahawk::query_ptr > ) ),
this, SLOT( pltemplateTracksLoadedForUrl( QString, Tomahawk::playlisttemplate_ptr ) ),
url, pltemplate.objectCast<Tomahawk::PlaylistTemplate>() );
tLog( LOGVERBOSE ) << Q_FUNC_INFO << m_resolver->name() << "Got playlist for " << url;
pltemplate->load();
}
else
{
tLog( LOGVERBOSE ) << Q_FUNC_INFO << m_resolver->name() << "No usable information found for " << url;
emit m_resolver->informationFound( url, QSharedPointer<QObject>() );
}
}
void
JSResolverHelper::nativeReportCapabilities( const QVariant& v )
{
if( m_stopped )
return;
bool ok;
int intCap = v.toInt( &ok );
Tomahawk::ExternalResolver::Capabilities capabilities;
@@ -615,6 +468,9 @@ JSResolverHelper::nativeReportCapabilities( const QVariant& v )
void
JSResolverHelper::reportScriptJobResults( const QVariantMap& result )
{
if( m_stopped )
return;
m_resolver->d_func()->scriptAccount->reportScriptJobResult( result );
}
@@ -622,6 +478,9 @@ JSResolverHelper::reportScriptJobResults( const QVariantMap& result )
void
JSResolverHelper::registerScriptPlugin( const QString& type, const QString& objectId )
{
if( m_stopped )
return;
m_resolver->d_func()->scriptAccount->registerScriptPlugin( type, objectId );
}
@@ -629,28 +488,10 @@ JSResolverHelper::registerScriptPlugin( const QString& type, const QString& obje
void
JSResolverHelper::unregisterScriptPlugin( const QString& type, const QString& objectId )
{
m_resolver->d_func()->scriptAccount->unregisterScriptPlugin( type, objectId );
}
void
JSResolverHelper::tracksAdded( const QList<query_ptr>&, const ModelMode, const collection_ptr&)
{
// Check if we still are actively waiting
if ( m_pendingAlbum.isNull() || m_pendingUrl.isNull() )
if( m_stopped )
return;
emit m_resolver->informationFound( m_pendingUrl, m_pendingAlbum.objectCast<QObject>() );
m_pendingAlbum = album_ptr();
m_pendingUrl = QString();
}
void
JSResolverHelper::pltemplateTracksLoadedForUrl( const QString& url, const playlisttemplate_ptr& pltemplate )
{
tLog() << Q_FUNC_INFO;
emit m_resolver->informationFound( url, pltemplate.objectCast<QObject>() );
m_resolver->d_func()->scriptAccount->unregisterScriptPlugin( type, objectId );
}
@@ -668,31 +509,6 @@ JSResolverHelper::accountId()
}
void
JSResolverHelper::addCustomUrlHandler( const QString& protocol,
const QString& callbackFuncName,
const QString& isAsynchronous )
{
m_urlCallbackIsAsync = ( isAsynchronous.toLower() == "true" );
std::function< void( const Tomahawk::result_ptr&, const QString&,
std::function< void( const QString&, QSharedPointer< QIODevice >& ) > )> fac =
std::bind( &JSResolverHelper::customIODeviceFactory, this,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3 );
Tomahawk::UrlHandler::registerIODeviceFactory( protocol, fac );
m_urlCallback = callbackFuncName;
}
void
JSResolverHelper::reportStreamUrl( const QString& qid, const QString& streamUrl )
{
reportStreamUrl( qid, streamUrl, QVariantMap() );
}
void JSResolverHelper::nativeAssert( bool assertion, const QString& message )
{
if ( !assertion )
@@ -703,61 +519,6 @@ void JSResolverHelper::nativeAssert( bool assertion, const QString& message )
}
void
JSResolverHelper::customIODeviceFactory( const Tomahawk::result_ptr&, const QString& url,
std::function< void( const QString&, QSharedPointer< QIODevice >& ) > callback )
{
//can be sync or async
if ( m_urlCallbackIsAsync )
{
QString qid = uuid();
QString getUrl = QString(
"if(Tomahawk.resolver.instance['_adapter_%1']) {"
" Tomahawk.resolver.instance._adapter_%1( {qid: '%2', url: '%3'} );"
"} else {"
" Tomahawk.resolver.instance.%1( {qid: '%2', url: '%3'} );"
"}"
).arg( m_urlCallback )
.arg( qid )
.arg( url );
m_streamCallbacks.insert( qid, callback );
m_resolver->d_func()->scriptAccount->evaluateJavaScript( getUrl );
}
else
{
QString getUrl = QString( "Tomahawk.resolver.instance.%1( '%2' );" ).arg( m_urlCallback )
.arg( url );
QString urlStr = m_resolver->d_func()->scriptAccount->evaluateJavaScriptWithResult( getUrl ).toString();
returnStreamUrl( urlStr, QMap<QString, QString>(), callback );
}
}
void
JSResolverHelper::reportStreamUrl( const QString& qid, const QString& streamUrl, const QVariantMap& headers )
{
if ( !m_streamCallbacks.contains( qid ) )
return;
std::function< void( const QString&, QSharedPointer< QIODevice >& ) > callback = m_streamCallbacks.take( qid );
QMap<QString, QString> parsedHeaders;
foreach ( const QString& key, headers.keys() )
{
Q_ASSERT_X( headers[key].canConvert( QVariant::String ), Q_FUNC_INFO, "Expected a Map of string for additional headers" );
if ( headers[key].canConvert( QVariant::String ) )
{
parsedHeaders.insert( key, headers[key].toString() );
}
}
returnStreamUrl( streamUrl, parsedHeaders, callback );
}
void
JSResolverHelper::nativeRetrieveMetadata( int metadataId, const QString& url,
const QString& mime_type, int sizehint,
@@ -896,12 +657,27 @@ JSResolverHelper::nativeRetrieveMetadata( int metadataId, const QString& url,
}
}
void
JSResolverHelper::invokeNativeScriptJob( int requestId, const QString& methodName, const QVariantMap& params )
{
if ( methodName == "httpRequest" ) {
nativeAsyncRequest( requestId, params );
} else {
QVariantMap error;
error["message"] = "NativeScriptJob methodName was not found";
error["name"] = "method_was_not_found";
m_resolver->d_func()->scriptAccount->reportNativeScriptJobError( requestId, error );
}
}
void
JSResolverHelper::nativeAsyncRequest( const int requestId, const QString& url,
const QVariantMap& headers,
const QVariantMap& options )
JSResolverHelper::nativeAsyncRequest( const int requestId, const QVariantMap& options )
{
QString url = options[ "url" ].toString();
QVariantMap headers = options[ "headers" ].toMap();
QNetworkRequest req( url );
foreach ( const QString& key, headers.keys() )
{
@@ -957,17 +733,16 @@ JSResolverHelper::nativeAsyncRequestDone( int requestId, NetworkReply* reply )
map["status"] = reply->reply()->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
map["statusText"] = QString("%1 %2").arg( map["status"].toString() )
.arg( reply->reply()->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString() );
if (reply->reply()->hasRawHeader( "Content-Type" ))
map["contentType"] = reply->reply()->rawHeader( "Content-Type" );
bool ok = false;
QString json = QString::fromUtf8( TomahawkUtils::toJson( map, &ok ) );
Q_ASSERT( ok );
QString javascript = QString( "Tomahawk.nativeAsyncRequestDone( %1, %2 );" )
.arg( QString::number( requestId ) )
.arg( json );
m_resolver->d_func()->scriptAccount->evaluateJavaScript( javascript );
QVariantMap responseHeaders;
foreach( const QNetworkReply::RawHeaderPair& pair, reply->reply()->rawHeaderPairs() )
{
responseHeaders[ pair.first ] = pair.second;
}
map["responseHeaders"] = responseHeaders;
m_resolver->d_func()->scriptAccount->reportNativeScriptJobResult( requestId, map );
}
@@ -1143,43 +918,3 @@ JSResolverHelper::readdResolver()
Pipeline::instance()->addResolver( m_resolver );
}
void
JSResolverHelper::returnStreamUrl( const QString& streamUrl, const QMap<QString, QString>& headers,
std::function< void( const QString&, QSharedPointer< QIODevice >& ) > callback )
{
if ( streamUrl.isEmpty() || !( TomahawkUtils::isHttpResult( streamUrl ) || TomahawkUtils::isHttpsResult( streamUrl ) ) )
{
// Not an https? URL, so let Phonon handle it
QSharedPointer< QIODevice > sp;
callback( streamUrl, sp );
}
else
{
QUrl url = QUrl::fromEncoded( streamUrl.toUtf8() );
QNetworkRequest req( url );
foreach ( const QString& key, headers.keys() )
{
req.setRawHeader( key.toLatin1(), headers[key].toLatin1() );
}
tDebug() << "Creating a QNetworkReply with url:" << req.url().toString();
NetworkReply* reply = new NetworkReply( Tomahawk::Utils::nam()->get( req ) );
NewClosure( reply, SIGNAL( finalUrlReached() ), this, SLOT( gotStreamUrl( IODeviceCallback, NetworkReply* )), callback, reply );
}
}
Q_DECLARE_METATYPE( IODeviceCallback )
void
JSResolverHelper::gotStreamUrl( std::function< void( const QString&, QSharedPointer< QIODevice >& ) > callback, NetworkReply* reply )
{
// std::functions cannot accept temporaries as parameters
QSharedPointer< QIODevice > sp ( reply->reply(), &QObject::deleteLater );
QString url = reply->reply()->url().toString();
reply->disconnectFromReply();
reply->deleteLater();
callback( url, sp );
}

View File

@@ -54,6 +54,9 @@ public:
*/
void setResolverConfig( const QVariantMap& config );
void start();
void stop();
/**
* Get the instance unique account id for this resolver.
*
@@ -61,9 +64,6 @@ public:
*/
Q_INVOKABLE QString accountId();
Q_INVOKABLE void addCustomUrlHandler( const QString& protocol, const QString& callbackFuncName, const QString& isAsynchronous = "false" );
Q_INVOKABLE void reportStreamUrl( const QString& qid, const QString& streamUrl );
Q_INVOKABLE void reportStreamUrl( const QString& qid, const QString& streamUrl, const QVariantMap& headers );
/**
* Make Tomahawk assert the assertion is true, probably not to be used by resolvers directly
@@ -89,20 +89,9 @@ public:
int sizehint,
const QVariantMap& options );
/**
* Native handler for asynchronous HTTP requests.
*
* This handler shall only be used if we cannot achieve the request with
* XMLHttpRequest as that would be more efficient.
* Use cases are:
* * Referer header: Stripped on MacOS and the specification says it
* should be stripped
*
* INTERNAL USE ONLY!
*/
Q_INVOKABLE void nativeAsyncRequest( int requestId, const QString& url,
const QVariantMap& headers,
const QVariantMap& options );
Q_INVOKABLE void invokeNativeScriptJob( int requestId,
const QString& methodName,
const QVariantMap& params );
/**
* Lucene++ indices for JS resolvers
@@ -122,18 +111,10 @@ public:
Q_INVOKABLE void readdResolver();
/**
* INTERNAL USE ONLY!
*/
void customIODeviceFactory( const Tomahawk::result_ptr&, const QString& url,
std::function< void( const QString&, QSharedPointer< QIODevice >& ) > callback ); // async
public slots:
QByteArray readRaw( const QString& fileName );
QString readBase64( const QString& fileName );
QString readCompressed( const QString& fileName );
QString instanceUUID();
QString uuid() const;
int currentCountry() const;
QString compress( const QString& data );
@@ -142,10 +123,6 @@ public slots:
void log( const QString& message );
bool fakeEnv() { return false; }
void addTrackResults( const QVariantMap& results );
void addUrlResult( const QString& url, const QVariantMap& result );
void nativeReportCapabilities( const QVariant& capabilities );
void reportScriptJobResults( const QVariantMap& result );
@@ -154,27 +131,20 @@ public slots:
void unregisterScriptPlugin( const QString& type, const QString& objectId );
private slots:
void gotStreamUrl( IODeviceCallback callback, NetworkReply* reply );
void tracksAdded( const QList<Tomahawk::query_ptr>& tracks, const Tomahawk::ModelMode, const Tomahawk::collection_ptr& collection );
void pltemplateTracksLoadedForUrl( const QString& url, const Tomahawk::playlisttemplate_ptr& pltemplate );
void nativeAsyncRequestDone( int requestId, NetworkReply* reply );
private:
Tomahawk::query_ptr parseTrack( const QVariantMap& track );
void returnStreamUrl( const QString& streamUrl, const QMap<QString, QString>& headers,
std::function< void( const QString&, QSharedPointer< QIODevice >& ) > callback );
bool indexDataFromVariant( const QVariantMap& map, struct Tomahawk::IndexData& indexData );
QVariantList searchInFuzzyIndex( const Tomahawk::query_ptr& query );
// native script jobs
void nativeAsyncRequest( int requestId, const QVariantMap& options );
QVariantMap m_resolverConfig;
JSResolver* m_resolver;
QString m_scriptPath, m_urlCallback, m_urlTranslator;
QHash< QString, std::function< void( const QString&, QSharedPointer< QIODevice >& ) > > m_streamCallbacks;
QHash< QString, std::function< void( const QString& ) > > m_translatorCallbacks;
bool m_urlCallbackIsAsync;
QString m_pendingUrl;
Tomahawk::album_ptr m_pendingAlbum;
QString m_scriptPath;
bool m_stopped;
};
} // ns: Tomahawk

Some files were not shown because too many files have changed in this diff Show More