Compare commits

..

490 Commits

Author SHA1 Message Date
Jonas Kvinge
f91b6c3468 Release 0.8.3 2020-10-24 01:32:25 +02:00
Jonas Kvinge
abe6eeb350 Update Changelog 2020-10-23 19:35:07 +02:00
Jonas Kvinge
64f90a7912 Remove Qt MacExtras, no longer used 2020-10-22 23:11:44 +02:00
Jonas Kvinge
5733966843 Dont reset settingspage changed status when window is shown
This is done by Init in Load() when the settings is opened.
2020-10-22 20:25:31 +02:00
Jonas Kvinge
eb1344fcec Unref caps in HandoffCallback 2020-10-22 17:49:13 +02:00
Strawbs Bot
c5fb29f00e Update translations 2020-10-22 01:02:03 +02:00
Jonas Kvinge
63135b9c54 Engine will never be in playing state on error 2020-10-21 23:27:15 +02:00
Jonas Kvinge
f7c666584e Remove debug line 2020-10-21 21:33:26 +02:00
Jonas Kvinge
6834324de2 Add more unit tests for organizeformat 2020-10-21 21:32:30 +02:00
Jonas Kvinge
3a0d59e66f Only append path if not empty 2020-10-21 21:32:12 +02:00
Jonas Kvinge
ffd2e2188a Fix crash with empty APE tag for MPC files 2020-10-21 20:29:06 +02:00
Jonas Kvinge
0e8d5bdc5d Fix crash with empty APE tag for WavPack and APE files 2020-10-21 20:27:43 +02:00
Jonas Kvinge
14806f6614 Update Changelog 2020-10-21 19:57:00 +02:00
Jonas Kvinge
00ece83b9d Tidal: Set default quality to lossless 2020-10-21 19:56:37 +02:00
Jonas Kvinge
8197ae2a2d Tidal: Guess filetype by filename extension in URL when missing codec. 2020-10-21 01:12:46 +02:00
Strawbs Bot
5fe658bb16 Update translations 2020-10-21 01:02:16 +02:00
Jonas Kvinge
617179f0c6 Always set state to NULL in destructor 2020-10-21 00:32:55 +02:00
Jonas Kvinge
95ac85f642 Move stream discoverer from pipeline to engine
Fixes #491
2020-10-21 00:07:58 +02:00
Jonas Kvinge
ca8877ad47 Revert gst_discoverer_stop 2020-10-20 18:47:40 +02:00
Jonas Kvinge
6d8f31048c Add call to gst_discoverer_stop
Stream discoverer currently only works on Linux
2020-10-20 18:28:09 +02:00
Jonas Kvinge
ac859eb576 Make sure we query empty instead of null 2020-10-20 17:14:38 +02:00
Jonas Kvinge
2dfa171b6a Save effective_albumartist as empty istead of null
Since we use the metadata now instead of the container keys for queries in
the model, there is a regression when the values are null.
2020-10-20 17:12:28 +02:00
Jonas Kvinge
6f72e3e2ea Always append divider key to sort text if the key is a digit 2020-10-20 17:11:14 +02:00
Strawbs Bot
c2b73ae963 Update translations 2020-10-20 01:04:50 +02:00
Jonas Kvinge
6c50077409 Fix concurrentrun test 2020-10-19 22:33:08 +02:00
Jonas Kvinge
06746449a1 Disable TestContainerNodes 2020-10-19 22:32:50 +02:00
Jonas Kvinge
da7b8edf51 Revert back to using PathWithoutFilenameExtension in organizedialog 2020-10-19 21:35:39 +02:00
Jonas Kvinge
6e29b41f23 Update Changelog 2020-10-19 21:07:25 +02:00
Jonas Kvinge
912bb069af Fix updating album cover to collection for edit tag dialog 2020-10-19 21:05:59 +02:00
Jonas Kvinge
6b2d7a67d8 Use Utilities::FiddleFileExtension in organize 2020-10-19 19:56:40 +02:00
Jonas Kvinge
dbb8ec0290 Remove debug in bus callbacks 2020-10-19 19:09:48 +02:00
Strawbs Bot
60b32760f2 Update translations 2020-10-19 01:03:27 +02:00
Jonas Kvinge
73a40bcb49 Update Changelog 2020-10-18 17:08:28 +02:00
Jonas Kvinge
0e258a5a32 Remove unused utilities functions 2020-10-18 14:12:01 +02:00
Jonas Kvinge
7ca65c81d8 Use QFileInfo instead of custom functions 2020-10-18 14:07:48 +02:00
Jonas Kvinge
2ad1a60e59 Use correct file extension in organize preview and resulting filename
Fixes #564
2020-10-18 13:24:33 +02:00
Jonas Kvinge
cf17ff4478 Update Changelog 2020-10-18 00:29:28 +02:00
Jonas Kvinge
fffc3aac68 Update Changelog 2020-10-17 21:11:06 +02:00
Jonas Kvinge
295ac3c458 Add back thumbbar 2020-10-17 21:10:57 +02:00
Jonas Kvinge
b6693a71f9 Rename initialise to initialize 2020-10-17 17:29:09 +02:00
Jonas Kvinge
5b21118a8c Replace gst_tag_list_free with gst_tag_list_unref 2020-10-17 04:54:46 +02:00
Jonas Kvinge
0235b19801 Only update buttons once 2020-10-17 04:21:11 +02:00
Jonas Kvinge
7426399aa2 Enable thumbbar for debug build 2020-10-17 03:41:28 +02:00
Jonas Kvinge
6861b0d668 Possible fix Windows thumbbar 2020-10-17 03:37:39 +02:00
Jonas Kvinge
c30fb0d38c Log errors in MM device finder 2020-10-17 03:19:13 +02:00
Jonas Kvinge
e45521c6c0 Fix updating playing widget song details in small mode 2020-10-16 23:57:18 +02:00
Strawbs Bot
d11fe8d4fc Update translations 2020-10-16 01:01:51 +02:00
Jonas Kvinge
8e83e63e3d Add snap warning dialog 2020-10-15 21:47:52 +02:00
Jonas Kvinge
c8fd0ac4b3 Update snapcraft.yaml 2020-10-15 18:51:21 +02:00
Jonas Kvinge
d78419eb33 Enable WASAPI plugin
Fixes #283
2020-10-15 16:08:59 +02:00
Jonas Kvinge
e44a3d013d Disable windows thumbbar
Fixes stability issues with WASAPI
2020-10-15 16:07:20 +02:00
Jonas Kvinge
aeb0d05017 Add CoInitializeEx() to mmdevice finder and refactor code 2020-10-15 16:06:07 +02:00
Jonas Kvinge
62702e4b3d Add space 2020-10-14 22:53:58 +02:00
Jonas Kvinge
5146cdfa2f Improve windows thumbbar code 2020-10-14 22:53:08 +02:00
Jonas Kvinge
246e7018c3 Remove concurrentrun.h 2020-10-14 22:50:19 +02:00
Jonas Kvinge
24286dbe9d Use QtConcurrent::run directly 2020-10-14 22:49:37 +02:00
Jonas Kvinge
2f442dfbe1 Ignore return value 2020-10-14 22:38:32 +02:00
Jonas Kvinge
b2fb01ee9c Use QtConcurrent 2020-10-14 22:35:54 +02:00
Jonas Kvinge
45e0a9a4ef Remove build-snap from CI 2020-10-14 18:36:29 +02:00
Jonas Kvinge
675b7b4bf4 Fix CI 2020-10-14 18:05:04 +02:00
Jonas Kvinge
be7a35443e Add gdb to nsi 2020-10-14 16:55:17 +02:00
Strawbs Bot
9918615fcd Update translations 2020-10-14 01:02:02 +02:00
Jonas Kvinge
4b72ef77c1 Turn back git revision 2020-10-14 00:26:35 +02:00
Jonas Kvinge
e427c61fbb Release 0.8.2 2020-10-13 18:03:05 +02:00
Jonas Kvinge
c78e8937d5 Update ISSUE_TEMPLATE.md 2020-10-13 01:50:32 +02:00
Jonas Kvinge
5d63f7a93d Update ISSUE_TEMPLATE.md 2020-10-13 01:50:03 +02:00
Jonas Kvinge
46551ada7f Update Changelog 2020-10-13 01:47:22 +02:00
Jonas Kvinge
83f17a37b1 Use format only when available 2020-10-13 01:38:09 +02:00
Strawbs Bot
9e23e0a623 Update translations 2020-10-13 01:05:54 +02:00
Jonas Kvinge
4a53d4f043 Ignore "IDirectSoundBuffer_GetStatus The operation completed successfully"
Fixes #557
2020-10-13 00:49:34 +02:00
Jonas Kvinge
0fd61945c7 Fix saving initial settings 2020-10-12 17:20:18 +02:00
Strawbs Bot
e3624eed30 Update translations 2020-10-12 01:03:47 +02:00
Jonas Kvinge
1923c8be0c Remove unused includes 2020-10-11 01:31:12 +02:00
Jonas Kvinge
a3e37fbfe2 Remove use of HTML in system tray icon tooltip 2020-10-11 01:29:26 +02:00
Jonas Kvinge
318c3bb422 Check if QNetworkRequest::ContentTypeHeader is filetype everwhere 2020-10-11 01:08:42 +02:00
Strawbs Bot
db96e24028 Update translations 2020-10-11 01:02:04 +02:00
Jonas Kvinge
cc2cce66df Make const 2020-10-11 00:07:38 +02:00
Jonas Kvinge
7afeabd288 Subsonic: Read created from album too
Fixes #526
2020-10-11 00:05:06 +02:00
Jonas Kvinge
eb43a812d6 Fix saving subsonic cover when ContentTypeHeader returns filetype 2020-10-10 23:46:29 +02:00
Jonas Kvinge
12cbcdb6f4 Fix SQL query by song id when song id is a string 2020-10-10 23:44:42 +02:00
Jonas Kvinge
a2e35e30dc Use lowercase for divider keys sort text
Fixes #556
2020-10-10 18:44:57 +02:00
Jonas Kvinge
b6ff7e6b47 Fix transition to next song in CUE files
Fixes #552
2020-10-10 01:57:02 +02:00
Jonas Kvinge
6dba40c6bb Turn back git revision 2020-10-10 01:21:13 +02:00
Jonas Kvinge
896da46422 Release 0.8.1 2020-10-09 21:38:00 +02:00
Jonas Kvinge
7c07f5eb2a Add clang-format file 2020-10-09 21:02:18 +02:00
Strawbs Bot
298cff37de Update translations 2020-10-09 01:01:57 +02:00
Jonas Kvinge
8add802fe9 Update Changelog 2020-10-08 19:42:33 +02:00
Jonas Kvinge
6d080a0d59 Fix crash when copying a closed playlist to a device
Fixes #551
2020-10-08 19:19:39 +02:00
Jonas Kvinge
f0ae1051ee Only set art manual for temporary metadata when temp metadata is set 2020-10-08 19:14:56 +02:00
Strawbs Bot
1ad0ffeaa6 Update translations 2020-10-08 01:02:36 +02:00
Jonas Kvinge
45b44d012d Fix source for CUE songs in collection watcher 2020-10-07 23:27:58 +02:00
Jonas Kvinge
cb6cbb9ee5 Remove unused screensaver classes 2020-10-07 22:10:31 +02:00
Jonas Kvinge
013a0d9cb0 Remove commented line 2020-10-07 21:28:52 +02:00
Jonas Kvinge
8abb3ea225 Fix backend settings tab order 2020-10-07 20:43:15 +02:00
Jonas Kvinge
74a5233b5d Replace use of deprecated gstreamer low-percent
- Add settings for low-watermark and high-watermark
- Add button to reset buffer settings to defaults
2020-10-07 20:29:26 +02:00
Strawbs Bot
cd695a4522 Update translations 2020-10-06 01:02:00 +02:00
Jonas Kvinge
eec4b2cc0e Call QLocalSocket::flush() after QLocalSocket::waitForBytesWritten() 2020-10-05 22:05:37 +02:00
Jonas Kvinge
616875d0d2 Log when sending message to primary instance fails 2020-10-05 22:04:58 +02:00
Jonas Kvinge
eb749bd76f Fix compile 2020-10-05 21:40:31 +02:00
Jonas Kvinge
7d1e404efd Fix header guard 2020-10-05 21:34:45 +02:00
Jonas Kvinge
4a01a578d1 Update singleapplication 2020-10-05 21:31:03 +02:00
Jonas Kvinge
393a2e0ea0 Set hide to false when window is shown 2020-10-05 19:08:09 +02:00
Jonas Kvinge
8e21decb8d Add WASAPI plugin to own setup file
Seem to still be stability issues.
2020-10-04 19:24:08 +02:00
Jonas Kvinge
22bfbab832 Add check for desktop notifications service version 2020-10-04 04:38:46 +02:00
Jonas Kvinge
28d0cc8795 Format singleapplication sources 2020-10-04 02:18:10 +02:00
Jonas Kvinge
51f2383a07 Remove lyrics from fandom.com
API no longer exists.
2020-10-04 02:13:06 +02:00
Jonas Kvinge
a6426c6eba Update Changelog 2020-10-04 02:00:12 +02:00
Jonas Kvinge
0631da6c8e Register Tidal URL Scheme in Windows installer 2020-10-04 01:59:40 +02:00
Jonas Kvinge
3960239cfe Add back WASAPI plugin again 2020-10-03 20:30:14 +02:00
Jonas Kvinge
b16e48af6f Add back WASAPI plugin again 2020-10-03 20:29:05 +02:00
Jonas Kvinge
4d3950565a Remove scangiomodulepath.cpp from CMakeLists.txt 2020-10-03 20:10:38 +02:00
Jonas Kvinge
8d24bc50c9 Change GST to GStreamer in debug logging 2020-10-03 20:08:33 +02:00
Jonas Kvinge
458efe9168 Remove gstreamer registry workaround for Windows 2020-10-03 20:05:30 +02:00
Jonas Kvinge
ae940a8681 Fix PRODUCT_NAME 2020-10-03 19:37:29 +02:00
Jonas Kvinge
d76b223b7c Fix typo 2020-10-03 19:36:10 +02:00
Jonas Kvinge
7243d5f7cb Add WASAPI plugin to own setup 2020-10-03 19:29:04 +02:00
Jonas Kvinge
387f74f66a Revert "Add back WASAPI plugin"
This reverts commit a553693f34.
2020-10-03 17:38:31 +02:00
Jonas Kvinge
61ffb7d97a Unref bus 2020-10-03 13:09:09 +02:00
Jonas Kvinge
a553693f34 Add back WASAPI plugin
Fixes #283
2020-10-03 02:03:37 +02:00
Jonas Kvinge
cdcfd64ec4 Possible fix for WASAPI crashes 2020-10-03 01:58:52 +02:00
Jonas Kvinge
7f4302ba20 Update Changelog 2020-10-02 22:09:40 +02:00
Jonas Kvinge
0d94f2d376 Update README 2020-10-02 22:09:33 +02:00
Jonas Kvinge
c1eb20a20b Force inform in Player::NextItem 2020-10-02 20:38:14 +02:00
Jonas Kvinge
634db67685 Change branch to master 2020-10-02 19:29:55 +02:00
Jonas Kvinge
86648258a3 Add CI for snap 2020-10-02 19:29:26 +02:00
Jonas Kvinge
287f0a3739 Remove redundant check for isNull() 2020-10-02 19:27:47 +02:00
Strawbs Bot
8f42df209a Update translations 2020-10-02 01:07:32 +02:00
Jonas Kvinge
390fd64a74 Save initial settings 2020-10-01 22:04:38 +02:00
Jonas Kvinge
48ee471def Remove force inform in PreviousItem 2020-10-01 20:30:31 +02:00
Jonas Kvinge
e862afcb78 Log song change 2020-10-01 20:09:36 +02:00
Jonas Kvinge
872da05ff6 Inform of song change on play restart, add playlist auto sorting.
Fixes #511
2020-10-01 19:58:16 +02:00
Jonas Kvinge
d09e2daf00 Mark backend settings changed if loaded engine, output or device does
not match the configured.
2020-10-01 19:53:07 +02:00
Jonas Kvinge
e2d5b44b0a Hide certain playlist list context menu options when items are not selected 2020-10-01 19:49:06 +02:00
Jonas Kvinge
52d42ef2a8 Use BUILD_WITH_QT6 2020-10-01 19:47:21 +02:00
Jonas Kvinge
b6abc34461 Set CollectionWatcher::sValidImages directly 2020-10-01 19:45:10 +02:00
Jonas Kvinge
ae6a50626d Change Qt 5/6 option 2020-10-01 19:43:39 +02:00
Jonas Kvinge
8f9dbfee2c Replace QMacCocoaViewContainer with QWidget::createWindowContainer 2020-10-01 19:40:55 +02:00
Jonas Kvinge
c71ccda967 Remove '&' in OSD messages
Causes weird problems with previous text getting stuck.
2020-10-01 19:39:49 +02:00
Strawbs Bot
0f284f2e1d Update translations 2020-10-01 01:01:35 +02:00
Jonas Kvinge
45ac80dd8c Change order of artist - title when song is paused/stopped in OSD 2020-09-30 01:14:00 +02:00
Jonas Kvinge
09cdba4b3d Fix minor code issues 2020-09-30 01:02:41 +02:00
Jonas Kvinge
d94ee8863c Fix playing widget stuck on error 2020-09-30 00:44:40 +02:00
Jonas Kvinge
cc8ced6430 Disable live scanning option 2020-09-30 00:03:06 +02:00
Jonas Kvinge
cdb144e6a6 Update Changelog 2020-09-29 23:26:55 +02:00
Jonas Kvinge
58fb8643c6 Fix typo in README 2020-09-29 23:26:26 +02:00
Jonas Kvinge
b81cfa8acb Update stream url to avoid requesting it twice 2020-09-29 23:03:50 +02:00
Jonas Kvinge
938ee20f1f Make sure song changed is only called once 2020-09-29 22:40:43 +02:00
Jonas Kvinge
d02dc54c1b Log remote cover loading 2020-09-29 22:30:44 +02:00
Jonas Kvinge
8680a54ae4 Only draw text when show/hide timeline is finished 2020-09-29 20:22:11 +02:00
Jonas Kvinge
30001be0ee Show song in OSD when pausing and stopping 2020-09-29 19:27:35 +02:00
Jonas Kvinge
4614cb5ec1 Make sure same cover isn't loaded twice 2020-09-29 17:51:31 +02:00
Jonas Kvinge
e390f3a399 Clear now playing in MainWindow::MediaPlaying() instead
Fixes #548
2020-09-29 17:38:00 +02:00
Jonas Kvinge
e22d463d11 Use QFileInfo::completeBaseName() instead of QFileInfo::baseName()
Fixes #550
2020-09-29 17:30:21 +02:00
Jonas Kvinge
5877aa822c Don't reset playing widget timeline 2020-09-29 01:07:04 +02:00
Strawbs Bot
a2915913bb Update translations 2020-09-29 01:02:40 +02:00
Jonas Kvinge
a8b40747b2 Attempt to improve playing widget up/down 2020-09-28 20:09:23 +02:00
Jonas Kvinge
b63030d302 Don't send now playing twice
Fixes #548
2020-09-28 17:47:45 +02:00
Strawbs Bot
4457e416db Update translations 2020-09-26 01:01:50 +02:00
Pascal Below
c7c1a8ede1 SubsonicRequest: create cover directory if it doesn't exist (#547) 2020-09-25 15:46:25 +02:00
Jonas Kvinge
fd1e9d7fb0 Fix clearing sent in scrobbler cache 2020-09-24 17:02:43 +02:00
Strawbs Bot
59a6d2317b Update translations 2020-09-24 01:02:04 +02:00
Jonas Kvinge
531c171542 Merge branch 'debian' into master 2020-09-23 19:06:22 +02:00
Jonas Kvinge
0c743452b0 Only compile Subsonic scrobbler when compiled with Subsonic support 2020-09-23 18:55:22 +02:00
Jonas Kvinge
4893d3da1f Add back debian bullseye and add dh-make to ubuntu too 2020-09-23 18:42:56 +02:00
Jonas Kvinge
5cdc24bfb0 Reload Subsonic scrobbler settings 2020-09-23 18:31:09 +02:00
Jonas Kvinge
523cdca449 Add back dh-make 2020-09-23 18:23:59 +02:00
Jonas Kvinge
09537d73da Remove debian bullseye CI 2020-09-23 18:10:13 +02:00
Jonas Kvinge
99c0c0b3b1 Update Changelog 2020-09-23 17:59:51 +02:00
Pascal Below
45bc353341 Add Subsonic scrobble support (#545)
* add SubsonicScrobbler, add Scrobble method in SubsonicService

* new class SubsonicScrobbleRequest, use queue again, clean up

* add checkbox to enable server-side scrobbling to Subsonic settings page

* Check serversidescrobbling in SubsonicScrobbler::ReloadSettings instead of SubsonicService

TODO: SubsonicScrobbler::ReloadSettings needs to be called when
SubsonicSettings change.
2020-09-23 17:55:12 +02:00
Strawbs Bot
b2fc41a911 Update translations 2020-09-23 01:02:50 +02:00
Jonas Kvinge
ebefe8b6d2 Update copyright 2020-09-23 00:55:34 +02:00
Jonas Kvinge
9e3508134b Add compilation to edit tag dialog 2020-09-23 00:52:41 +02:00
Jonas Kvinge
3166ca2127 Use QRecursiveMutex 2020-09-22 18:58:44 +02:00
Jonas Kvinge
204508478f Don't ignore closeevent if already hidden
Possible fix for #277
2020-09-22 18:40:37 +02:00
Strawbs Bot
51ce674c97 Update translations 2020-09-22 01:02:29 +02:00
Jonas Kvinge
6fcde9fe5f Add qobuz to scrobbler settings 2020-09-22 00:00:02 +02:00
Jonas Kvinge
c688e3431d Fix debian builds 2020-09-20 15:55:25 +02:00
Jonas Kvinge
230376c7f3 Ignore unused variable in MoveToTrashRecursive 2020-09-20 14:55:52 +02:00
Jonas Kvinge
91d0a2cd0c Fix uninitialized variable 2020-09-19 02:10:16 +02:00
Jonas Kvinge
1ffd010e4a Add windows.h include to MM device finder
Fixes compile error with mingw-w64 8.0.0
2020-09-19 02:06:15 +02:00
Strawbs Bot
ae366e141f Update translations 2020-09-19 01:01:38 +02:00
Strawbs Bot
eb869c6e97 Update translations 2020-09-18 01:08:32 +02:00
Jonas Kvinge
b559f7cd16 Fix minor code issues 2020-09-17 18:12:14 +02:00
Jonas Kvinge
375d32f08e Add missing utilities include 2020-09-17 18:01:24 +02:00
Jonas Kvinge
89d6b7cec0 Add smart playlists, ratings and Qobuz
Fixes #259
Fixes #264
2020-09-17 17:50:17 +02:00
Jonas Kvinge
fdf96e8342 Update README 2020-09-17 17:49:39 +02:00
Jonas Kvinge
c5d73d7b09 Update Changelog 2020-09-17 17:49:25 +02:00
Jonas Kvinge
34b4cc2d9e Remove unused function 2020-09-17 17:28:32 +02:00
Jonas Kvinge
40ed17e609 Change defaults for context 2020-09-17 17:12:01 +02:00
Jonas Kvinge
673d76af45 Change defaults for Tidal 2020-09-17 17:07:54 +02:00
Jonas Kvinge
c13fb6f9d5 Use QLibraryInfo::path with Qt 6 2020-09-17 17:00:45 +02:00
Jonas Kvinge
424b1e7d1f Fix strawberry.spec for Mageia 2020-09-17 16:28:16 +02:00
Jonas Kvinge
e80c9c4101 Fix strawberry.spec for CentOS and Mageia 2020-09-16 19:21:37 +02:00
Jonas Kvinge
f75c3633e9 Add Fedora 34 CI 2020-09-16 18:11:34 +02:00
Jonas Kvinge
d535cd8276 Add Fedora 33 to CI 2020-09-16 18:08:33 +02:00
Jonas Kvinge
c13f6ab2af Remove brew unlink python@2 2020-09-16 18:05:49 +02:00
Jonas Kvinge
d2a30bfb78 Simplify strawberry.spec 2020-09-16 18:03:34 +02:00
Jonas Kvinge
e5b17092b4 Fix stretchheaderview column widths too wide 2020-09-16 00:01:16 +02:00
Strawbs Bot
466cb4c78b Update translations 2020-09-15 01:01:29 +02:00
Strawbs Bot
9722e1ddc4 Update translations 2020-09-13 01:01:42 +02:00
Jonas Kvinge
e3a9b0b943 Use collection settings for group by settings 2020-09-12 15:48:26 +02:00
Jonas Kvinge
d668a8aee2 Change sort text for divider keys
Fixes problems on Windows with specific regionale settings, where
divider keys are all on the top, while albums are on the bottom
2020-09-12 13:30:45 +02:00
Strawbs Bot
75dc0aafcf Update translations 2020-09-12 01:02:03 +02:00
Jonas Kvinge
41b94233c6 Add group by version check in internet search view too 2020-09-12 00:32:50 +02:00
Jonas Kvinge
52cff01b9c Fix group by version 2020-09-12 00:18:08 +02:00
Jonas Kvinge
372fc6603d Update .github/workflows/ccpp.yml 2020-09-11 23:35:21 +02:00
Jonas Kvinge
9221797c9d Add sqlite3.exe to nsi 2020-09-11 22:58:56 +02:00
Strawbs Bot
7501131558 Update translations 2020-09-11 01:04:26 +02:00
Jonas Kvinge
bba7be99a3 Remove override.h 2020-09-10 23:41:53 +02:00
Jonas Kvinge
abb95534d0 Add musepack to macdeploy 2020-09-10 23:06:10 +02:00
Jonas Kvinge
0b7b4c12e4 Add test for collection model container nodes 2020-09-10 22:13:20 +02:00
Jonas Kvinge
4056f00169 Only initialize translations in test when compiled with translations 2020-09-10 22:11:40 +02:00
Jonas Kvinge
10303cb9c0 Use unique keys for all container nodes in collection model
Fixes #539 and probably many more issues with the model.
2020-09-10 22:09:24 +02:00
Jonas Kvinge
e3587d369e Add const 2020-09-10 22:05:12 +02:00
Jonas Kvinge
2a048502cc Add PlaylistItem::NewFromSong function 2020-09-10 22:04:11 +02:00
Jonas Kvinge
cac01fbde9 Dont seperate compilation albums by directory in GetAlbums
Fixes issue where multi-disc albums are showing more than once in cover
manager.
2020-09-10 21:27:07 +02:00
Jonas Kvinge
2d2ce191ec Change gstreamer libraries in macdeploy 2020-09-10 19:15:06 +02:00
Jonas Kvinge
6380cb8458 Add brew upgrade step in CI 2020-09-10 19:14:33 +02:00
Jonas Kvinge
48e0e6af71 Ignore Radio Paradise "commercial" break for cover and lyrics search 2020-09-10 17:17:55 +02:00
Jonas Kvinge
b756bccc7a Link to iconv to fix compile on Windows 2020-09-10 17:14:14 +02:00
Jonas Kvinge
ae8eed7a67 Add ffmpeg to nsi 2020-09-10 17:12:18 +02:00
Strawbs Bot
ae4882bec3 Update translations 2020-09-10 01:03:26 +02:00
Strawbs Bot
1379741859 Update translations 2020-09-09 01:02:25 +02:00
Jonas Kvinge
b45c7ace78 Fix show equalizer signal slot connect 2020-09-08 18:12:18 +02:00
Strawbs Bot
3843d9f55b Update translations 2020-09-06 01:02:20 +02:00
Jonas Kvinge
f3422cb2fe Call raise() on dialogs to make sure they are on top
Fixes #535
2020-09-05 19:54:21 +02:00
Jonas Kvinge
73692797dc Fix QWidget::enterEvent with Qt 6 2020-09-05 19:20:43 +02:00
Jonas Kvinge
31dd910289 Make sure albums in cover manager are unique
Fixes #529
2020-09-05 18:59:35 +02:00
Jonas Kvinge
da51580299 Fix year - album collection grouping
Fixes #534
2020-09-05 17:26:42 +02:00
Strawbs Bot
9db148b1ec Update translations 2020-09-05 01:03:16 +02:00
Jonas Kvinge
091b1b8209 Use QKeyCombination with Qt 6 2020-09-04 23:00:42 +02:00
Jonas Kvinge
900a4071ed Use bytearray for qChecksum with Qt 6 2020-09-04 21:54:59 +02:00
Jonas Kvinge
df4b2f7938 Add icon for edit tag playlist right click menu actions
Fixes #531
2020-09-04 20:55:57 +02:00
Jonas Kvinge
803f44d9bc Disable use system icons setting for macOS and Windows
Fixes #532
2020-09-04 20:51:43 +02:00
Jonas Kvinge
4b67aee020 Use | operator for QShortcut 2020-09-04 20:43:02 +02:00
Jonas Kvinge
71dc47d6c9 Use deleteLater() when destroying lazy initialized objects
Fixes #530
2020-09-04 20:25:46 +02:00
Strawbs Bot
9cb305fb0d Update translations 2020-09-04 01:02:02 +02:00
Jonas Kvinge
1672130486 Merge compilation off/off
Fixes #528
2020-09-03 19:20:59 +02:00
Jonas Kvinge
8e135e3e79 Set compilation for "Various Artists" in album artist tag too 2020-09-03 19:20:11 +02:00
Jonas Kvinge
aa1a4f6ea5 Update verify icons script 2020-09-03 17:04:40 +02:00
Jonas Kvinge
ba34cf5258 Possible fix for crash when deleting queued songs from playlist
See #527
2020-09-03 16:59:18 +02:00
Strawbs Bot
647089d2a8 Update translations 2020-09-03 01:02:25 +02:00
Jonas Kvinge
5211508eb4 Add multimedia-player-ipod-standard-black.png 2020-09-02 23:20:01 +02:00
Jonas Kvinge
e6f05ae465 Make sure icon exists in GuessIconForPath 2020-09-02 20:40:07 +02:00
Jonas Kvinge
a9193f9b76 Move itdb_device_free 2020-09-02 20:03:22 +02:00
Jonas Kvinge
e373a17cd3 Fallback to device-ipod for ipod icon name 2020-09-02 19:36:47 +02:00
Jonas Kvinge
ebab9b7e4a Delete deviceinfo using parent object 2020-09-02 19:35:13 +02:00
Jonas Kvinge
6de0399807 Guess icon for device in udisks2 lister 2020-09-02 19:34:46 +02:00
Strawbs Bot
5cc7bb80f6 Update translations 2020-09-02 01:11:51 +02:00
Jonas Kvinge
6e0bd9b3f8 Add override to LastFMImportDialog::closeEvent() 2020-09-01 22:27:34 +02:00
Jonas Kvinge
d1943f72d3 Remove use of Qt::AA_UseHighDpiPixmaps with Qt 6 2020-09-01 21:05:07 +02:00
Jonas Kvinge
81709873bd Use art_manual for itdb thumbnail 2020-09-01 20:59:19 +02:00
Jonas Kvinge
f6bb7cd8ed Only include Dmg.cmake on macOS 2020-09-01 20:40:14 +02:00
Jonas Kvinge
d1c19e431c Add check for gdk-pixbuf-2.0 2020-09-01 20:27:05 +02:00
Jonas Kvinge
9ab2dde8ab Read tumbnails from itdb tracks 2020-09-01 01:04:01 +02:00
Jonas Kvinge
71559bb125 Turn pretty covers on by default in collection model 2020-09-01 01:02:02 +02:00
Strawbs Bot
ae4c95262c Update translations 2020-09-01 01:01:45 +02:00
Jonas Kvinge
dbbf07c9c1 Specify JPG when saving cover to temp file for iPod's 2020-08-31 20:13:53 +02:00
Jonas Kvinge
ab8cd619d5 Save cover image to file before copying to iPod
Fixes #519
2020-08-31 18:38:54 +02:00
Jonas Kvinge
c30ad2819d Disable use of HTML in system tray tooltip on Cinnamon 2020-08-31 18:06:37 +02:00
Jonas Kvinge
311e91797a Update ISSUE_TEMPLATE.md 2020-08-31 17:53:46 +02:00
Jonas Kvinge
52be4df355 Use only one issue template 2020-08-31 17:52:16 +02:00
Jonas Kvinge
0364e81050 Remove mageia from Circle CI 2020-08-31 17:15:31 +02:00
Jonas Kvinge
2d49b71bc9 Read song creation time from subsonic API
Fixes #526
2020-08-31 17:05:09 +02:00
Jonas Kvinge
a18a4bdf31 Set wordwrap 2020-08-31 16:25:01 +02:00
Strawbs Bot
d3acbe8288 Update translations 2020-08-31 01:04:42 +02:00
Jonas Kvinge
22afcbcbb6 Only allow playlist editing if song is editable
Set proper flags in model instead of overriding edit in view.

Proper fix for #524
2020-08-30 22:23:38 +02:00
Jonas Kvinge
495c6bc21c Remove unused StyleHelper::drawIconWithShadow function 2020-08-30 21:51:26 +02:00
Jonas Kvinge
cfd1fe59f3 Only allow playlist editing if song is editable.
Fixes #524
2020-08-30 21:40:04 +02:00
Jonas Kvinge
c46cf5bc84 Check for missing lyrics 2020-08-30 21:13:31 +02:00
Jonas Kvinge
a8742557bd Add lyrics from fandom.com 2020-08-30 21:06:58 +02:00
Jonas Kvinge
3bea58cf56 Fix resetting last.fm import dialog on close when finished 2020-08-30 18:28:51 +02:00
Jonas Kvinge
5aaa5231b8 Add Last.fm import
Fixes #247
2020-08-30 18:09:13 +02:00
Jonas Kvinge
82d10dd7cb Remove debug line 2020-08-30 01:59:26 +02:00
Jonas Kvinge
841065fb91 Load icons for buttons before setting enabled/disabled
Fixes #500
2020-08-30 01:57:21 +02:00
Strawbs Bot
c7146ef138 Update translations 2020-08-30 01:02:19 +02:00
Jonas Kvinge
08f32d1de6 Refactor playlist view/header code
- Don't reload all settings when changing playlists
- Fix initial playlist header columns sizes
- Properly reset header state when resetting columns
2020-08-29 19:55:00 +02:00
Jonas Kvinge
4c3f86aa4d Remove use of Qt::AA_EnableHighDpiScaling with Qt 6 2020-08-29 19:18:56 +02:00
Jonas Kvinge
445cf22333 Use deleteLater 2020-08-29 16:24:40 +02:00
Strawbs Bot
4919de647a Update translations 2020-08-28 01:01:40 +02:00
Jonas Kvinge
77fae99528 Fix spacing for sources in scrobbling settings
Fixes #523
2020-08-27 23:36:09 +02:00
Jonas Kvinge
10bd4b40b9 Switch branch for macOS upload 2020-08-27 22:51:34 +02:00
Jonas Kvinge
9d8e6bb253 Merge branch 'macos' into master 2020-08-27 22:50:11 +02:00
Jonas Kvinge
49c71ecfad Update macOS CI 2020-08-27 22:11:19 +02:00
Strawbs Bot
d18834b415 Update translations 2020-08-27 01:02:03 +02:00
Jonas Kvinge
e52cda193e Replace QAbstractItemView::viewOptions with initViewItemOption 2020-08-26 23:35:33 +02:00
Jonas Kvinge
0d5edd35ea Register ColumnAlignmentMap 2020-08-26 22:36:09 +02:00
Strawbs Bot
f3f51c3d9d Update translations 2020-08-26 01:08:17 +02:00
Jonas Kvinge
1431916183 Add setting for enabling scrobbling based on song source
Fixes #416
2020-08-25 23:44:27 +02:00
Jonas Kvinge
ae48008803 Remove use of qRegisterMetaTypeStreamOperators with Qt 6 2020-08-25 22:48:21 +02:00
Jonas Kvinge
5664813931 Remove Mageia from GitHub CI 2020-08-25 21:57:09 +02:00
Jonas Kvinge
3948af80b8 Fix pixelated source icon for currently playing song in playlist 2020-08-25 21:51:23 +02:00
Strawbs Bot
343d6f9aff Update translations 2020-08-24 01:03:49 +02:00
Jonas Kvinge
51b379502f Always use qint32 for QDBusArgument 2020-08-23 21:55:34 +02:00
Jonas Kvinge
82142751de Improve playlist autoscrolling
Fixes #420
2020-08-23 19:37:24 +02:00
Jonas Kvinge
4e5755f218 Refactor some functions 2020-08-23 19:17:50 +02:00
Jonas Kvinge
2f4f29517e Create new contructor for UrlHandler 2020-08-23 18:52:30 +02:00
Strawbs Bot
e8a073651f Update translations 2020-08-23 05:53:13 +02:00
Jonas Kvinge
d23da7a612 Replace Qt::MidButton with Qt::MiddleButton 2020-08-23 03:27:24 +02:00
Jonas Kvinge
1ae4938da3 Register D-Bus metatype for QImage if system has D-Bus 2020-08-23 03:20:53 +02:00
Jonas Kvinge
b5e27d4d69 Declare QDBusArgument for QImage in osddbus.h 2020-08-23 03:19:40 +02:00
Jonas Kvinge
d74fc8d1ce Fix macoOS builds in Travis CI 2020-08-22 14:02:12 +02:00
Jonas Kvinge
c5d9a41967 Remove tumbleweed from CircleCI 2020-08-22 00:27:12 +02:00
Jonas Kvinge
7e3042c4f4 Remove AA_DontShowIconsInMenus
Possible fix for #516
2020-08-21 23:40:44 +02:00
Jonas Kvinge
1291dadbd6 Replace use of QStringRef 2020-08-21 22:39:01 +02:00
Jonas Kvinge
f645099a39 Remove FMPS parser 2020-08-21 22:38:40 +02:00
Jonas Kvinge
8f32038891 Add missing musicstorage include 2020-08-19 22:35:35 +02:00
Jonas Kvinge
a6fb1dcdf9 Add missing QWindow include 2020-08-19 22:35:22 +02:00
Jonas Kvinge
f01b469f3f Allow to delete files permanently in fileview with Qt < 5.15 2020-08-19 22:25:05 +02:00
Jonas Kvinge
47884919c8 Only show delete files option in setting with Qt >= 5.15 2020-08-19 22:11:03 +02:00
Jonas Kvinge
653a35496d Add optional delete from disk in collection and playlist
Fixes #284
2020-08-19 22:02:35 +02:00
Jonas Kvinge
9b14df6b27 Use static_cast in QComboBox::findData 2020-08-16 18:22:10 +02:00
Jonas Kvinge
09813f3c5a Turn back git revision 2020-08-16 03:02:27 +02:00
Jonas Kvinge
7d76b7e4c2 Update protobuf version in nsi 2020-08-15 23:15:51 +02:00
Jonas Kvinge
47b5dea95c Release 0.7.2 2020-08-15 23:01:56 +02:00
Jonas Kvinge
b0df63f1e8 Fix translations dir 2020-08-15 23:01:28 +02:00
Jonas Kvinge
3c4209b676 Add more compilation titles 2020-08-15 17:33:54 +02:00
Jonas Kvinge
d51b9a8e0e Add more compilation titles 2020-08-15 17:29:30 +02:00
Jonas Kvinge
3b56125bd2 Increase maximum time step for seeking to 120
Fixes #509
2020-08-15 15:18:58 +02:00
Jonas Kvinge
6e69e39007 Use static_cast instead for destroyed object 2020-08-15 15:16:06 +02:00
Jonas Kvinge
97208cb329 Add --auto to urpmi command for Mageia CI 2020-08-15 11:55:49 +02:00
Jonas Kvinge
414a4a97fb Use unicode option when replacing non-words
Fixes #513
2020-08-15 11:43:14 +02:00
Jonas Kvinge
2a809f96c4 Update Changelog 2020-08-15 11:31:29 +02:00
Jonas Kvinge
17799b03f3 Fix installation directory for translations
Fixes #512
2020-08-15 11:08:47 +02:00
Jonas Kvinge
efc55fc648 Fix typo in snapcraft.yaml 2020-08-15 02:36:54 +02:00
Jonas Kvinge
22bd41211a Turn back git revision 2020-08-15 02:36:37 +02:00
Jonas Kvinge
4cb0171bd0 Release 0.7.1 2020-08-15 00:44:28 +02:00
Strawbs Bot
ce6c5af72c Update translations 2020-08-15 00:25:52 +02:00
Jonas Kvinge
171575256c Remove broken iPhone (libimobiledevice) support
Fixes #212
2020-08-14 21:38:08 +02:00
Jonas Kvinge
d3664dcf78 Set QNetworkRequest::RedirectPolicyAttribute with Qt >= 5.9 2020-08-14 20:31:04 +02:00
Jonas Kvinge
0788981783 Set QNetworkRequest::RedirectPolicyAttribute with Qt >= 5.9 2020-08-14 20:20:41 +02:00
Jonas Kvinge
aeee7c02d5 Update Changelog 2020-08-14 18:39:56 +02:00
Jonas Kvinge
6e49a50461 Update Changelog 2020-08-14 18:38:50 +02:00
Jonas Kvinge
fbc99827ab Revert "Turn off sort indicators for playlist"
This reverts commit 7b50ec4630.
2020-08-14 17:30:27 +02:00
Jonas Kvinge
3b134320c4 Fix minor issue in cue parser with date and genre 2020-08-13 21:14:12 +02:00
Jonas Kvinge
c315e5016d Change mtime and ctime to qint64 2020-08-13 21:09:06 +02:00
Jonas Kvinge
7aebd6ed57 Only install translations if HAVE_TRANSLATIONS is set 2020-08-13 21:05:09 +02:00
Jonas Kvinge
1a8ca06495 Only install translations when INSTALL_TRANSLATIONS is set 2020-08-13 20:55:53 +02:00
Jonas Kvinge
a27ae7e4a6 Add CMake option to install translations
Fixes #485
2020-08-13 19:53:36 +02:00
Strawbs Bot
dd0ab897aa Update translations 2020-08-13 01:03:32 +02:00
Jonas Kvinge
00ad92fb6d Hide unavailable collection context menu actions 2020-08-12 21:34:42 +02:00
Jonas Kvinge
f84128ecbd Remove unused collection playlist container type 2020-08-12 21:33:38 +02:00
Jonas Kvinge
ba89e0f4e3 Fix MockPlaylistItem 2020-08-12 20:03:45 +02:00
Jonas Kvinge
832d36a4d4 Add Qt 6 option to strawberry.nsi 2020-08-12 18:07:21 +02:00
Jonas Kvinge
0b437b3bfb Use standard text color for links in about
Fixes #508
2020-08-12 17:27:08 +02:00
Jonas Kvinge
7b50ec4630 Turn off sort indicators for playlist
Fixes #511
2020-08-12 16:56:28 +02:00
Jonas Kvinge
4ddb13abac Increase maximum time step for seeking to 60
Fixes #509
2020-08-12 16:31:32 +02:00
Jonas Kvinge
d2ac081177 Fix taglib cmake type size checks 2020-08-11 16:23:07 +02:00
Strawbs Bot
9692fbf15b Update translations 2020-08-11 01:02:03 +02:00
Jonas Kvinge
be966488e8 Fix OSD D-Bus assertion with Qt 6 2020-08-10 23:05:07 +02:00
Jonas Kvinge
0ce613264f Make sure to always use original metadata when editing tags 2020-08-10 21:32:14 +02:00
Jonas Kvinge
34634d776e Make sure to always use original metadata when editing tags 2020-08-10 21:27:56 +02:00
Jonas Kvinge
673ded3819 Add proper check for collection song in edit tag dialog 2020-08-10 21:27:27 +02:00
Jonas Kvinge
b9a94ad3ae Default originalyear to -1 2020-08-10 18:06:20 +02:00
Jonas Kvinge
3f80b330cc Log artist and album name 2020-08-10 18:05:52 +02:00
Jonas Kvinge
01632d538c Decrease score for more compilation albums 2020-08-10 17:39:40 +02:00
Strawbs Bot
b0a9b1cd09 Update translations 2020-08-10 01:05:14 +02:00
Jonas Kvinge
1f772081fd Only update temporary metadata when set
Fixes #507
2020-08-10 00:32:57 +02:00
Jonas Kvinge
4ae54dbaad Decrease score for more compilation albums 2020-08-09 20:58:27 +02:00
Jonas Kvinge
e47f4ff731 Fix musixmatch cover size 2020-08-09 20:15:24 +02:00
Jonas Kvinge
465369d79e Base initial score on album cover sizes retrieved from API 2020-08-09 20:10:53 +02:00
Jonas Kvinge
15ddf6ff20 Save and restore playlist scrollbar position when switching between playlists 2020-08-09 14:00:56 +02:00
Jonas Kvinge
16a753bd95 Treat erors returned by the URL handler as non fatal
Fixes #505
2020-08-09 02:52:18 +02:00
Jonas Kvinge
c15103636c Fix NotificationPreview signal slot 2020-08-09 02:07:22 +02:00
Jonas Kvinge
8a5f82ee7d Tidal: Only return streamable songs in result
Fixes #505
2020-08-09 01:59:28 +02:00
Jonas Kvinge
5ec33ec821 Tidal: Show API error instead of network error when available 2020-08-09 01:50:03 +02:00
Jonas Kvinge
ab7d383cf1 Use virtual functions for OSD 2020-08-09 01:37:00 +02:00
Strawbs Bot
184e9a5c93 Update translations 2020-08-09 01:01:43 +02:00
Jonas Kvinge
c4f5363cde Properly enable/disable queue buttons depending on selection 2020-08-08 20:36:03 +02:00
Jonas Kvinge
002882cebf Build Qt 6 without linguist, qtbase code keeps updating breaking qt-tools 2020-08-08 19:20:27 +02:00
Jonas Kvinge
1cb3ec0c7b Only add autocomplete tags to playlist menu when we have chromaprint and gstreamer 2020-08-08 19:05:14 +02:00
Jonas Kvinge
6bf325c6f6 Fix QSslSocket::ignoreSslErrors compile error with Qt 6 2020-08-08 19:04:44 +02:00
Strawbs Bot
09c7ff9e8b Update translations 2020-08-08 01:01:46 +02:00
Jonas Kvinge
c2a94b61bf Fixes to playlist context menu
- Add all playlist actions to initialization list
- Make rescan songs work for non-collection songs by using playlist item reload
- Only show add to another playlist and remove from playlist when songs are selected
- Add some missing icons

Fixes #503
2020-08-07 22:13:02 +02:00
Jonas Kvinge
1db16232de Only show rescan songs for collection songs
Fixes #503
2020-08-07 21:18:48 +02:00
Jonas Kvinge
3da681a6b1 Add fatal error for missing protobuf compiler 2020-08-07 19:43:03 +02:00
Strawbs Bot
a79b3e7852 Update translations 2020-08-07 01:03:20 +02:00
Jonas Kvinge
b1099e6974 Handle metadata with tilde in title 2020-08-07 00:52:09 +02:00
Jonas Kvinge
4f6e06131c Change allow album cover search check 2020-08-07 00:28:46 +02:00
Jonas Kvinge
19f69e9e6c Allow cover search only using either artist, album or title 2020-08-07 00:18:31 +02:00
Jonas Kvinge
01481da773 Use Qt::QueuedConnection for cover fetcher 2020-08-06 23:55:44 +02:00
Jonas Kvinge
3e8f7e1cf1 Register CoverSearchStatistics metatype 2020-08-06 23:54:54 +02:00
Jonas Kvinge
5da69646f2 Add authentication for Qobuz cover provider 2020-08-06 22:57:44 +02:00
Jonas Kvinge
3cac01583b Add username password dialog 2020-08-06 22:54:21 +02:00
Jonas Kvinge
d16a26605e Fix updating playlist songs when there are multiple files with the same URL
Fixes #501
2020-08-06 21:40:42 +02:00
Jonas Kvinge
a4f692c788 Only show playlist add file(s) to transcoder when songs are selected 2020-08-06 18:37:17 +02:00
Jonas Kvinge
9f01206c57 Only show open in file browser when songs are selected 2020-08-06 18:36:52 +02:00
Jonas Kvinge
d34fc551ed Add playlist right click option to copy URL 2020-08-06 18:29:35 +02:00
Jonas Kvinge
7aa5f0d258 Only show delete and save playlist button when item is selected
Fixes #500
2020-08-06 16:00:03 +02:00
Jonas Kvinge
276a34bb66 Fix parsing Tidal track duration with Qt 6 2020-08-06 15:58:53 +02:00
Jonas Kvinge
0d820eda12 Remove diacritics in FTS search 2020-08-05 23:31:52 +02:00
Strawbs Bot
1991c1b677 Update translations 2020-08-05 01:02:22 +02:00
Jonas Kvinge
459404e3f0 Rename organise to organize
Prefer US spelling
2020-08-04 21:18:14 +02:00
Jonas Kvinge
badc623a3c Update Changelog 2020-08-03 21:53:31 +02:00
Jonas Kvinge
8e39f92cb7 Make album optional when reading scrobbles from cache 2020-08-03 21:50:26 +02:00
Jonas Kvinge
3ff4885973 Fix reading ASF comment tag 2020-08-03 21:03:14 +02:00
Jonas Kvinge
f9d45f7657 Fix reading and saving MP4 lyrics tag 2020-08-03 20:44:25 +02:00
Jonas Kvinge
789ff9df5c Add SUPublicEDKey Info.plist 2020-08-03 19:31:28 +02:00
Jonas Kvinge
a2064ed16b Always bundle libraries provided by homebrew
Fixes #343
2020-08-03 00:14:17 +02:00
Jonas Kvinge
b3d06c0868 Make macdeploy properly handle loader_path and libicudata 2020-08-02 17:02:28 +02:00
Jonas Kvinge
db1a6b3e38 Manually copy libicudata for macOS deploy
Fixes #498
2020-08-02 15:47:39 +02:00
Jonas Kvinge
8390237cc4 Fix Sparkle integration for macOS 2020-08-02 06:32:01 +02:00
Jonas Kvinge
9967eae7bb Decrease album cover score if artist doesn't match and cover isn't requested using album title 2020-08-02 04:34:15 +02:00
Jonas Kvinge
b3be7d1c6f Add CI for Qt 6 2020-08-02 04:19:39 +02:00
Jonas Kvinge
33ccb5dbb2 Remove duplicate check for X11 2020-08-02 04:18:40 +02:00
Strawbs Bot
5b90c0d695 Update translations 2020-08-02 01:07:53 +02:00
Jonas Kvinge
472a660239 Update README.md 2020-08-01 23:46:59 +02:00
Jonas Kvinge
ab67536d9a Prevent compilation and live albums from being picked before studio albums for album cover searches based on artist + song title 2020-08-01 23:17:35 +02:00
Jonas Kvinge
ee85fb3aec Use QString() on non-translated text in collection filter widget 2020-08-01 22:50:02 +02:00
Jonas Kvinge
214b6f4358 Use correct qt sparkle include for Qt 6 2020-08-01 03:41:48 +02:00
Jonas Kvinge
af0d092054 Use sparkle to check for updates on macOS and Windows 2020-08-01 03:37:16 +02:00
Jonas Kvinge
b07903c3e9 Register QVector<int> 2020-08-01 03:32:25 +02:00
Jonas Kvinge
ffa4c6bf09 Add SUFeedURL to Info.plist 2020-08-01 03:31:29 +02:00
Jonas Kvinge
b4125fa56c Remove create-dmg.sh script and use create-dmg directly from CMake 2020-08-01 03:31:01 +02:00
Jonas Kvinge
2c72302087 Install sparkle for macOS builds 2020-08-01 03:28:43 +02:00
Jonas Kvinge
0fa52bc64f Fix macdeploy script 2020-08-01 03:28:01 +02:00
Jonas Kvinge
f55a80b15a Use Q_UNUSED 2020-08-01 03:23:50 +02:00
Strawbs Bot
0735483321 Update translations 2020-08-01 01:01:36 +02:00
Jonas Kvinge
53fc2c7c21 Add extra safety for overwriting files for filesystem storage 2020-07-31 21:45:01 +02:00
Jonas Kvinge
f22133c3c5 Fix translations for Qt 6 2020-07-30 20:49:24 +02:00
Jonas Kvinge
2d5a6d6583 Use album artist for album repeat mode 2020-07-30 20:46:30 +02:00
Strawbs Bot
dc4adf2836 Update translations 2020-07-30 01:01:45 +02:00
Jonas Kvinge
dd6e254e4f Use quotes in collection query to allow special characters
Fixes #492
2020-07-29 21:41:35 +02:00
Jonas Kvinge
4c028c1659 Use position().toPoint() with Qt 6 2020-07-29 21:40:03 +02:00
Jonas Kvinge
d332a6777a Use QSortFilterProxyModel::filterRegularExpression only with Qt 6 2020-07-29 21:39:02 +02:00
Strawbs Bot
378251f229 Update translations 2020-07-29 01:01:35 +02:00
Strawbs Bot
b6b9b903ed Update translations 2020-07-28 01:01:37 +02:00
Strawbs Bot
143d68cfd5 Update translations 2020-07-27 01:01:53 +02:00
Jonas Kvinge
a31eac1426 Base warning for show in file browser on unique directories
Fixes #484
2020-07-26 15:10:00 +02:00
Jonas Kvinge
797196f7fc Register column aligment as int too 2020-07-26 15:05:00 +02:00
Chongo Bong
c9d0bc81dd simple addition of playlist actions to icon (#490)
very simple addition of playlist/playback actions to strawberry's icon. this re-introduces some nice little polish that was present in clementine's .desktop file.
2020-07-24 20:38:17 +02:00
Strawbs Bot
b5448ff607 Update translations 2020-07-22 01:01:41 +02:00
Jonas Kvinge
5ebd363d5d Fixes to last.fm scrobbling
- Start array notation for parameters at 0
- Correctly send trackNumber
2020-07-21 03:14:02 +02:00
Strawbs Bot
1d439e673e Update translations 2020-07-20 01:05:14 +02:00
Jonas Kvinge
0b7b7656b2 Replace use of QRegExp 2020-07-20 00:57:42 +02:00
Jonas Kvinge
eb270df835 Use std::bind in QtConcurrent::run() to fix compile with Qt 6 2020-07-19 22:43:58 +02:00
Jonas Kvinge
ff73dd2183 Partial revert commit af67de8 2020-07-19 19:07:12 +02:00
Strawbs Bot
e043a03eb6 Update translations 2020-07-19 15:23:03 +02:00
Jonas Kvinge
3cb4e8e373 Fix OSD Pretty margin 2020-07-19 04:09:34 +02:00
Jonas Kvinge
7e6de528b4 Update Last.fm error codes 2020-07-19 03:47:21 +02:00
Jonas Kvinge
df901c30ef Use QString() for html codes in about dialog 2020-07-19 03:46:41 +02:00
Jonas Kvinge
13856b33ec Fix playlist filter with Qt 5 2020-07-18 22:37:49 +02:00
Jonas Kvinge
a3a1c6f4c8 Fix saving playlist column alignment 2020-07-18 18:18:34 +02:00
Jonas Kvinge
638998a861 Replace QTimeLine::CurveShape with QEasingCurve 2020-07-18 17:53:14 +02:00
Jonas Kvinge
6e2ec89a05 Use QMouseEvent::pos() 2020-07-18 17:35:03 +02:00
Jonas Kvinge
af67de8aa6 Use lambdas for QtConcurrent::run instead of NewClosure 2020-07-18 16:28:39 +02:00
Jonas Kvinge
425dac478e Replace QProcess::error() with QProcess::errorOccurred() 2020-07-18 16:23:39 +02:00
Jonas Kvinge
b15c4ecd10 Fix check for context tab in TabSwitched
Broken with Qt 6
2020-07-18 15:52:36 +02:00
Jonas Kvinge
d7f88cf3a4 Register QItemSelection 2020-07-18 06:02:54 +02:00
Jonas Kvinge
5b7fbcd9b8 Remove debian stretch 2020-07-18 05:28:26 +02:00
Jonas Kvinge
7af64b0782 Fix QRegularExpressionMatch in FMPSParser 2020-07-18 05:02:34 +02:00
Jonas Kvinge
b84c70e811 Link Qt6::Core5Compat in libstrawberry-tagreader 2020-07-18 05:01:54 +02:00
Jonas Kvinge
f5b245c72d Add option to compile with Qt 6 2020-07-18 04:47:54 +02:00
Jonas Kvinge
4307183817 Fix utilities test 2020-07-18 04:32:37 +02:00
Jonas Kvinge
aeb32783d6 Only use QTextStream::setCodec() with Qt < 6 2020-07-18 04:27:21 +02:00
Jonas Kvinge
3927b3bf27 Remove QPainter::HighQualityAntialiasing 2020-07-18 04:26:19 +02:00
Jonas Kvinge
da9d2f9417 Replace QPalette::Background with QPalette::Window 2020-07-18 04:25:29 +02:00
Jonas Kvinge
1cec48e8f8 Use static_cast 2020-07-18 04:25:15 +02:00
Jonas Kvinge
f1105393da Replace QDateTime::toTime_t() with QDateTime::toSecsSinceEpoch() 2020-07-18 04:24:16 +02:00
Jonas Kvinge
dc7047e3c2 Use QLocale::LongFormat 2020-07-18 04:22:59 +02:00
Jonas Kvinge
08b2945623 Remove Qt 5.6 backward compatibility 2020-07-18 04:21:45 +02:00
Jonas Kvinge
cbcc223150 Replace QRegExp with QRegularExpression 2020-07-18 04:21:19 +02:00
Jonas Kvinge
9830f21e4a Use setContentsMargins() on layout 2020-07-18 04:20:20 +02:00
Jonas Kvinge
5f49567bf7 Make GlobalShortcut::nativeEventFilter compatible with Qt 6 2020-07-18 04:18:46 +02:00
Jonas Kvinge
6154ae7342 Move QDialogButtonBox signal/slot connect from UI file to class 2020-07-18 04:17:27 +02:00
Jonas Kvinge
978f3a3682 Add QSslError include 2020-07-18 04:16:31 +02:00
Jonas Kvinge
1f0961d574 Make MainWindow::nativeEvent compatible with Qt 6 2020-07-18 04:15:42 +02:00
Jonas Kvinge
a101252701 Make OSDPretty compatible with Qt 6 2020-07-18 04:15:19 +02:00
Jonas Kvinge
c96c29b1e3 Replace QRegExp with QRegularExpression 2020-07-18 04:14:51 +02:00
Jonas Kvinge
3b0fc180ff Make QListWidget::mimeData compatible with Qt 6 2020-07-18 04:13:53 +02:00
Jonas Kvinge
9b8bfdf33c Replace QPalette::Background with QPalette::Window 2020-07-18 04:12:50 +02:00
Jonas Kvinge
4328831fcd Use globalPosition() 2020-07-18 04:09:36 +02:00
Jonas Kvinge
e5b3df41e9 Replace QRegExp with QRegularExpression 2020-07-18 04:05:07 +02:00
Jonas Kvinge
cf5259e218 Add QActionGroup include 2020-07-18 03:54:52 +02:00
Jonas Kvinge
f24b6a520c Replace QDateTime::toTime_t() with QDateTime::toSecsSinceEpoch() 2020-07-18 03:53:30 +02:00
Jonas Kvinge
4140163ab2 Mark unused parameters 2020-07-17 16:36:24 +02:00
Jonas Kvinge
7afde0e93f Fix compile warning in qsearchfield_mac.mm 2020-07-17 16:35:57 +02:00
Jonas Kvinge
d27a571882 Ignore compile warning in SBSystemPreferences.h 2020-07-17 16:35:11 +02:00
Jonas Kvinge
1c70e3be25 Ignore format nonliteral in tutils.h 2020-07-17 16:34:37 +02:00
Jonas Kvinge
1819f64467 Disable deprecation warning for QMacCocoaViewContainer 2020-07-17 16:33:10 +02:00
Jonas Kvinge
9852e588c1 Change compile options 2020-07-17 16:32:37 +02:00
Jonas Kvinge
71a1ea481b Replace some uses of static_cast with qobject_cast 2020-07-17 01:32:07 +02:00
Jonas Kvinge
9e32f0d778 Silence some compile warnings with reinterpret cast 2020-07-16 22:46:31 +02:00
Jonas Kvinge
4478174dc2 Fix incorrectly mapped keys
Fixes #483
2020-07-16 22:28:35 +02:00
Jonas Kvinge
07553476d4 Remove xine 2020-07-16 00:59:46 +02:00
Jonas Kvinge
1773283456 Remove xine metronom.pts_per_smpls check 2020-07-16 00:44:41 +02:00
Jonas Kvinge
221ab51d90 Simply startup behaviour 2020-07-16 00:06:51 +02:00
Jonas Kvinge
43e0dd922b Hide settings that are unavailable on macOS in the settings 2020-07-16 00:05:49 +02:00
Jonas Kvinge
b3262652c3 Turn back git revision 2020-07-14 18:40:48 +02:00
Strawbs Bot
0cfa2b8c20 Update translations 2020-07-14 01:01:50 +02:00
491 changed files with 35248 additions and 16005 deletions

View File

@@ -80,7 +80,6 @@ commands:
libpulse-devel
gstreamer-devel
gstreamer-plugins-base-devel
libxine-devel
vlc-devel
libQt5Core-devel
libQt5Gui-devel
@@ -96,9 +95,7 @@ commands:
libqt5-linguist-devel
libcdio-devel
libgpod-devel
libplist-devel
libmtp-devel
libusbmuxd-devel
libchromaprint-devel
desktop-file-utils
update-desktop-files
@@ -150,8 +147,6 @@ commands:
taglib-devel
libcdio-devel
libgpod-devel
libplist-devel
libusbmuxd-devel
libmtp-devel
libchromaprint-devel
fftw-devel
@@ -214,8 +209,6 @@ commands:
libchromaprint-devel
libcdio-devel
libgpod-devel
libplist-devel
libusbmuxd-devel
libmtp-devel
libjpeg-devel
cairo-devel
@@ -233,68 +226,6 @@ commands:
gstreamer1-plugins-base-devel
install_mageia_dependencies:
description: Install Mageia dependencies
steps:
- run:
name: Update packages
command: urpmi.update -a
- run:
name: Configure auto update
command: urpmi --auto-update
- run:
name: Install dependencies
command: >
urpmi --force
urpmi-debuginfo-install
git
tar
rpmdevtools
make
cmake
glibc
binutils
gcc-c++
man
gettext
notification-daemon
dbus-devel
libgnutls-devel
lib64boost-devel
lib64protobuf-devel
protobuf-compiler
lib64sqlite3-devel
lib64alsa2-devel
lib64pulseaudio-devel
lib64notify-devel
lib64qt5core-devel
lib64qt5gui-devel
lib64qt5widgets-devel
lib64qt5network-devel
lib64qt5concurrent-devel
lib64qt5sql-devel
lib64qt5dbus-devel
lib64qt5x11extras-devel
lib64qt5help-devel
libqt5test-devel
lib64gstreamer1.0-devel
lib64gstreamer-plugins-base1.0-devel
lib64cdio-devel
lib64gpod-devel
lib64plist-devel
lib64usbmuxd-devel
lib64mtp-devel
lib64raw1394-devel
lib64chromaprint-devel
libfftw-devel
desktop-file-utils
appstream-util
libappstream-glib8
hicolor-icon-theme
qt5ct
lib64mesaegl1
install_debian_dependencies:
description: Install Debian dependencies
steps:
@@ -303,6 +234,7 @@ commands:
command: >
apt-get update && apt-get install -y
build-essential
dh-make
ssh
git
make
@@ -336,9 +268,6 @@ commands:
libcdio-dev
libmtp-dev
libgpod-dev
libimobiledevice-dev
libplist-dev
libusbmuxd-dev
install_ubuntu_dependencies:
@@ -354,6 +283,7 @@ commands:
command: >
apt-get update && apt-get install -y
build-essential
dh-make
ssh
git
make
@@ -390,9 +320,6 @@ commands:
libcdio-dev
libmtp-dev
libgpod-dev
libimobiledevice-dev
libplist-dev
libusbmuxd-dev
jobs:
@@ -407,24 +334,6 @@ jobs:
- build_source
build_opensuse_tumbleweed:
docker:
- image: opensuse/tumbleweed
environment:
RPM_BUILD_NCPUS: "2"
steps:
- run:
name: Update packages
command: zypper --non-interactive --gpg-auto-import-keys ref
- run:
name: Upgrade packages
command: zypper --non-interactive --gpg-auto-import-keys dup
- install_opensuse_dependencies
- checkout
- cmake
- build_source
- build_rpm
build_opensuse_lp151:
docker:
- image: opensuse/leap:15.1
@@ -488,28 +397,6 @@ jobs:
- build_rpm
build_mageia_7:
docker:
- image: mageia:7
environment:
RPM_BUILD_NCPUS: "2"
steps:
- install_mageia_dependencies
- checkout
- cmake
- build_source
- build_rpm
build_debian_stretch:
docker:
- image: debian:stretch
steps:
- install_debian_dependencies
- checkout
- cmake
- build_deb
build_debian_buster:
docker:
- image: debian:buster
@@ -567,10 +454,6 @@ workflows:
only: /.*/
- build_opensuse_tumbleweed:
filters:
tags:
only: /.*/
- build_opensuse_lp151:
filters:
tags:
@@ -591,22 +474,12 @@ workflows:
only: /.*/
- build_mageia_7:
filters:
tags:
only: /.*/
- build_centos_8:
filters:
tags:
only: /.*/
- build_debian_stretch:
filters:
tags:
only: /.*/
- build_debian_buster:
filters:
tags:

105
.clang-format Normal file
View File

@@ -0,0 +1,105 @@
BasedOnStyle: Chromium
Language: Cpp
Standard: Cpp11
AccessModifierOffset: -1
AlignAfterOpenBracket: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignConsecutiveMacros: true
AlignEscapedNewlines: true
AlignOperands: false
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
AllowShortLambdasOnASingleLine: true
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: No
BinPackArguments: true
BinPackParameters: true
BreakBeforeBraces: false
BreakBeforeBinaryOperators: false
BreakBeforeBraces: Stroustrup
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: false
ColumnLimit: 0
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 2
Cpp11BracedListStyle: false
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: true
FixNamespaceComments: true
IncludeBlocks: Preserve
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: AfterHash
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 100
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakAssignment: 0
PenaltyBreakBeforeFirstCallParameter: 0
PenaltyBreakComment: 0
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 0
PenaltyBreakTemplateDeclaration: 0
PenaltyExcessCharacter: 0
PenaltyReturnTypeOnItsOwnLine: 0
PointerAlignment: Right
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInParentheses: false
SpacesInSquareBrackets: false
UseCRLF: false
UseTab: Never
BreakBeforeBraces: Custom
BraceWrapping:
AfterFunction: false
AfterCaseLabel: false
AfterStruct: false
AfterClass: false
AfterEnum: false
AfterUnion: false
AfterControlStatement: Never
AfterNamespace: false
AfterObjCDeclaration: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: true
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false

View File

@@ -7,7 +7,12 @@ assignees: ''
---
For general technical questions and help with technical issues please use the forum on https://forum.strawberrymusicplayer.org/
For technical issues, questions and feature suggestions/requests please use the forum on https://forum.strawberrymusicplayer.org/
Check the Changelog to see if the issue is already fixed:
https://github.com/strawberrymusicplayer/strawberry/blob/master/Changelog
If it's fixed, try the latest development build from: https://builds.strawberrymusicplayer.org/
**Describe the bug**
A clear and concise description of what the bug is.

View File

@@ -38,7 +38,6 @@ jobs:
libpulse-devel
gstreamer-devel
gstreamer-plugins-base-devel
libxine-devel
vlc-devel
libQt5Core-devel
libQt5Gui-devel
@@ -54,9 +53,7 @@ jobs:
libqt5-linguist-devel
libcdio-devel
libgpod-devel
libplist-devel
libmtp-devel
libusbmuxd-devel
libchromaprint-devel
desktop-file-utils
update-desktop-files
@@ -109,7 +106,6 @@ jobs:
libpulse-devel
gstreamer-devel
gstreamer-plugins-base-devel
libxine-devel
vlc-devel
libQt5Core-devel
libQt5Gui-devel
@@ -125,9 +121,7 @@ jobs:
libqt5-linguist-devel
libcdio-devel
libgpod-devel
libplist-devel
libmtp-devel
libusbmuxd-devel
libchromaprint-devel
desktop-file-utils
update-desktop-files
@@ -188,7 +182,6 @@ jobs:
libpulse-devel
gstreamer-devel
gstreamer-plugins-base-devel
libxine-devel
vlc-devel
libQt5Core-devel
libQt5Gui-devel
@@ -204,9 +197,7 @@ jobs:
libqt5-linguist-devel
libcdio-devel
libgpod-devel
libplist-devel
libmtp-devel
libusbmuxd-devel
libchromaprint-devel
desktop-file-utils
update-desktop-files
@@ -269,7 +260,6 @@ jobs:
libpulse-devel
gstreamer-devel
gstreamer-plugins-base-devel
libxine-devel
vlc-devel
libQt5Core-devel
libQt5Gui-devel
@@ -285,9 +275,7 @@ jobs:
libqt5-linguist-devel
libcdio-devel
libgpod-devel
libplist-devel
libmtp-devel
libusbmuxd-devel
libchromaprint-devel
desktop-file-utils
update-desktop-files
@@ -313,6 +301,78 @@ jobs:
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_opensuse_qt6:
name: Build openSUSE Qt 6
runs-on: ubuntu-latest
container:
image: opensuse/tumbleweed
steps:
- uses: actions/checkout@v1.2.0
- name: Add Qt 6 repository
run: zypper -n ar -c -f -n 'repo-qt6' https://download.opensuse.org/repositories/home:/jonaski:/qt6/openSUSE_Tumbleweed/ repo-qt6
- name: Update packages
run: zypper --non-interactive --gpg-auto-import-keys ref
- name: Upgrade packages
run: zypper --non-interactive --gpg-auto-import-keys dup
- name: Install openSUSE dependencies
run: >
zypper --non-interactive --gpg-auto-import-keys install
lsb-release
rpm-build
git
tar
make
cmake
gcc
gcc-c++
gettext-tools
glibc-devel
libboost_headers-devel
boost-devel
glib2-devel
glib2-tools
dbus-1-devel
alsa-devel
libnotify-devel
libgnutls-devel
protobuf-devel
sqlite3-devel
libpulse-devel
gstreamer-devel
gstreamer-plugins-base-devel
vlc-devel
qt6-core-devel
qt6-gui-devel
qt6-widgets-devel
qt6-concurrent-devel
qt6-network-devel
qt6-sql-devel
qt6-dbus-devel
qt6-test-devel
qt6-x11extras-devel
qt6-base-common-devel
qt6-sql-sqlite
qt6-qt5compat-devel
libcdio-devel
libgpod-devel
libmtp-devel
libchromaprint-devel
desktop-file-utils
update-desktop-files
appstream-glib
hicolor-icon-theme
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
- name: Configure CMake
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_WITH_QT6=ON
- name: Build
working-directory: build
run: cmake --build . --config $BUILD_TYPE
build_fedora_32:
name: Build Fedora 32
runs-on: ubuntu-latest
@@ -364,8 +424,160 @@ jobs:
taglib-devel
libcdio-devel
libgpod-devel
libplist-devel
libusbmuxd-devel
libmtp-devel
libchromaprint-devel
fftw-devel
desktop-file-utils
libappstream-glib
hicolor-icon-theme
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
- name: Configure CMake
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
- name: Create source tarball
working-directory: build
run: ../dist/scripts/maketarball.sh
- name: Create RPM build sources directories
working-directory: build
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
- name: Copy source tarball
working-directory: build
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
- name: Build RPM
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_fedora_33:
name: Build Fedora 33
runs-on: ubuntu-latest
container:
image: fedora:33
env:
RPM_BUILD_NCPUS: "2"
steps:
- uses: actions/checkout@v1.2.0
- name: Update packages
run: yum update --assumeyes
- name: Upgrade packages
run: yum upgrade --assumeyes
- name: Install Fedora dependencies
run: >
dnf install --assumeyes
@development-tools
redhat-lsb-core
git
glibc
gcc-c++
rpmdevtools
make
cmake
pkgconfig
glib
man
tar
gettext
openssh
boost-devel
dbus-devel
protobuf-devel
protobuf-compiler
sqlite-devel
alsa-lib-devel
pulseaudio-libs-devel
libnotify-devel
gnutls-devel
qt5-qtbase-devel
qt5-qtx11extras-devel
qt5-qttools-devel
gstreamer1-devel
gstreamer1-plugins-base-devel
taglib-devel
libcdio-devel
libgpod-devel
libmtp-devel
libchromaprint-devel
fftw-devel
desktop-file-utils
libappstream-glib
hicolor-icon-theme
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
- name: Configure CMake
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
- name: Create source tarball
working-directory: build
run: ../dist/scripts/maketarball.sh
- name: Create RPM build sources directories
working-directory: build
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
- name: Copy source tarball
working-directory: build
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
- name: Build RPM
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_fedora_34:
name: Build Fedora 34
runs-on: ubuntu-latest
container:
image: fedora:34
env:
RPM_BUILD_NCPUS: "2"
steps:
- uses: actions/checkout@v1.2.0
- name: Update packages
run: yum update --assumeyes
- name: Upgrade packages
run: yum upgrade --assumeyes
- name: Install Fedora dependencies
run: >
dnf install --assumeyes
@development-tools
redhat-lsb-core
git
glibc
gcc-c++
rpmdevtools
make
cmake
pkgconfig
glib
man
tar
gettext
openssh
boost-devel
dbus-devel
protobuf-devel
protobuf-compiler
sqlite-devel
alsa-lib-devel
pulseaudio-libs-devel
libnotify-devel
gnutls-devel
qt5-qtbase-devel
qt5-qtx11extras-devel
qt5-qttools-devel
gstreamer1-devel
gstreamer1-plugins-base-devel
taglib-devel
libcdio-devel
libgpod-devel
libmtp-devel
libchromaprint-devel
fftw-devel
@@ -448,8 +660,6 @@ jobs:
libchromaprint-devel
libcdio-devel
libgpod-devel
libplist-devel
libusbmuxd-devel
libmtp-devel
libjpeg-devel
cairo-devel
@@ -485,150 +695,6 @@ jobs:
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_mageia_7:
name: Build Mageia 7
runs-on: ubuntu-latest
container:
image: mageia:7
steps:
- uses: actions/checkout@v1.2.0
- name: Update packages
run: urpmi.update -a
- name: Configure auto update
run: urpmi --auto-update
- name: Install Mageia dependencies
run: >
urpmi --force
urpmi-debuginfo-install
git
tar
rpmdevtools
make
cmake
glibc
binutils
gcc-c++
man
gettext
notification-daemon
dbus-devel
libgnutls-devel
lib64boost-devel
lib64protobuf-devel
protobuf-compiler
lib64sqlite3-devel
lib64alsa2-devel
lib64pulseaudio-devel
lib64notify-devel
lib64qt5core-devel
lib64qt5gui-devel
lib64qt5widgets-devel
lib64qt5network-devel
lib64qt5concurrent-devel
lib64qt5sql-devel
lib64qt5dbus-devel
lib64qt5x11extras-devel
lib64qt5help-devel
libqt5test-devel
lib64gstreamer1.0-devel
lib64gstreamer-plugins-base1.0-devel
lib64cdio-devel
lib64gpod-devel
lib64plist-devel
lib64usbmuxd-devel
lib64mtp-devel
lib64raw1394-devel
lib64chromaprint-devel
libfftw-devel
desktop-file-utils
appstream-util
libappstream-glib8
hicolor-icon-theme
qt5ct
lib64mesaegl1
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
- name: Configure CMake
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
- name: Create source tarball
working-directory: build
run: ../dist/scripts/maketarball.sh
- name: Create RPM build sources directories
working-directory: build
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
- name: Copy source tarball
working-directory: build
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
- name: Build RPM
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_debian_stretch:
name: Build Debian Stretch
runs-on: ubuntu-latest
container:
image: debian:stretch
steps:
- uses: actions/checkout@v1.2.0
- name: Install Debian dependencies
run: >
apt-get update && apt-get install -y
build-essential
ssh
git
make
cmake
gcc
pkg-config
fakeroot
gettext
lsb-release
libglib2.0-dev
dpkg-dev
libdbus-1-dev
libboost-dev
libprotobuf-dev
protobuf-compiler
libsqlite3-dev
libgnutls28-dev
libasound2-dev
libpulse-dev
qtbase5-dev
qtbase5-dev-tools
qtbase5-private-dev
libqt5x11extras5-dev
qttools5-dev
libgstreamer1.0-dev
libgstreamer-plugins-base1.0-dev
gstreamer1.0-alsa
gstreamer1.0-pulseaudio
libchromaprint-dev
libfftw3-dev
libcdio-dev
libmtp-dev
libgpod-dev
libimobiledevice-dev
libplist-dev
libusbmuxd-dev
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
- name: Configure CMake
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
- name: make deb
shell: bash
run: dpkg-buildpackage -b -d -uc -us -nc -j2
build_debian_buster:
name: Build Debian Buster
runs-on: ubuntu-latest
@@ -640,6 +706,7 @@ jobs:
run: >
apt-get update && apt-get install -y
build-essential
dh-make
ssh
git
make
@@ -673,9 +740,6 @@ jobs:
libcdio-dev
libmtp-dev
libgpod-dev
libimobiledevice-dev
libplist-dev
libusbmuxd-dev
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
@@ -699,6 +763,7 @@ jobs:
run: >
apt-get update && apt-get install -y
build-essential
dh-make
ssh
git
make
@@ -732,9 +797,6 @@ jobs:
libcdio-dev
libmtp-dev
libgpod-dev
libimobiledevice-dev
libplist-dev
libusbmuxd-dev
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
@@ -760,6 +822,7 @@ jobs:
run: >
apt-get update && apt-get install -y
build-essential
dh-make
ssh
git
make
@@ -796,9 +859,6 @@ jobs:
libcdio-dev
libmtp-dev
libgpod-dev
libimobiledevice-dev
libplist-dev
libusbmuxd-dev
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
@@ -824,6 +884,7 @@ jobs:
run: >
apt-get update && apt-get install -y
build-essential
dh-make
ssh
git
make
@@ -860,9 +921,6 @@ jobs:
libcdio-dev
libmtp-dev
libgpod-dev
libimobiledevice-dev
libplist-dev
libusbmuxd-dev
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
@@ -888,6 +946,7 @@ jobs:
run: >
apt-get update && apt-get install -y
build-essential
dh-make
ssh
git
make
@@ -924,9 +983,6 @@ jobs:
libcdio-dev
libmtp-dev
libgpod-dev
libimobiledevice-dev
libplist-dev
libusbmuxd-dev
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
@@ -944,8 +1000,10 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v1.2.0
- name: Unlink python
run: brew unlink python@2
#- name: Update
# run: brew update
#- name: Upgrade
# run: brew upgrade
- name: Install packages
run: >
brew install
@@ -969,8 +1027,6 @@ jobs:
gst-libav
libcdio
libmtp
libimobiledevice
libplist
create-dmg
taglib
@@ -999,10 +1055,14 @@ jobs:
working-directory: build
shell: bash
run: make install
#- name: Create DMG
# working-directory: build
# shell: bash
# run: make dmg
- name: Create DMG
working-directory: build
shell: bash
run: make dmg
- uses: actions/upload-artifact@v2
with:
name: upload-macos
path: build/strawberry-*.dmg
build-windows:
@@ -1034,22 +1094,16 @@ jobs:
-DENABLE_WIN32_CONSOLE=OFF
-DENABLE_DBUS=OFF
-DENABLE_LIBGPOD=OFF
-DENABLE_IMOBILEDEVICE=OFF
-DENABLE_LIBMTP=OFF
-DENABLE_XINE=OFF
-DProtobuf_PROTOC_EXECUTABLE=/usr/src/strawberry-mxe/usr/x86_64-pc-linux-gnu/bin/protoc
- name: Run Make
working-directory: build
run: make -j2
- name: Strip executables
working-directory: build
run: /usr/src/strawberry-mxe/usr/bin/x86_64-w64-mingw32.shared-strip *.exe
- name: Create directories
working-directory: build
run: mkdir -p gio-modules platforms sqldrivers imageformats styles gstreamer-plugins xine-plugins nsisplugins
run: mkdir -p gio-modules platforms sqldrivers imageformats styles gstreamer-plugins nsisplugins
- name: Copy GIO modules
working-directory: build
@@ -1123,13 +1177,9 @@ jobs:
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstlibav.dll
${GITHUB_WORKSPACE}/build/gstreamer-plugins/
- name: Copy killproc.exe
- name: Copy extra binaries
working-directory: build
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/killproc.exe .
- name: Copy liborc-0.4-0.dll
working-directory: build
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/liborc-0.4-0.dll .
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/{sqlite3.exe,killproc.exe,liborc-0.4-0.dll} .
- name: Copy dependencies
working-directory: build
@@ -1145,6 +1195,10 @@ jobs:
-F ./gstreamer-plugins
-R /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared
- name: Strip binaries
working-directory: build
run: find . -type f \( -iname \*.dll -o -iname \*.exe \) -exec /usr/src/strawberry-mxe/usr/bin/x86_64-w64-mingw32.shared-strip {} \;
- name: Copy nsis files
working-directory: build
run: cp ${GITHUB_WORKSPACE}/dist/windows/*.nsi ${GITHUB_WORKSPACE}/dist/windows/*.nsh ${GITHUB_WORKSPACE}/dist/windows/*.ico .
@@ -1152,3 +1206,27 @@ jobs:
- name: Build Windows installer
working-directory: build
run: makensis strawberry.nsi
upload-macos:
name: Upload macOS DMG
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
needs:
- build-macos
steps:
- uses: actions/checkout@v1.2.0
- uses: actions/download-artifact@v2
with:
path: uploads
- name: Install SSH keys
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{ secrets.KNOWN_HOSTS2 }}
key: ${{ secrets.SSH_KEY }}
- name: rsync
run: |
set -x
for i in $(find uploads -type f -name '*.dmg'); do
rsync -e "ssh -p 50220 -o StrictHostKeyChecking=no" -va $i travis@echoes.jkvinge.net:/home/travis/builds/macos/catalina/
done

1
.gitignore vendored
View File

@@ -104,7 +104,6 @@ Thumbs.db
# Stuff in dist
maketarball.sh
create-dmg.sh
changelog
PKGBUILD

View File

@@ -2,8 +2,6 @@ sudo: required
language: C++
os:
- osx
services:
- docker
compiler:
- gcc
@@ -12,42 +10,36 @@ before_install:
echo $DEPLOY_KEY_ENC | base64 --decode | openssl aes-256-cbc -K $encrypted_83a41ac424a6_key -iv $encrypted_83a41ac424a6_iv -out ~/.ssh/id_rsa -d ;
chmod 600 ~/.ssh/id_rsa ;
fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
docker build -f Dockerfile -t strawberry-build . || travis_terminate 1;
docker run --name build -itd strawberry-build /bin/bash || travis_terminate 1;
docker exec build git clone https://github.com/strawberrymusicplayer/strawberry;
fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
git fetch --unshallow || travis_terminate 1;
git pull || travis_terminate 1;
brew unlink python@2 || travis_terminate 1;
brew install glib pkgconfig libffi protobuf protobuf-c qt gettext gnutls fftw sqlite chromaprint zlib taglib;
brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav;
brew install libcdio libmtp libimobiledevice libplist;
brew install create-dmg;
export Qt5_DIR=/usr/local/opt/qt5/lib/cmake;
export Qt5LinguistTools_DIR=/usr/local/opt/qt5/lib/cmake/Qt5LinguistTools;
export PATH="/usr/local/opt/gettext/bin:$PATH";
export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig/:/usr/local/opt/zlib/lib/pkgconfig:$PKG_CONFIG_PATH";
ls /usr/local/lib/gstreamer-1.0;
fi
- git fetch --unshallow
- git pull
- brew update
- travis_wait 120 brew upgrade || echo "Failed"
- travis_wait 120 brew upgrade || echo "Failed"
- brew install glib pkgconfig libffi protobuf protobuf-c qt gettext gnutls fftw sqlite chromaprint zlib taglib
- brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav
- brew install libcdio libmtp
- brew install create-dmg
- brew cask install sparkle
- sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework /Library/Frameworks/Sparkle.framework
- sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework.dSYM /Library/Frameworks/Sparkle.framework.dSYM
- export Qt5_DIR=/usr/local/opt/qt5/lib/cmake
- export Qt5LinguistTools_DIR=/usr/local/opt/qt5/lib/cmake/Qt5LinguistTools
- ls /usr/local/lib/gstreamer-1.0
before_script:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build cmake -Hstrawberry -Bbuild ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir build; cd build; cmake .. -DUSE_BUNDLE=ON ; fi
- mkdir build
- cd build
- cmake .. -DUSE_BUNDLE=ON
script:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build make -C build -j8 ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
make -j8 || travis_terminate 1;
make install || travis_terminate 1;
make dmg;
fi
- make -j8
- make install
- make dmg
after_success:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ls -lh strawberry*.dmg; fi
- ls -lh strawberry*.dmg
- if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [[ "$CC_FOR_BUILD" == "gcc" ]] && [ -f ~/.ssh/id_rsa ]; then
if [[ "$TRAVIS_BRANCH" == "master" ]]; then
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos;
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos/highsierra/;
elif [[ "$TRAVIS_BRANCH" == "macos" ]]; then
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos;
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos/highsierra/;
fi
fi

View File

@@ -10,38 +10,46 @@ endif()
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
QT5_WRAP_CPP(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
if(BUILD_WITH_QT6)
qt6_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
else()
qt5_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
endif()
add_library(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC})
target_include_directories(singleapplication SYSTEM PRIVATE
${Qt5Core_INCLUDE_DIRS}
${Qt5Widgets_INCLUDE_DIRS}
${Qt5Network_INCLUDE_DIRS}
${QtCore_INCLUDE_DIRS}
${QtWidgets_INCLUDE_DIRS}
${QtNetwork_INCLUDE_DIRS}
)
target_include_directories(singleapplication PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(singleapplication PRIVATE
${Qt5Core_LIBRARIES}
${Qt5Widgets_LIBRARIES}
${Qt5Network_LIBRARIES}
${QtCore_LIBRARIES}
${QtWidgets_LIBRARIES}
${QtNetwork_LIBRARIES}
)
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
QT5_WRAP_CPP(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
if(BUILD_WITH_QT6)
qt6_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
else()
qt5_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
endif()
add_library(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC})
target_include_directories(singlecoreapplication SYSTEM PRIVATE
${Qt5Core_INCLUDE_DIRS}
${Qt5Network_INCLUDE_DIRS}
${QtCore_INCLUDE_DIRS}
${QtNetwork_INCLUDE_DIRS}
)
target_include_directories(singlecoreapplication PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(singlecoreapplication PRIVATE
${Qt5Core_LIBRARIES}
${Qt5Network_LIBRARIES}
${QtCore_LIBRARIES}
${QtNetwork_LIBRARIES}
)
configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h")

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -35,113 +35,131 @@
#include <limits>
#include <QtGlobal>
#include <QCoreApplication>
#include <QApplication>
#include <QThread>
#include <QSharedMemory>
#include <QLocalSocket>
#include <QByteArray>
#include <QElapsedTimer>
#include <QtDebug>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#else
# include <QDateTime>
#endif
#include "singleapplication.h"
#include "singleapplication_p.h"
/**
* @brief Constructor.
* Checks and fires up LocalServer or closes the program if another instance already exists
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
* @param argc
* @param argv
* @param {bool} allowSecondaryInstances
* @param allowSecondary Whether to enable secondary instance support
* @param options Optional flags to toggle specific behaviour
* @param timeout Maximum time blocking functions are allowed during app load
*/
SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
: app_t(argc, argv), d_ptr(new SingleApplicationPrivate(this)) {
: app_t(argc, argv),
d_ptr(new SingleApplicationPrivate(this)) {
Q_D(SingleApplication);
// Store the current mode of the program
d->options = options;
d->options_ = options;
// Generating an application ID used for identifying the shared memory block and QLocalServer
d->genBlockServerName();
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
d->randomSleep();
#ifdef Q_OS_UNIX
// By explicitly attaching it and then deleting it we make sure that the
// memory is deleted even after the process has crashed on Unix.
d->memory = new QSharedMemory(d->blockServerName);
d->memory->attach();
delete d->memory;
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
d->memory_ = new QSharedMemory(d->blockServerName_);
d->memory_->attach();
delete d->memory_;
#endif
// Guarantee thread safe behaviour with a shared memory block.
d->memory = new QSharedMemory(d->blockServerName);
d->memory_ = new QSharedMemory(d->blockServerName_);
// Create a shared memory block
if (d->memory->create(sizeof(InstancesInfo))) {
if (d->memory_->create(sizeof(InstancesInfo))) {
// Initialize the shared memory block
d->memory->lock();
if (!d->memory_->lock()) {
qCritical() << "SingleApplication: Unable to lock memory block after create.";
abortSafely();
}
d->initializeMemoryBlock();
d->memory->unlock();
}
else {
// Attempt to attach to the memory segment
if (! d->memory->attach()) {
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
qCritical() << d->memory->errorString();
delete d;
::exit(EXIT_FAILURE);
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
// Attempt to attach to the memory segment
if (!d->memory_->attach()) {
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
abortSafely();
}
if (!d->memory_->lock()) {
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
abortSafely();
}
}
else {
qCritical() << "SingleApplication: Unable to create block.";
abortSafely();
}
}
InstancesInfo* inst = static_cast<InstancesInfo*>(d->memory->data());
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
QElapsedTimer time;
time.start();
// Make sure the shared memory block is initialised and in consistent state
while (true) {
d->memory->lock();
forever {
// If the shared memory block's checksum is valid continue
if (d->blockChecksum() == inst->checksum) break;
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
if (time.elapsed() > 5000) {
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
d->initializeMemoryBlock();
}
d->memory->unlock();
// Random sleep here limits the probability of a collision between two racing apps
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u));
#else
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
QThread::sleep(8 + static_cast<unsigned long>(static_cast <float>(qrand()) / RAND_MAX * 10));
#endif
// Otherwise wait for a random period and try again.
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
if (!d->memory_->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
qDebug() << d->memory_->errorString();
}
d->randomSleep();
if (!d->memory_->lock()) {
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
abortSafely();
}
}
if (inst->primary == false) {
d->startPrimary();
d->memory->unlock();
if (!d->memory_->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
qDebug() << d->memory_->errorString();
}
return;
}
// Check if another instance can be started
if (allowSecondary) {
inst->secondary += 1;
inst->checksum = d->blockChecksum();
d->instanceNumber = inst->secondary;
d->startSecondary();
if (d->options & Mode::SecondaryNotification) {
if (d->options_ & Mode::SecondaryNotification) {
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
}
d->memory->unlock();
if (!d->memory_->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
qDebug() << d->memory_->errorString();
}
return;
}
d->memory->unlock();
if (!d->memory_->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
qDebug() << d->memory_->errorString();
}
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
@@ -151,34 +169,73 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
}
/**
* @brief Destructor
*/
SingleApplication::~SingleApplication() {
Q_D(SingleApplication);
delete d;
}
/**
* Checks if the current application instance is primary.
* @return Returns true if the instance is primary, false otherwise.
*/
bool SingleApplication::isPrimary() {
Q_D(SingleApplication);
return d->server != nullptr;
return d->server_ != nullptr;
}
/**
* Checks if the current application instance is secondary.
* @return Returns true if the instance is secondary, false otherwise.
*/
bool SingleApplication::isSecondary() {
Q_D(SingleApplication);
return d->server == nullptr;
return d->server_ == nullptr;
}
/**
* Allows you to identify an instance by returning unique consecutive instance ids.
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
* @return Returns a unique instance id.
*/
quint32 SingleApplication::instanceId() {
Q_D(SingleApplication);
return d->instanceNumber;
return d->instanceNumber_;
}
/**
* Returns the OS PID (Process Identifier) of the process running the primary instance.
* Especially useful when SingleApplication is coupled with OS. specific APIs.
* @return Returns the primary instance PID.
*/
qint64 SingleApplication::primaryPid() {
Q_D(SingleApplication);
return d->primaryPid();
}
/**
* Returns the username the primary instance is running as.
* @return Returns the username the primary instance is running as.
*/
QString SingleApplication::primaryUser() {
Q_D(SingleApplication);
return d->primaryUser();
}
/**
* Returns the username the current instance is running as.
* @return Returns the username the current instance is running as.
*/
QString SingleApplication::currentUser() {
Q_D(SingleApplication);
return d->getUsername();
}
/**
* Sends message to the Primary Instance.
* @param message The message to send.
* @param timeout the maximum timeout in milliseconds for blocking functions.
* @return true if the message was sent successfuly, false otherwise.
*/
bool SingleApplication::sendMessage(QByteArray message, int timeout) {
Q_D(SingleApplication);
@@ -187,11 +244,26 @@ bool SingleApplication::sendMessage(QByteArray message, int timeout) {
if (isPrimary()) return false;
// Make sure the socket is connected
d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect);
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect))
return false;
d->socket->write(message);
bool dataWritten = d->socket->waitForBytesWritten(timeout);
d->socket->flush();
d->socket_->write(message);
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
d->socket_->flush();
return dataWritten;
}
/**
* Cleans up the shared memory block and exits with a failure.
* This function halts program execution.
*/
void SingleApplication::abortSafely() {
Q_D(SingleApplication);
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
delete d;
::exit(EXIT_FAILURE);
}

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -42,16 +42,15 @@
class SingleApplicationPrivate;
/**
* @brief The SingleApplication class handles multipe instances of the same
* Application
* @see QCoreApplication
* @brief The SingleApplication class handles multipe instances of the same Application
* @see QApplication
*/
class SingleApplication : public QApplication {
Q_OBJECT
typedef QApplication app_t;
public:
public:
/**
* @brief Mode of operation of SingleApplication.
* Whether the block should be user-wide or system-wide and whether the
@@ -63,11 +62,11 @@ public:
* @enum
*/
enum Mode {
User = 1 << 0,
System = 1 << 1,
SecondaryNotification = 1 << 2,
ExcludeAppVersion = 1 << 3,
ExcludeAppPath = 1 << 4
User = 1 << 0,
System = 1 << 1,
SecondaryNotification = 1 << 2,
ExcludeAppVersion = 1 << 3,
ExcludeAppPath = 1 << 4
};
Q_DECLARE_FLAGS(Options, Mode)
@@ -91,7 +90,7 @@ public:
* Usually 4*timeout would be the worst case (fail) scenario.
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
*/
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 );
explicit SingleApplication(int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000);
~SingleApplication() override;
/**
@@ -118,6 +117,18 @@ public:
*/
qint64 primaryPid();
/**
* @brief Returns the username of the user running the primary instance
* @returns {QString}
*/
QString primaryUser();
/**
* @brief Returns the username of the current user
* @returns {QString}
*/
QString currentUser();
/**
* @brief Sends a message to the primary instance. Returns true on success.
* @param {int} timeout - Timeout for connecting
@@ -125,18 +136,18 @@ public:
* @note sendMessage() will return false if invoked from the primary
* instance.
*/
bool sendMessage( QByteArray message, int timeout = 1000 );
bool sendMessage(QByteArray message, int timeout = 1000);
signals:
void instanceStarted();
void receivedMessage( quint32 instanceId, QByteArray message );
void receivedMessage(quint32 instanceId, QByteArray message);
private:
SingleApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleApplication)
void abortSafely();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
#endif // SINGLEAPPLICATION_H
#endif // SINGLEAPPLICATION_H

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -44,6 +44,8 @@
# include <pwd.h>
#endif
#include <QObject>
#include <QThread>
#include <QIODevice>
#include <QSharedMemory>
#include <QByteArray>
@@ -52,6 +54,12 @@
#include <QLocalServer>
#include <QLocalSocket>
#include <QDir>
#include <QElapsedTimer>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#else
# include <QDateTime>
#endif
#include "singleapplication.h"
#include "singleapplication_p.h"
@@ -61,33 +69,73 @@
# include <lmcons.h>
#endif
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *_q_ptr)
: q_ptr(_q_ptr),
memory(nullptr),
socket(nullptr),
server(nullptr),
instanceNumber(-1)
{}
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
: q_ptr(ptr),
memory_(nullptr),
socket_(nullptr),
server_(nullptr),
instanceNumber_(-1) {}
SingleApplicationPrivate::~SingleApplicationPrivate() {
if (socket != nullptr) {
socket->close();
delete socket;
if (socket_ != nullptr) {
socket_->close();
delete socket_;
socket_ = nullptr;
}
memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
if (server != nullptr) {
server->close();
delete server;
inst->primary = false;
inst->primaryPid = -1;
inst->checksum = blockChecksum();
}
memory->unlock();
if (memory_ != nullptr) {
memory_->lock();
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
if (server_ != nullptr) {
server_->close();
delete server_;
inst->primary = false;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
memory_->unlock();
delete memory;
delete memory_;
memory_ = nullptr;
}
}
QString SingleApplicationPrivate::getUsername() {
#ifdef Q_OS_UNIX
QString username;
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
struct passwd *pw = getpwuid(geteuid());
if (pw) {
username = QString::fromLocal8Bit(pw->pw_name);
}
#endif
if (username.isEmpty()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
username = qEnvironmentVariable("USER");
#else
username = QString::fromLocal8Bit(qgetenv("USER"));
#endif
}
return username;
#endif
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if (GetUserNameW(username, &usernameLength)) {
return QString::fromWCharArray(username);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
return qEnvironmentVariable("USERNAME");
#else
return QString::fromLocal8Bit(qgetenv("USERNAME"));
#endif
#endif
}
@@ -99,11 +147,11 @@ void SingleApplicationPrivate::genBlockServerName() {
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
if (!(options & SingleApplication::Mode::ExcludeAppVersion)) {
if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) {
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
}
if (! (options & SingleApplication::Mode::ExcludeAppPath)) {
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
#ifdef Q_OS_WIN
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
#else
@@ -112,42 +160,22 @@ void SingleApplicationPrivate::genBlockServerName() {
}
// User level block requires a user specific data in the hash
if (options & SingleApplication::Mode::User) {
#ifdef Q_OS_UNIX
QByteArray username;
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
struct passwd *pw = getpwuid(geteuid());
if (pw) {
username = pw->pw_name;
}
#endif
if (username.isEmpty()) username = qgetenv("USER");
appData.addData(username);
#endif
#ifdef Q_OS_WIN
wchar_t username [ UNLEN + 1 ];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if (GetUserNameW(username, &usernameLength)) {
appData.addData(QString::fromWCharArray(username).toUtf8());
}
else {
appData.addData(qgetenv("USERNAME"));
}
#endif
if (options_ & SingleApplication::Mode::User) {
appData.addData(getUsername().toUtf8());
}
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
blockServerName = appData.result().toBase64().replace("/", "_");
blockServerName_ = appData.result().toBase64().replace("/", "_");
}
void SingleApplicationPrivate::initializeMemoryBlock() {
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->primary = false;
inst->secondary = 0;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
@@ -156,133 +184,161 @@ void SingleApplicationPrivate::startPrimary() {
Q_Q(SingleApplication);
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer(blockServerName);
server = new QLocalServer();
// Restrict access to the socket according to the
// SingleApplication::Mode::User flag on User level or no restrictions
if (options & SingleApplication::Mode::User) {
server->setSocketOptions(QLocalServer::UserAccessOption);
}
else {
server->setSocketOptions(QLocalServer::WorldAccessOption);
}
server->listen(blockServerName);
QObject::connect(server, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
// Reset the number of connections
InstancesInfo* inst = static_cast <InstancesInfo*>(memory->data());
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->primary = true;
inst->primaryPid = q->applicationPid();
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
inst->checksum = blockChecksum();
instanceNumber_ = 0;
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer(blockServerName_);
server_ = new QLocalServer();
instanceNumber = 0;
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
if (options_ & SingleApplication::Mode::User) {
server_->setSocketOptions(QLocalServer::UserAccessOption);
}
else {
server_->setSocketOptions(QLocalServer::WorldAccessOption);
}
server_->listen(blockServerName_);
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
}
void SingleApplicationPrivate::startSecondary() {}
void SingleApplicationPrivate::startSecondary() {
void SingleApplicationPrivate::connectToPrimary(const int msecs, const ConnectionType connectionType) {
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->secondary += 1;
inst->checksum = blockChecksum();
instanceNumber_ = inst->secondary;
}
bool SingleApplicationPrivate::connectToPrimary(int timeout, ConnectionType connectionType) {
QElapsedTimer time;
time.start();
// Connect to the Local Server of the Primary Instance if not already connected.
if (socket == nullptr) {
socket = new QLocalSocket();
if (socket_ == nullptr) {
socket_ = new QLocalSocket();
}
// If already connected - we are done;
if (socket->state() == QLocalSocket::ConnectedState)
return;
if (socket_->state() == QLocalSocket::ConnectedState) return true;
// If not connect
if (socket->state() == QLocalSocket::UnconnectedState ||
socket->state() == QLocalSocket::ClosingState) {
socket->connectToServer(blockServerName);
}
if (socket_->state() != QLocalSocket::ConnectedState) {
// Wait for being connected
if (socket->state() == QLocalSocket::ConnectingState) {
socket->waitForConnected(msecs);
forever {
randomSleep();
if (socket_->state() != QLocalSocket::ConnectingState)
socket_->connectToServer(blockServerName_);
if (socket_->state() == QLocalSocket::ConnectingState) {
socket_->waitForConnected(timeout - time.elapsed());
}
// If connected break out of the loop
if (socket_->state() == QLocalSocket::ConnectedState) break;
// If elapsed time since start is longer than the method timeout return
if (time.elapsed() >= timeout) return false;
}
}
// Initialisation message according to the SingleApplication protocol
if (socket->state() == QLocalSocket::ConnectedState) {
// Notify the parent that a new instance had been started;
QByteArray initMsg;
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
QByteArray initMsg;
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
writeStream.setVersion(QDataStream::Qt_5_8);
writeStream.setVersion(QDataStream::Qt_5_6);
writeStream << blockServerName_.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber_;
writeStream << blockServerName.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber;
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
writeStream << checksum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
#else
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
#endif
// The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
writeStream << checksum;
headerStream.setVersion(QDataStream::Qt_5_6);
// The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
headerStream.setVersion(QDataStream::Qt_5_8);
headerStream << static_cast<quint64>(initMsg.length());
headerStream << static_cast <quint64>(initMsg.length());
socket_->write(header);
socket_->write(initMsg);
bool result = socket_->waitForBytesWritten(timeout - time.elapsed());
socket_->flush();
socket->write(header);
socket->write(initMsg);
socket->flush();
socket->waitForBytesWritten(msecs);
}
return result;
}
quint16 SingleApplicationPrivate::blockChecksum() {
return qChecksum(static_cast <const char *>(memory->data()), offsetof(InstancesInfo, checksum));
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
#else
quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
#endif
return checksum;
}
qint64 SingleApplicationPrivate::primaryPid() {
qint64 pid;
memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
pid = inst->primaryPid;
memory->unlock();
memory_->lock();
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
qint64 pid = inst->primaryPid;
memory_->unlock();
return pid;
}
QString SingleApplicationPrivate::primaryUser() {
memory_->lock();
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
QByteArray username = inst->primaryUser;
memory_->unlock();
return QString::fromUtf8(username);
}
/**
* @brief Executed when a connection has been made to the LocalServer
*/
void SingleApplicationPrivate::slotConnectionEstablished() {
QLocalSocket *nextConnSocket = server->nextPendingConnection();
connectionMap.insert(nextConnSocket, ConnectionInfo());
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
connectionMap_.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
[nextConnSocket, this]() {
auto &info = connectionMap[nextConnSocket];
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
}
);
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() {
auto &info = connectionMap_[nextConnSocket];
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
});
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
[nextConnSocket, this](){
connectionMap.remove(nextConnSocket);
nextConnSocket->deleteLater();
}
);
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() {
connectionMap_.remove(nextConnSocket);
nextConnSocket->deleteLater();
});
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
[nextConnSocket, this]() {
auto &info = connectionMap[nextConnSocket];
switch(info.stage) {
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() {
auto &info = connectionMap_[nextConnSocket];
switch (info.stage) {
case StageHeader:
readInitMessageHeader(nextConnSocket);
break;
@@ -294,15 +350,14 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
break;
default:
break;
};
}
);
};
});
}
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
if (!connectionMap.contains(sock)) {
if (!connectionMap_.contains(sock)) {
return;
}
@@ -311,13 +366,12 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
}
QDataStream headerStream(sock);
headerStream.setVersion(QDataStream::Qt_5_6);
headerStream.setVersion(QDataStream::Qt_5_8);
// Read the header to know the message length
quint64 msgLen = 0;
headerStream >> msgLen;
ConnectionInfo &info = connectionMap[sock];
ConnectionInfo &info = connectionMap_[sock];
info.stage = StageBody;
info.msgLen = msgLen;
@@ -331,11 +385,11 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
Q_Q(SingleApplication);
if (!connectionMap.contains(sock)) {
if (!connectionMap_.contains(sock)) {
return;
}
ConnectionInfo &info = connectionMap[sock];
ConnectionInfo &info = connectionMap_[sock];
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
return;
}
@@ -343,8 +397,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
// Read the message body
QByteArray msgBytes = sock->read(info.msgLen);
QDataStream readStream(msgBytes);
readStream.setVersion(QDataStream::Qt_5_6);
readStream.setVersion(QDataStream::Qt_5_8);
// server name
QByteArray latin1Name;
@@ -354,7 +407,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
ConnectionType connectionType = InvalidConnection;
quint8 connTypeVal = InvalidConnection;
readStream >> connTypeVal;
connectionType = static_cast <ConnectionType>(connTypeVal);
connectionType = static_cast<ConnectionType>(connTypeVal);
// instance id
quint32 instanceId = 0;
@@ -364,9 +417,13 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
quint16 msgChecksum = 0;
readStream >> msgChecksum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
#else
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
#endif
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName && msgChecksum == actualChecksum;
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
if (!isValid) {
sock->close();
@@ -376,7 +433,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
info.instanceId = instanceId;
info.stage = StageConnected;
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options & SingleApplication::Mode::SecondaryNotification)) {
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
Q_EMIT q->instanceStarted();
}
@@ -395,7 +452,19 @@ void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
if (closedSocket->bytesAvailable() > 0)
if (closedSocket->bytesAvailable() > 0) {
Q_EMIT slotDataAvailable(closedSocket, instanceId);
}
}
void SingleApplicationPrivate::randomSleep() {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u));
#else
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
#endif
}

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2016
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -36,6 +36,7 @@
#include <QtGlobal>
#include <QObject>
#include <QString>
#include <QMap>
#include "singleapplication.h"
@@ -48,6 +49,7 @@ struct InstancesInfo {
bool primary;
quint32 secondary;
qint64 primaryPid;
char primaryUser[128];
quint16 checksum;
};
@@ -60,6 +62,7 @@ struct ConnectionInfo {
class SingleApplicationPrivate : public QObject {
Q_OBJECT
public:
enum ConnectionType : quint8 {
InvalidConnection = 0,
@@ -74,27 +77,30 @@ class SingleApplicationPrivate : public QObject {
};
Q_DECLARE_PUBLIC(SingleApplication)
explicit SingleApplicationPrivate(SingleApplication *_q_ptr);
explicit SingleApplicationPrivate(SingleApplication *ptr);
~SingleApplicationPrivate() override;
QString getUsername();
void genBlockServerName();
void initializeMemoryBlock();
void startPrimary();
void startSecondary();
void connectToPrimary(const int msecs, const ConnectionType connectionType);
bool connectToPrimary(const int msecs, const ConnectionType connectionType);
quint16 blockChecksum();
qint64 primaryPid();
QString primaryUser();
void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket);
void randomSleep();
SingleApplication *q_ptr;
QSharedMemory *memory;
QLocalSocket *socket;
QLocalServer *server;
quint32 instanceNumber;
QString blockServerName;
SingleApplication::Options options;
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
QSharedMemory *memory_;
QLocalSocket *socket_;
QLocalServer *server_;
quint32 instanceNumber_;
QString blockServerName_;
SingleApplication::Options options_;
QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
public slots:
void slotConnectionEstablished();

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -42,106 +42,124 @@
#include <QByteArray>
#include <QElapsedTimer>
#include <QtDebug>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#else
# include <QDateTime>
#endif
#include "singlecoreapplication.h"
#include "singlecoreapplication_p.h"
/**
* @brief Constructor. Checks and fires up LocalServer or closes the program
* if another instance already exists
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
* @param argc
* @param argv
* @param {bool} allowSecondaryInstances
* @param allowSecondary Whether to enable secondary instance support
* @param options Optional flags to toggle specific behaviour
* @param timeout Maximum time blocking functions are allowed during app load
*/
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
: app_t(argc, argv), d_ptr(new SingleCoreApplicationPrivate(this)) {
: app_t(argc, argv),
d_ptr(new SingleCoreApplicationPrivate(this)) {
Q_D(SingleCoreApplication);
// Store the current mode of the program
d->options = options;
d->options_ = options;
// Generating an application ID used for identifying the shared memory block and QLocalServer
d->genBlockServerName();
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
d->randomSleep();
#ifdef Q_OS_UNIX
// By explicitly attaching it and then deleting it we make sure that the
// memory is deleted even after the process has crashed on Unix.
d->memory = new QSharedMemory(d->blockServerName);
d->memory->attach();
delete d->memory;
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
d->memory_ = new QSharedMemory(d->blockServerName_);
d->memory_->attach();
delete d->memory_;
#endif
// Guarantee thread safe behaviour with a shared memory block.
d->memory = new QSharedMemory(d->blockServerName);
d->memory_ = new QSharedMemory(d->blockServerName_);
// Create a shared memory block
if (d->memory->create(sizeof(InstancesInfo))) {
if (d->memory_->create(sizeof(InstancesInfo))) {
// Initialize the shared memory block
d->memory->lock();
if (!d->memory_->lock()) {
qCritical() << "SingleCoreApplication: Unable to lock memory block after create.";
abortSafely();
}
d->initializeMemoryBlock();
d->memory->unlock();
}
else {
// Attempt to attach to the memory segment
if (!d->memory->attach()) {
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
qCritical() << d->memory->errorString();
delete d;
::exit(EXIT_FAILURE);
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
// Attempt to attach to the memory segment
if (!d->memory_->attach()) {
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
abortSafely();
}
if (!d->memory_->lock()) {
qCritical() << "SingleCoreApplication: Unable to lock memory block after attach.";
abortSafely();
}
}
else {
qCritical() << "SingleCoreApplication: Unable to create block.";
abortSafely();
}
}
InstancesInfo* inst = static_cast<InstancesInfo*>(d->memory->data());
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
QElapsedTimer time;
time.start();
// Make sure the shared memory block is initialised and in consistent state
while (true) {
d->memory->lock();
if(d->blockChecksum() == inst->checksum) break;
forever {
// If the shared memory block's checksum is valid continue
if (d->blockChecksum() == inst->checksum) break;
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
if (time.elapsed() > 5000) {
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
d->initializeMemoryBlock();
}
d->memory->unlock();
// Random sleep here limits the probability of a collision between two racing apps
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u));
#else
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
QThread::sleep(8 + static_cast<unsigned long>(static_cast <float>(qrand()) / RAND_MAX * 10));
#endif
// Otherwise wait for a random period and try again.
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
qDebug() << d->memory_->errorString();
}
d->randomSleep();
if (!d->memory_->lock()) {
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
abortSafely();
}
}
if (inst->primary == false) {
d->startPrimary();
d->memory->unlock();
if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
qDebug() << d->memory_->errorString();
}
return;
}
// Check if another instance can be started
if (allowSecondary) {
inst->secondary += 1;
inst->checksum = d->blockChecksum();
d->instanceNumber = inst->secondary;
d->startSecondary();
if(d->options & Mode::SecondaryNotification) {
if (d->options_ & Mode::SecondaryNotification) {
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance);
}
d->memory->unlock();
if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start.";
qDebug() << d->memory_->errorString();
}
return;
}
d->memory->unlock();
if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution.";
qDebug() << d->memory_->errorString();
}
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
@@ -151,47 +169,101 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
}
/**
* @brief Destructor
*/
SingleCoreApplication::~SingleCoreApplication() {
Q_D(SingleCoreApplication);
delete d;
}
/**
* Checks if the current application instance is primary.
* @return Returns true if the instance is primary, false otherwise.
*/
bool SingleCoreApplication::isPrimary() {
Q_D(SingleCoreApplication);
return d->server != nullptr;
return d->server_ != nullptr;
}
/**
* Checks if the current application instance is secondary.
* @return Returns true if the instance is secondary, false otherwise.
*/
bool SingleCoreApplication::isSecondary() {
Q_D(SingleCoreApplication);
return d->server == nullptr;
return d->server_ == nullptr;
}
/**
* Allows you to identify an instance by returning unique consecutive instance ids.
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
* @return Returns a unique instance id.
*/
quint32 SingleCoreApplication::instanceId() {
Q_D(SingleCoreApplication);
return d->instanceNumber;
return d->instanceNumber_;
}
/**
* Returns the OS PID (Process Identifier) of the process running the primary instance.
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
* @return Returns the primary instance PID.
*/
qint64 SingleCoreApplication::primaryPid() {
Q_D(SingleCoreApplication);
return d->primaryPid();
}
/**
* Returns the username the primary instance is running as.
* @return Returns the username the primary instance is running as.
*/
QString SingleCoreApplication::primaryUser() {
Q_D(SingleCoreApplication);
return d->primaryUser();
}
/**
* Returns the username the current instance is running as.
* @return Returns the username the current instance is running as.
*/
QString SingleCoreApplication::currentUser() {
Q_D(SingleCoreApplication);
return d->getUsername();
}
/**
* Sends message to the Primary Instance.
* @param message The message to send.
* @param timeout the maximum timeout in milliseconds for blocking functions.
* @return true if the message was sent successfuly, false otherwise.
*/
bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) {
Q_D(SingleCoreApplication);
// Nobody to connect to
if(isPrimary()) return false;
if (isPrimary()) return false;
// Make sure the socket is connected
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect);
if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect))
return false;
d->socket->write(message);
bool dataWritten = d->socket->waitForBytesWritten(timeout);
d->socket->flush();
d->socket_->write(message);
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
d->socket_->flush();
return dataWritten;
}
/**
* Cleans up the shared memory block and exits with a failure.
* This function halts program execution.
*/
void SingleCoreApplication::abortSafely() {
Q_D(SingleCoreApplication);
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
delete d;
::exit(EXIT_FAILURE);
}

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -42,8 +42,7 @@
class SingleCoreApplicationPrivate;
/**
* @brief The SingleCoreApplication class handles multiple instances of the same
* Application
* @brief The SingleCoreApplication class handles multipe instances of the same Application
* @see QCoreApplication
*/
class SingleCoreApplication : public QCoreApplication {
@@ -51,7 +50,7 @@ class SingleCoreApplication : public QCoreApplication {
typedef QCoreApplication app_t;
public:
public:
/**
* @brief Mode of operation of SingleCoreApplication.
* Whether the block should be user-wide or system-wide and whether the
@@ -63,11 +62,11 @@ public:
* @enum
*/
enum Mode {
User = 1 << 0,
System = 1 << 1,
SecondaryNotification = 1 << 2,
ExcludeAppVersion = 1 << 3,
ExcludeAppPath = 1 << 4
User = 1 << 0,
System = 1 << 1,
SecondaryNotification = 1 << 2,
ExcludeAppVersion = 1 << 3,
ExcludeAppPath = 1 << 4
};
Q_DECLARE_FLAGS(Options, Mode)
@@ -89,9 +88,8 @@ public:
* operations. It does not guarantee that the SingleCoreApplication
* initialisation will be completed in given time, though is a good hint.
* Usually 4*timeout would be the worst case (fail) scenario.
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
*/
explicit SingleCoreApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 );
explicit SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
~SingleCoreApplication() override;
/**
@@ -118,6 +116,18 @@ public:
*/
qint64 primaryPid();
/**
* @brief Returns the username of the user running the primary instance
* @returns {QString}
*/
QString primaryUser();
/**
* @brief Returns the username of the current user
* @returns {QString}
*/
QString currentUser();
/**
* @brief Sends a message to the primary instance. Returns true on success.
* @param {int} timeout - Timeout for connecting
@@ -125,17 +135,18 @@ public:
* @note sendMessage() will return false if invoked from the primary
* instance.
*/
bool sendMessage( QByteArray message, int timeout = 1000 );
bool sendMessage(QByteArray message, int timeout = 1000);
signals:
void instanceStarted();
void receivedMessage( quint32 instanceId, QByteArray message );
void receivedMessage(quint32 instanceId, QByteArray message);
private:
private:
SingleCoreApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleCoreApplication)
void abortSafely();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
#endif // SINGLECOREAPPLICATION_H
#endif // SINGLECOREAPPLICATION_H

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -44,6 +44,8 @@
# include <pwd.h>
#endif
#include <QObject>
#include <QThread>
#include <QIODevice>
#include <QSharedMemory>
#include <QByteArray>
@@ -52,6 +54,12 @@
#include <QLocalServer>
#include <QLocalSocket>
#include <QDir>
#include <QElapsedTimer>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#else
# include <QDateTime>
#endif
#include "singlecoreapplication.h"
#include "singlecoreapplication_p.h"
@@ -61,33 +69,73 @@
# include <lmcons.h>
#endif
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr)
: q_ptr(_q_ptr),
memory(nullptr),
socket(nullptr),
server(nullptr),
instanceNumber(-1)
{}
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
: q_ptr(ptr),
memory_(nullptr),
socket_(nullptr),
server_(nullptr),
instanceNumber_(-1) {}
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
if (socket != nullptr) {
socket->close();
delete socket;
if (socket_ != nullptr) {
socket_->close();
delete socket_;
socket_ = nullptr;
}
memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
if (server != nullptr) {
server->close();
delete server;
inst->primary = false;
inst->primaryPid = -1;
inst->checksum = blockChecksum();
}
memory->unlock();
if (memory_ != nullptr) {
memory_->lock();
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
if (server_ != nullptr) {
server_->close();
delete server_;
inst->primary = false;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
memory_->unlock();
delete memory;
delete memory_;
memory_ = nullptr;
}
}
QString SingleCoreApplicationPrivate::getUsername() {
#ifdef Q_OS_UNIX
QString username;
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
struct passwd *pw = getpwuid(geteuid());
if (pw) {
username = QString::fromLocal8Bit(pw->pw_name);
}
#endif
if (username.isEmpty()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
username = qEnvironmentVariable("USER");
#else
username = QString::fromLocal8Bit(qgetenv("USER"));
#endif
}
return username;
#endif
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if (GetUserNameW(username, &usernameLength)) {
return QString::fromWCharArray(username);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
return qEnvironmentVariable("USERNAME");
#else
return QString::fromLocal8Bit(qgetenv("USERNAME"));
#endif
#endif
}
@@ -99,11 +147,11 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
if (!(options & SingleCoreApplication::Mode::ExcludeAppVersion)) {
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppVersion)) {
appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8());
}
if (!(options & SingleCoreApplication::Mode::ExcludeAppPath)) {
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
#ifdef Q_OS_WIN
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
#else
@@ -112,42 +160,22 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
}
// User level block requires a user specific data in the hash
if (options & SingleCoreApplication::Mode::User) {
#ifdef Q_OS_UNIX
QByteArray username;
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
struct passwd *pw = getpwuid(geteuid());
if (pw) {
username = pw->pw_name;
}
#endif
if (username.isEmpty()) username = qgetenv("USER");
appData.addData(username);
#endif
#ifdef Q_OS_WIN
wchar_t username [ UNLEN + 1 ];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if (GetUserNameW(username, &usernameLength)) {
appData.addData(QString::fromWCharArray(username).toUtf8());
}
else {
appData.addData(qgetenv("USERNAME"));
}
#endif
if (options_ & SingleCoreApplication::Mode::User) {
appData.addData(getUsername().toUtf8());
}
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
blockServerName = appData.result().toBase64().replace("/", "_");
blockServerName_ = appData.result().toBase64().replace("/", "_");
}
void SingleCoreApplicationPrivate::initializeMemoryBlock() {
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->primary = false;
inst->secondary = 0;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
@@ -156,133 +184,161 @@ void SingleCoreApplicationPrivate::startPrimary() {
Q_Q(SingleCoreApplication);
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer(blockServerName);
server = new QLocalServer();
// Restrict access to the socket according to the
// SingleCoreApplication::Mode::User flag on User level or no restrictions
if (options & SingleCoreApplication::Mode::User) {
server->setSocketOptions(QLocalServer::UserAccessOption);
}
else {
server->setSocketOptions(QLocalServer::WorldAccessOption);
}
server->listen(blockServerName);
QObject::connect(server, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished);
// Reset the number of connections
InstancesInfo* inst = static_cast <InstancesInfo*>(memory->data());
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->primary = true;
inst->primaryPid = q->applicationPid();
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
inst->checksum = blockChecksum();
instanceNumber_ = 0;
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer(blockServerName_);
server_ = new QLocalServer();
instanceNumber = 0;
// Restrict access to the socket according to the SingleCoreApplication::Mode::User flag on User level or no restrictions
if (options_ & SingleCoreApplication::Mode::User) {
server_->setSocketOptions(QLocalServer::UserAccessOption);
}
else {
server_->setSocketOptions(QLocalServer::WorldAccessOption);
}
server_->listen(blockServerName_);
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished);
}
void SingleCoreApplicationPrivate::startSecondary() {}
void SingleCoreApplicationPrivate::startSecondary() {
void SingleCoreApplicationPrivate::connectToPrimary(const int msecs, const ConnectionType connectionType) {
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->secondary += 1;
inst->checksum = blockChecksum();
instanceNumber_ = inst->secondary;
}
bool SingleCoreApplicationPrivate::connectToPrimary(int timeout, ConnectionType connectionType) {
QElapsedTimer time;
time.start();
// Connect to the Local Server of the Primary Instance if not already connected.
if (socket == nullptr) {
socket = new QLocalSocket();
if (socket_ == nullptr) {
socket_ = new QLocalSocket();
}
// If already connected - we are done;
if (socket->state() == QLocalSocket::ConnectedState)
return;
if (socket_->state() == QLocalSocket::ConnectedState) return true;
// If not connect
if (socket->state() == QLocalSocket::UnconnectedState ||
socket->state() == QLocalSocket::ClosingState) {
socket->connectToServer(blockServerName);
}
if (socket_->state() != QLocalSocket::ConnectedState) {
// Wait for being connected
if (socket->state() == QLocalSocket::ConnectingState) {
socket->waitForConnected(msecs);
forever {
randomSleep();
if (socket_->state() != QLocalSocket::ConnectingState)
socket_->connectToServer(blockServerName_);
if (socket_->state() == QLocalSocket::ConnectingState) {
socket_->waitForConnected(timeout - time.elapsed());
}
// If connected break out of the loop
if (socket_->state() == QLocalSocket::ConnectedState) break;
// If elapsed time since start is longer than the method timeout return
if (time.elapsed() >= timeout) return false;
}
}
// Initialisation message according to the SingleCoreApplication protocol
if (socket->state() == QLocalSocket::ConnectedState) {
// Notify the parent that a new instance had been started;
QByteArray initMsg;
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
QByteArray initMsg;
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
writeStream.setVersion(QDataStream::Qt_5_8);
writeStream.setVersion(QDataStream::Qt_5_6);
writeStream << blockServerName_.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber_;
writeStream << blockServerName.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber;
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
writeStream << checksum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
#else
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
#endif
// The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
writeStream << checksum;
headerStream.setVersion(QDataStream::Qt_5_6);
// The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
headerStream.setVersion(QDataStream::Qt_5_8);
headerStream << static_cast<quint64>(initMsg.length());
headerStream << static_cast <quint64>(initMsg.length());
socket_->write(header);
socket_->write(initMsg);
bool result = socket_->waitForBytesWritten(timeout - time.elapsed());
socket_->flush();
socket->write(header);
socket->write(initMsg);
socket->flush();
socket->waitForBytesWritten(msecs);
}
return result;
}
quint16 SingleCoreApplicationPrivate::blockChecksum() {
return qChecksum(static_cast <const char*> (memory->data()), offsetof(InstancesInfo, checksum));
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
#else
quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
#endif
return checksum;
}
qint64 SingleCoreApplicationPrivate::primaryPid() {
qint64 pid;
memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
pid = inst->primaryPid;
memory->unlock();
memory_->lock();
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
qint64 pid = inst->primaryPid;
memory_->unlock();
return pid;
}
QString SingleCoreApplicationPrivate::primaryUser() {
memory_->lock();
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
QByteArray username = inst->primaryUser;
memory_->unlock();
return QString::fromUtf8(username);
}
/**
* @brief Executed when a connection has been made to the LocalServer
*/
void SingleCoreApplicationPrivate::slotConnectionEstablished() {
QLocalSocket *nextConnSocket = server->nextPendingConnection();
connectionMap.insert(nextConnSocket, ConnectionInfo());
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
connectionMap_.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
[nextConnSocket, this]() {
auto &info = connectionMap[nextConnSocket];
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
}
);
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() {
auto &info = connectionMap_[nextConnSocket];
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
});
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
[nextConnSocket, this](){
connectionMap.remove(nextConnSocket);
nextConnSocket->deleteLater();
}
);
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() {
connectionMap_.remove(nextConnSocket);
nextConnSocket->deleteLater();
});
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
[nextConnSocket, this]() {
auto &info = connectionMap[nextConnSocket];
switch(info.stage) {
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() {
auto &info = connectionMap_[nextConnSocket];
switch (info.stage) {
case StageHeader:
readInitMessageHeader(nextConnSocket);
break;
@@ -294,15 +350,14 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() {
break;
default:
break;
};
}
);
};
});
}
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
if (!connectionMap.contains(sock)) {
if (!connectionMap_.contains(sock)) {
return;
}
@@ -311,13 +366,12 @@ void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
}
QDataStream headerStream(sock);
headerStream.setVersion(QDataStream::Qt_5_6);
headerStream.setVersion(QDataStream::Qt_5_8);
// Read the header to know the message length
quint64 msgLen = 0;
headerStream >> msgLen;
ConnectionInfo &info = connectionMap[sock];
ConnectionInfo &info = connectionMap_[sock];
info.stage = StageBody;
info.msgLen = msgLen;
@@ -331,11 +385,11 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
Q_Q(SingleCoreApplication);
if (!connectionMap.contains(sock)) {
if (!connectionMap_.contains(sock)) {
return;
}
ConnectionInfo &info = connectionMap[sock];
ConnectionInfo &info = connectionMap_[sock];
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
return;
}
@@ -343,8 +397,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
// Read the message body
QByteArray msgBytes = sock->read(info.msgLen);
QDataStream readStream(msgBytes);
readStream.setVersion(QDataStream::Qt_5_6);
readStream.setVersion(QDataStream::Qt_5_8);
// server name
QByteArray latin1Name;
@@ -354,7 +407,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
ConnectionType connectionType = InvalidConnection;
quint8 connTypeVal = InvalidConnection;
readStream >> connTypeVal;
connectionType = static_cast <ConnectionType>(connTypeVal);
connectionType = static_cast<ConnectionType>(connTypeVal);
// instance id
quint32 instanceId = 0;
@@ -364,9 +417,13 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
quint16 msgChecksum = 0;
readStream >> msgChecksum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
#else
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
#endif
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName && msgChecksum == actualChecksum;
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
if (!isValid) {
sock->close();
@@ -376,7 +433,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
info.instanceId = instanceId;
info.stage = StageConnected;
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options & SingleCoreApplication::Mode::SecondaryNotification)) {
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
Q_EMIT q->instanceStarted();
}
@@ -395,7 +452,19 @@ void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, c
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
if (closedSocket->bytesAvailable() > 0)
if (closedSocket->bytesAvailable() > 0) {
Q_EMIT slotDataAvailable(closedSocket, instanceId);
}
}
void SingleCoreApplicationPrivate::randomSleep() {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u));
#else
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
#endif
}

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2016
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -36,6 +36,7 @@
#include <QtGlobal>
#include <QObject>
#include <QString>
#include <QMap>
#include "singlecoreapplication.h"
@@ -48,6 +49,7 @@ struct InstancesInfo {
bool primary;
quint32 secondary;
qint64 primaryPid;
char primaryUser[128];
quint16 checksum;
};
@@ -60,6 +62,7 @@ struct ConnectionInfo {
class SingleCoreApplicationPrivate : public QObject {
Q_OBJECT
public:
enum ConnectionType : quint8 {
InvalidConnection = 0,
@@ -74,27 +77,30 @@ class SingleCoreApplicationPrivate : public QObject {
};
Q_DECLARE_PUBLIC(SingleCoreApplication)
explicit SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr);
explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
~SingleCoreApplicationPrivate() override;
QString getUsername();
void genBlockServerName();
void initializeMemoryBlock();
void startPrimary();
void startSecondary();
void connectToPrimary(const int msecs, const ConnectionType connectionType);
bool connectToPrimary(const int msecs, const ConnectionType connectionType);
quint16 blockChecksum();
qint64 primaryPid();
QString primaryUser();
void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket);
void randomSleep();
SingleCoreApplication *q_ptr;
QSharedMemory *memory;
QLocalSocket *socket;
QLocalServer *server;
quint32 instanceNumber;
QString blockServerName;
SingleCoreApplication::Options options;
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
QSharedMemory *memory_;
QLocalSocket *socket_;
QLocalServer *server_;
quint32 instanceNumber_;
QString blockServerName_;
SingleCoreApplication::Options options_;
QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
public slots:
void slotConnectionEstablished();

View File

@@ -6,32 +6,32 @@ include(CheckCXXSourceCompiles)
# Check if the size of numeric types are suitable.
check_type_size("short" SIZEOF_SHORT)
if(NOT ${SIZEOF_SHORT} EQUAL 2)
if(NOT SIZEOF_SHORT EQUAL 2)
message(FATAL_ERROR "TagLib requires that short is 16-bit wide.")
endif()
check_type_size("int" SIZEOF_INT)
if(NOT ${SIZEOF_INT} EQUAL 4)
if(NOT SIZEOF_INT EQUAL 4)
message(FATAL_ERROR "TagLib requires that int is 32-bit wide.")
endif()
check_type_size("long long" SIZEOF_LONGLONG)
if(NOT ${SIZEOF_LONGLONG} EQUAL 8)
if(NOT SIZEOF_LONGLONG EQUAL 8)
message(FATAL_ERROR "TagLib requires that long long is 64-bit wide.")
endif()
check_type_size("wchar_t" SIZEOF_WCHAR_T)
if(${SIZEOF_WCHAR_T} LESS 2)
if(SIZEOF_WCHAR_T LESS 2)
message(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.")
endif()
check_type_size("float" SIZEOF_FLOAT)
if(NOT ${SIZEOF_FLOAT} EQUAL 4)
if(NOT SIZEOF_FLOAT EQUAL 4)
message(FATAL_ERROR "TagLib requires that float is 32-bit wide.")
endif()
check_type_size("double" SIZEOF_DOUBLE)
if(NOT ${SIZEOF_DOUBLE} EQUAL 8)
if(NOT SIZEOF_DOUBLE EQUAL 8)
message(FATAL_ERROR "TagLib requires that double is 64-bit wide.")
endif()
@@ -212,5 +212,5 @@ endif()
# Detect WinRT mode
if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
set(PLATFORM WINRT 1)
set(PLATFORM WINRT 1)
endif()

View File

@@ -160,6 +160,9 @@ inline String formatString(const char *format, ...) {
char buf[BufferSize];
int length;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
# if defined(HAVE_VSNPRINTF)
length = vsnprintf(buf, BufferSize, format, args);
@@ -180,6 +183,8 @@ inline String formatString(const char *format, ...) {
# endif
#pragma GCC diagnostic pop
va_end(args);
if (length > 0)

View File

@@ -13,7 +13,9 @@ include(cmake/OptionalSource.cmake)
include(cmake/ParseArguments.cmake)
include(cmake/Rpm.cmake)
include(cmake/Deb.cmake)
include(cmake/Dmg.cmake)
if(APPLE)
include(cmake/Dmg.cmake)
endif()
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
@@ -50,16 +52,11 @@ list(APPEND COMPILE_OPTIONS
-Wunused-parameter
-Wformat=2
-Wdisabled-optimization
-Wno-sign-conversion
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
$<$<COMPILE_LANGUAGE:CXX>:-Wno-old-style-cast>
$<$<COMPILE_LANGUAGE:CXX>:-fpermissive>
)
if(APPLE)
list(APPEND COMPILE_OPTIONS -Wno-unused-parameter)
endif()
option(BUILD_WERROR "Build with -Werror" OFF)
if(BUILD_WERROR)
list(APPEND COMPILE_OPTIONS -Werror)
@@ -77,6 +74,11 @@ if(${CMAKE_BUILD_TYPE} MATCHES "Debug")
set(DEBUG ON)
endif()
if(APPLE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks")
endif()
find_program(CCACHE_EXECUTABLE NAMES ccache)
if (CCACHE_EXECUTABLE)
message(STATUS "ccache found: will be used for compilation and linkage")
@@ -91,9 +93,12 @@ find_package(Backtrace QUIET)
if(Backtrace_FOUND)
set(HAVE_BACKTRACE ON)
endif()
find_package(Iconv QUIET)
find_package(GnuTLS REQUIRED)
find_package(Protobuf REQUIRED)
find_library(PROTOBUF_STATIC_LIBRARY libprotobuf.a libprotobuf)
if (NOT Protobuf_PROTOC_EXECUTABLE)
message(FATAL_ERROR "Missing protobuf compiler.")
endif()
if(LINUX)
find_package(ALSA REQUIRED)
pkg_check_modules(DBUS REQUIRED dbus-1)
@@ -118,62 +123,96 @@ pkg_check_modules(GSTREAMER_AUDIO gstreamer-audio-1.0)
pkg_check_modules(GSTREAMER_APP gstreamer-app-1.0)
pkg_check_modules(GSTREAMER_TAG gstreamer-tag-1.0)
pkg_check_modules(GSTREAMER_PBUTILS gstreamer-pbutils-1.0)
pkg_check_modules(LIBXINE libxine)
pkg_check_modules(LIBVLC libvlc)
pkg_check_modules(SQLITE REQUIRED sqlite3>=3.9)
pkg_check_modules(LIBPULSE libpulse)
pkg_check_modules(CHROMAPRINT libchromaprint)
pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92)
pkg_check_modules(LIBMTP libmtp>=1.0)
pkg_check_modules(LIBIMOBILEDEVICE libimobiledevice-1.0)
pkg_search_module(LIBUSBMUXD libusbmuxd-2.0 libusbmuxd)
pkg_search_module(LIBPLIST libplist-2.0 libplist)
pkg_check_modules(GDK_PIXBUF gdk-pixbuf-2.0)
find_package(Gettext)
find_package(FFTW3)
# QT
set(QT_MIN_VERSION 5.6)
set(QT_COMPONENTS Core Concurrent Widgets Network Sql)
option(BUILD_WITH_QT5 "Use Qt 5" OFF)
option(BUILD_WITH_QT6 "Use Qt 6" OFF)
if(WITH_QT6)
set(BUILD_WITH_QT6 ON)
endif()
if(NOT BUILD_WITH_QT5 AND NOT BUILD_WITH_QT6)
set(BUILD_WITH_QT5 ON)
endif()
set(QT_COMPONENTS Core Concurrent Widgets Network Sql)
if(X11_FOUND)
list(APPEND QT_COMPONENTS X11Extras)
endif()
if(DBUS_FOUND)
list(APPEND QT_COMPONENTS DBus)
endif()
if(APPLE)
list(APPEND QT_COMPONENTS MacExtras)
endif()
if(WIN32)
list(APPEND QT_COMPONENTS WinExtras)
endif()
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS ${QT_COMPONENTS})
set(QT_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Concurrent_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5Network_LIBRARIES} ${Qt5Sql_LIBRARIES})
set(QT_INCLUDE_DIRS ${Qt5Core_INCLUDE_DIRS} ${Qt5Concurrent_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ${Qt5Sql_INCLUDE_DIRS})
if(Qt5DBus_FOUND)
list(APPEND QT_LIBRARIES ${Qt5DBus_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5DBus_INCLUDE_DIRS})
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt5::qdbusxml2cpp LOCATION)
endif()
if(Qt5X11Extras_FOUND)
list(APPEND QT_LIBRARIES ${Qt5X11Extras_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5X11Extras_INCLUDE_DIRS})
endif()
if(Qt5MacExtras_FOUND)
list(APPEND QT_LIBRARIES ${Qt5MacExtras_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5MacExtras_INCLUDE_DIRS})
endif()
if(Qt5WinExtras_FOUND)
list(APPEND QT_LIBRARIES ${Qt5WinExtras_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5WinExtras_INCLUDE_DIRS})
endif()
find_package(Qt5 ${QT_MIN_VERSION} QUIET COMPONENTS LinguistTools CONFIG)
if (Qt5LinguistTools_FOUND)
set(QT_LCONVERT_EXECUTABLE Qt5::lconvert)
if(BUILD_WITH_QT6)
list(APPEND QT_COMPONENTS Core5Compat)
find_package(Qt6 REQUIRED COMPONENTS ${QT_COMPONENTS})
set(QtCore_LIBRARIES Qt6::Core)
set(QtConcurrent_LIBRARIES Qt6::Concurrent)
set(QtWidgets_LIBRARIES Qt6::Widgets)
set(QtNetwork_LIBRARIES Qt6::Network)
set(QtSql_LIBRARIES Qt6::Sql)
set(QT_LIBRARIES Qt6::Core Qt6::Concurrent Qt6::Widgets Qt6::Network Qt6::Sql Qt6::Core5Compat)
if(Qt6DBus_FOUND)
set(QtDBus_LIBRARIES Qt6::DBus)
list(APPEND QT_LIBRARIES Qt6::DBus)
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt6::qdbusxml2cpp LOCATION)
endif()
if(Qt6X11Extras_FOUND)
set(QtX11Extras_LIBRARIES Qt6::X11Extras)
list(APPEND QT_LIBRARIES Qt6::X11Extras)
endif()
if(Qt6WinExtras_FOUND)
set(QtWinExtras_LIBRARIES Qt6::WinExtras)
list(APPEND QT_LIBRARIES Qt6::WinExtras)
endif()
find_package(Qt6 QUIET COMPONENTS LinguistTools CONFIG)
if (Qt6LinguistTools_FOUND)
set(QT_LCONVERT_EXECUTABLE Qt6::lconvert)
endif()
elseif(BUILD_WITH_QT5)
set(QT_MIN_VERSION 5.8)
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS ${QT_COMPONENTS})
set(QtCore_LIBRARIES ${Qt5Core_LIBRARIES})
set(QtConcurrent_LIBRARIES ${Qt5Concurrent_LIBRARIES})
set(QtWidgets_LIBRARIES ${Qt5Widgets_LIBRARIES})
set(QtNetwork_LIBRARIES ${Qt5Network_LIBRARIES})
set(QtSql_LIBRARIES ${Qt5Sql_LIBRARIES})
set(QT_LIBRARIES ${QtCore_LIBRARIES} ${QtConcurrent_LIBRARIES} ${QtWidgets_LIBRARIES} ${QtNetwork_LIBRARIES} ${QtSql_LIBRARIES})
set(QT_INCLUDE_DIRS ${Qt5Core_INCLUDE_DIRS} ${Qt5Concurrent_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ${Qt5Sql_INCLUDE_DIRS})
if(Qt5DBus_FOUND)
set(QtDBus_LIBRARIES ${Qt5DBus_LIBRARIES})
list(APPEND QT_LIBRARIES ${Qt5DBus_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5DBus_INCLUDE_DIRS})
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt5::qdbusxml2cpp LOCATION)
endif()
if(Qt5X11Extras_FOUND)
set(QtX11Extras_LIBRARIES ${Qt5X11Extras_LIBRARIES})
list(APPEND QT_LIBRARIES ${Qt5X11Extras_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5X11Extras_INCLUDE_DIRS})
endif()
if(Qt5WinExtras_FOUND)
set(QtWinExtras_LIBRARIES ${Qt5WinExtras_LIBRARIES})
list(APPEND QT_LIBRARIES ${Qt5WinExtras_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5WinExtras_INCLUDE_DIRS})
endif()
find_package(Qt5 ${QT_MIN_VERSION} QUIET COMPONENTS LinguistTools CONFIG)
if (Qt5LinguistTools_FOUND)
set(QT_LCONVERT_EXECUTABLE Qt5::lconvert)
endif()
else()
message(FATAL_ERROR "Set BUILD_WITH_QT5 or BUILD_WITH_QT6")
endif()
if(X11_FOUND)
@@ -221,9 +260,20 @@ set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleap
set(SINGLEAPPLICATION_LIBRARIES singleapplication)
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
if (APPLE)
if(APPLE)
find_library(SPARKLE Sparkle)
endif (APPLE)
endif(APPLE)
if(NOT SPARKLE AND (APPLE OR WIN32))
if(BUILD_WITH_QT6)
pkg_check_modules(QTSPARKLE qtsparkle-qt6)
else()
pkg_check_modules(QTSPARKLE qtsparkle-qt5)
endif()
if(QTSPARKLE_FOUND)
set(HAVE_QTSPARKLE ON)
endif()
endif()
if (WIN32)
# RC compiler
@@ -258,10 +308,6 @@ optional_component(GSTREAMER ON "Engine: GStreamer backend"
DEPENDS "gstreamer-pbutils-1.0" GSTREAMER_PBUTILS_FOUND
)
optional_component(XINE ON "Engine: Xine backend"
DEPENDS "libxine" LIBXINE_FOUND
)
optional_component(VLC ON "Engine: VLC backend"
DEPENDS "libvlc" LIBVLC_FOUND
)
@@ -293,31 +339,35 @@ optional_component(GIO ON "Devices: GIO device backend"
optional_component(LIBGPOD ON "Devices: iPod classic support"
DEPENDS "libgpod" LIBGPOD_FOUND
DEPENDS "gdk-pixbuf" GDK_PIXBUF_FOUND
)
optional_component(LIBMTP ON "Devices: MTP support"
DEPENDS "libmtp" LIBMTP_FOUND
)
optional_component(IMOBILEDEVICE ON "Devices: iPhone, iPod Touch, iPad and Apple TV support"
DEPENDS "libimobiledevice" LIBIMOBILEDEVICE_FOUND
DEPENDS "libplist" LIBPLIST_FOUND
DEPENDS "libusbmuxd" LIBUSBMUXD_FOUND
DEPENDS "libgpod" HAVE_LIBGPOD
)
optional_component(SPARKLE ON "Sparkle integration"
DEPENDS "macOS" APPLE
DEPENDS "Sparkle" SPARKLE
)
optional_component(TRANSLATIONS ON "Translations"
DEPENDS "gettext" GETTEXT_FOUND
DEPENDS "Qt5LinguistTools" Qt5LinguistTools_FOUND
)
if(BUILD_WITH_QT6)
optional_component(TRANSLATIONS ON "Translations"
DEPENDS "gettext" GETTEXT_FOUND
DEPENDS "Qt6LinguistTools" Qt6LinguistTools_FOUND
)
else()
optional_component(TRANSLATIONS ON "Translations"
DEPENDS "gettext" GETTEXT_FOUND
DEPENDS "Qt5LinguistTools" Qt5LinguistTools_FOUND
)
endif()
option(INSTALL_TRANSLATIONS "Install translations" OFF)
optional_component(SUBSONIC ON "Subsonic support")
optional_component(TIDAL ON "Tidal support")
optional_component(QOBUZ ON "Qobuz support")
optional_component(MOODBAR ON "Moodbar"
DEPENDS "fftw3" FFTW3_FOUND
@@ -343,8 +393,8 @@ endif(USE_BUNDLE AND NOT USE_BUNDLE_DIR)
if(NOT CMAKE_CROSSCOMPILING)
set(CMAKE_REQUIRED_FLAGS "--std=c++11")
set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Sql_LIBRARIES})
set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Sql_INCLUDE_DIRS})
set(CMAKE_REQUIRED_LIBRARIES ${QtCore_LIBRARIES} ${QtSql_LIBRARIES})
set(CMAKE_REQUIRED_INCLUDES ${QtCore_INCLUDE_DIRS} ${QtSql_INCLUDE_DIRS})
check_cxx_source_runs("
#include <QSqlDatabase>
#include <QSqlQuery>
@@ -361,20 +411,6 @@ if(NOT CMAKE_CROSSCOMPILING)
)
endif()
if(HAVE_XINE)
check_cxx_source_compiles("
#define METRONOM_INTERNAL
#include <iostream>
#include <xine/metronom.h>
int main() {
metronom_t metronom;
std::cout << metronom.pts_per_smpls;
return 0;
}
"
XINE_ANALYZER)
endif()
# Set up definitions
add_definitions(-DBOOST_BIND_NO_PLACEHOLDERS)
@@ -415,8 +451,8 @@ add_custom_target(uninstall
# Show a summary of what we have enabled
summary_show()
if(NOT HAVE_GSTREAMER AND NOT HAVE_XINE AND NOT HAVE_VLC)
message(FATAL_ERROR "You need to have either GStreamer, Xine or VLC to compile!")
if(NOT HAVE_GSTREAMER AND NOT HAVE_VLC)
message(FATAL_ERROR "You need to have either GStreamer or VLC to compile!")
elseif(NOT HAVE_GSTREAMER)
message(WARNING "GStreamer is the only engine that is fully implemented. Using other engines is possible but not recommended.")
endif()

120
Changelog
View File

@@ -2,6 +2,126 @@ Strawberry Music Player
=======================
ChangeLog
0.8.3:
Bugfixes:
* Fixed updating playing widget song details in small cover mode.
* Fixed file extension when transcoding songs.
* Fixed updating album cover to collection in edit tag dialog when pressing save.
* Fixed songs with empty artist in collection.
* Fixed possible crashes with stream discovery.
* Fixed setting engine state to null.
* Fixed tagreader crash with empty APE tags.
* Fixed a gstreamer memory leak.
Enhancements:
* (Windows) Added WASAPI plugin.
0.8.2:
Bugfixes:
* Fixed broken transition to next song for CUE files with certain audio formats (regression since version 0.6.13).
* Fixed all collection divider keys showing on top with some language collate settings (regression in version 0.8.1).
* Fixed SQL querying songs by song ID when song ID is a string.
* Fixed saving album covers for LMS Subsonic servers.
* Fixed reading song creation dates with LMS Subsonic servers.
* Fixed saving initial settings.
* Removed use of HTML in system tray icon tooltip for all desktop environments instead of just KDE and Cinnamon.
* (Windows) Ignore "IDirectSoundBuffer_GetStatus The operation completed successfully" false error when switching device while playing.
0.8.1:
Bugfixes:
* Fixed engine selection in backend settings with Qt 6.
* Fixed pixelated playlist source icon for currently playing song.
* Fixed crash when deleting queued songs from playlist.
* Fixed situations where songs could disappear or be shown multiple times with certain collection groupings.
* Fixed initial sizes of playlist header columns.
* Fixed Strawberry preventing logout.
* Fixed incorrectly splitting of basename for moodbar and transcoding for filenames with several dots.
* Fixed certain cases where "playing now" for scrobbler were sent twice.
* Fixed album cover loaded twice for certain songs causing slugglish playing widget.
* Fixed playing widget to draw text after album cover is fully shown.
* Fixed crash when trying to copy a closed playlist to a device.
* Fixed incorrect song source for CUE songs when added through the collection watcher.
* Disable use of HTML in system tray tooltip on Cinnamon too.
* Remove problematic '&' character from OSD messages.
* (macOS) Fixed crash on exit when cover manager is open.
* (macOS) Fixed graphical corruption.
* (Windows) Fixed GStreamer registry problems.
* (Windows) Register Tidal URL Scheme in Windows installer.
Enhancements:
* Improved playlist autoscrolling.
* Only allow playlist right click tag editing for editable songs.
* Read song creation time from subsonic API.
* Remember manually set compilation status for albums when songs are rescanned.
* Added icons for edit tag playlist right click menu actions.
* Maximize dialogs if they are already open when clicked again in the menu.
* Added support for compilation tag to edit tag dialog.
* Show song info and album cover in OSD on stop and pause.
* Reshow OSD on song restart.
* Always save initial settings.
* Removed use of deprecated gstreamer "low-percent" (Minimum buffer fill setting).
* Added buffer low and high watermark settings to backend settings.
* Make use of newer version of the desktop notifications service when available.
New features:
* Added setting for enabling scrobbling based on song source.
* Added optional delete from disk in collection and playlist.
* Added Last.fm import data wizard.
* Added smart and dynamic playlists.
* Added song ratings.
* Added Qobuz streaming support.
* Added Subsonic server side scrobbling support.
* Load thumbnails from iPods to show under device collection.
0.7.2:
Bugfixes:
* Fixed installation directory for translations.
* Fixed collection sorting for non-ASCII characters.
* Fixed closing connected devices on exit.
0.7.1:
Bugfixes:
* Fixed incorrectly mapped global shortcuts keys "2" and "3".
* Fixed Last.fm scrobbling to correctly start array notation for parameters at 0 and not 1.
* Fixed sending trackNumber correctly for Last.fm and Libre.fm scrobbling.
* Fixed collection search when using special characters in the search query.
* Fixed reading and saving MP4 lyrics tag.
* Fixed reading ASF comment tag.
* Fixed adding playlist songs outside the collection when there are multiple files with the same URL.
* Fixed the rescan songs option to work with local songs outside of the collection.
* Fixed problems with editing song metadata in the playlists.
* Fixed saving and restoring playlist scrollbar position when switching between playlists.
* Fixed minor issue in cue parser with date and genre.
* (macOS) Fixed gst-libav plugin issue resulting in MP3 not working.
Enhancements:
* Simplified and improved startup behaviour code.
* Adapted all source code to be compatible with Qt 6, and increased required Qt version to 5.8.
* Added option to compile with Qt 6 (-DWITH_QT6=ON).
* Base warning for show in file browser on unique directories to avoid unneeded warning about opening many files.
* Use album artist instead of artist for album repeat mode when available.
* Added extra safety for overwriting files for filesystem storages when organizing files.
* Remove diacritics in FTS search.
* Improved playlist context menu.
* Added fatal CMake error for missing protobuf compiler.
* Added support for parsing radio streams metadata with tilde in title.
* Added CMake option to install translation files.
* Increased maximum time step for seeking to 60.
* (Unix) Added playback actions to desktop file.
* (macOS) Hide behaviour settings that are unavailable on macOS.
* (macOS) Fixed compile warnings.
* (macOS) Added Sparkle integration to notify on new versions.
* (Windows) Added QtSparkle support to notify on new versions.
Removed features:
* Removed Xine engine support.
* Removed broken imobiledevice (iPhone) support.
0.6.13:
Bugfixes:

View File

@@ -1,5 +0,0 @@
from jonaski/opensuse:lp151
run mkdir -p /usr/src/app
workdir /usr/src/app
copy . /usr/src/app

View File

@@ -2,7 +2,7 @@
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/jonaskvinge)
=======================
Strawberry is a music player and music collection organizer. It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles. It's written in C++ using the Qt 5 framework.
Strawberry is a music player and music collection organizer. It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles. It's written in C++ using the Qt toolkit.
![Browse](https://www.strawberrymusicplayer.org/pictures/screenshot-002-large.png)
@@ -35,7 +35,8 @@ You can also make a one-time payment through [paypal.me/jonaskvinge](https://pay
* Supports WAV, FLAC, WavPack, Ogg FLAC, Ogg Vorbis, Ogg Opus, Ogg Speex, MPC, TrueAudio, AIFF, MP4, MP3, ASF and Monkey's Audio.
* Audio CD playback
* Native desktop notifications
* Playlists in multiple formats
* Playlist management
* Smart and dynamic playlists
* Advanced audio output and device configuration for bit-perfect playback on Linux
* Edit tags on music files
* Fetch tags from MusicBrainz
@@ -44,9 +45,9 @@ You can also make a one-time payment through [paypal.me/jonaskvinge](https://pay
* Support for multiple backends
* Audio analyzer
* Audio equalizer
* Transfer music to iPod, iPhone, MTP or mass-storage USB player
* Transfer music to iPod, MTP or mass-storage USB player
* Scrobbler with support for [Last.fm](https://www.last.fm/), [Libre.fm](https://libre.fm/) and [ListenBrainz](https://listenbrainz.org/)
* Subsonic and Tidal streaming support
* Subsonic, Tidal and Qobuz streaming support
It has so far been tested to work on Linux, OpenBSD, FreeBSD, macOS and Windows.
@@ -63,14 +64,14 @@ To build Strawberry from source you need the following installed on your system
* [POSIX thread (pthread)](http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html)
* [GLib](https://developer.gnome.org/glib/)
* [Protobuf library and compiler](https://developers.google.com/protocol-buffers/)
* [Qt 5.6 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
* [Qt 5 components X11Extras and DBus for Linux/BSD, MacExtras for macOS and WinExtras for Windows](https://www.qt.io/)
* [Qt 5.8 or higher (or Qt 6) with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
* [Qt components X11Extras and D-Bus for Linux/BSD and WinExtras for Windows](https://www.qt.io/)
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
* [Chromaprint library](https://acoustid.org/chromaprint)
* [ALSA library (linux)](https://www.alsa-project.org/)
* [DBus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
* [D-Bus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
* [PulseAudio (linux optional)](https://www.freedesktop.org/wiki/Software/PulseAudio/?)
* [GStreamer](https://gstreamer.freedesktop.org/), [Xine](https://www.xine-project.org) or [VLC](https://www.videolan.org)
* [GStreamer](https://gstreamer.freedesktop.org/) or [VLC](https://www.videolan.org)
* [GnuTLS](https://www.gnutls.org/)
Optional dependencies:
@@ -78,12 +79,13 @@ Optional dependencies:
* Audio CD: [libcdio](https://www.gnu.org/software/libcdio/)
* MTP devices: [libmtp](http://libmtp.sourceforge.net/)
* iPod Classic devices: [libgpod](http://www.gtkpod.org/libgpod/)
* iPhone, iPod Touch, iPad and Apple TV devices: [libimobiledevice, libplist and libusbmuxd](https://www.libimobiledevice.org/)
* Moodbar: [fftw3](http://www.fftw.org/)
Either GStreamer, Xine or VLC engine is required, but only GStreamer is fully implemented so far.
Either GStreamer or VLC engine is required, but only GStreamer is fully implemented so far.
You should also install the gstreamer plugins base and good, and optionally bad and ugly.
With Qt 6 we also depend on the Core5Compat module for QTextCodec.
### :wrench: Compiling from source
### Get the code:
@@ -95,8 +97,12 @@ You should also install the gstreamer plugins base and good, and optionally bad
cd strawberry
mkdir build && cd build
cmake ..
make -j4
make -j$(nproc)
sudo make install
To compile with Qt 6 use:
cmake .. -DBUILD_WITH_QT6=ON
### :penguin: Packaging status

View File

@@ -1,6 +1,8 @@
execute_process(COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macversion.sh OUTPUT_VARIABLE MACOS_VERSION_PACKAGE OUTPUT_STRIP_TRAILING_WHITESPACE)
add_custom_target(dmg
COMMAND /usr/local/opt/qt5/bin/macdeployqt strawberry.app
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macdeploy.py strawberry.app
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/create-dmg.sh strawberry.app
COMMAND create-dmg --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 strawberry-${STRAWBERRY_VERSION_PACKAGE}-${MACOS_VERSION_PACKAGE}.dmg strawberry.app
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

View File

@@ -15,7 +15,11 @@ macro(optional_source TOGGLE)
list(APPEND OTHER_SOURCES ${OPTIONAL_SOURCE_HEADERS})
set(_uic_sources)
qt5_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
if(BUILD_WITH_QT6)
qt6_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
else()
qt5_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
endif()
list(APPEND OTHER_SOURCES ${_uic_sources})
list(APPEND OTHER_UIC_SOURCES ${_uic_sources})
endif(${TOGGLE})

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.11)
cmake_minimum_required(VERSION 3.0)
find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext)
if(NOT GETTEXT_XGETTEXT_EXECUTABLE)
@@ -19,6 +19,8 @@ set (XGETTEXT_OPTIONS
--from-code=utf-8
)
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/translations)
macro(add_pot outfiles header pot)
# Make relative filenames for all source files
set(add_pot_sources)
@@ -64,14 +66,21 @@ macro(add_po outfiles po_prefix)
)
list(APPEND ${outfiles} ${_qm_filepath})
list(APPEND INSTALL_TRANSLATIONS_FILES ${_qm_filepath})
endforeach (_lang)
# Generate a qrc file for the translations
set(_qrc ${CMAKE_CURRENT_BINARY_DIR}/${ADD_PO_DIRECTORY}/translations.qrc)
file(WRITE ${_qrc} "<RCC><qresource prefix=\"/${ADD_PO_DIRECTORY}\">")
foreach(_lang ${ADD_PO_LANGUAGES})
file(APPEND ${_qrc} "<file>${po_prefix}${_lang}.qm</file>")
endforeach(_lang)
file(APPEND ${_qrc} "</qresource></RCC>")
qt5_add_resources(${outfiles} ${_qrc})
if(NOT INSTALL_TRANSLATIONS)
set(_qrc ${CMAKE_CURRENT_BINARY_DIR}/${ADD_PO_DIRECTORY}/translations.qrc)
file(WRITE ${_qrc} "<RCC><qresource prefix=\"/${ADD_PO_DIRECTORY}\">")
foreach(_lang ${ADD_PO_LANGUAGES})
file(APPEND ${_qrc} "<file>${po_prefix}${_lang}.qm</file>")
endforeach(_lang)
file(APPEND ${_qrc} "</qresource></RCC>")
if(BUILD_WITH_QT6)
qt6_add_resources(${outfiles} ${_qrc})
else()
qt5_add_resources(${outfiles} ${_qrc})
endif()
endif()
endmacro(add_po)

View File

@@ -1,6 +1,6 @@
set(STRAWBERRY_VERSION_MAJOR 0)
set(STRAWBERRY_VERSION_MINOR 6)
set(STRAWBERRY_VERSION_PATCH 13)
set(STRAWBERRY_VERSION_MINOR 8)
set(STRAWBERRY_VERSION_PATCH 3)
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION OFF)

View File

@@ -13,9 +13,10 @@
<file>schema/schema-10.sql</file>
<file>schema/schema-11.sql</file>
<file>schema/schema-12.sql</file>
<file>schema/schema-13.sql</file>
<file>schema/device-schema.sql</file>
<file>style/strawberry.css</file>
<file>html/playing-tooltip.html</file>
<file>style/smartplaylistsearchterm.css</file>
<file>html/oauthsuccess.html</file>
<file>pictures/strawberry.png</file>
<file>pictures/strawberry-faded.png</file>
@@ -40,6 +41,8 @@
<file>pictures/osd_shadow_edge.png</file>
<file>pictures/nyancat.png</file>
<file>pictures/rainbowdash.png</file>
<file>pictures/star-on.png</file>
<file>pictures/star-off.png</file>
<file>fonts/HumongousofEternitySt.ttf</file>
<file>mood/sample.mood</file>
<file>text/ghosts.txt</file>

View File

@@ -1,41 +0,0 @@
<table cellspacing="5" cellpadding="5">
<tr>
<td colspan="%columns">
<center><h4>%appName</h4></center>
</td>
</tr>
<tr>
%image
<td>
<table cellspacing="1" cellpadding="1">
<tr>
<td>
<p align="right">%titleKey</p>
</td>
<td>%titleValue</td>
</tr>
<tr>
<td>
<p align="right">%artistKey</p>
</td>
<td>%artistValue</td>
</tr>
<tr>
<td>
<p align="right">%albumKey</p>
</td>
<td>%albumValue</td>
</tr>
<tr>
<td colspan="2" height="20" />
</tr>
<tr>
<td>
<p align="right">%lengthKey</p>
</td>
<td>%lengthValue</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -81,7 +81,6 @@
<file>icons/128x128/view-refresh.png</file>
<file>icons/128x128/library-music.png</file>
<file>icons/128x128/vlc.png</file>
<file>icons/128x128/xine.png</file>
<file>icons/128x128/zoom-in.png</file>
<file>icons/128x128/zoom-out.png</file>
<file>icons/128x128/scrobble.png</file>
@@ -90,6 +89,8 @@
<file>icons/128x128/love.png</file>
<file>icons/128x128/subsonic.png</file>
<file>icons/128x128/tidal.png</file>
<file>icons/128x128/qobuz.png</file>
<file>icons/128x128/multimedia-player-ipod-standard-black.png</file>
<file>icons/64x64/albums.png</file>
<file>icons/64x64/alsa.png</file>
<file>icons/64x64/application-exit.png</file>
@@ -172,7 +173,6 @@
<file>icons/64x64/view-refresh.png</file>
<file>icons/64x64/library-music.png</file>
<file>icons/64x64/vlc.png</file>
<file>icons/64x64/xine.png</file>
<file>icons/64x64/zoom-in.png</file>
<file>icons/64x64/zoom-out.png</file>
<file>icons/64x64/scrobble.png</file>
@@ -181,6 +181,8 @@
<file>icons/64x64/love.png</file>
<file>icons/64x64/subsonic.png</file>
<file>icons/64x64/tidal.png</file>
<file>icons/64x64/qobuz.png</file>
<file>icons/64x64/multimedia-player-ipod-standard-black.png</file>
<file>icons/48x48/albums.png</file>
<file>icons/48x48/alsa.png</file>
<file>icons/48x48/application-exit.png</file>
@@ -267,7 +269,6 @@
<file>icons/48x48/view-refresh.png</file>
<file>icons/48x48/library-music.png</file>
<file>icons/48x48/vlc.png</file>
<file>icons/48x48/xine.png</file>
<file>icons/48x48/zoom-in.png</file>
<file>icons/48x48/zoom-out.png</file>
<file>icons/48x48/scrobble.png</file>
@@ -276,6 +277,8 @@
<file>icons/48x48/love.png</file>
<file>icons/48x48/subsonic.png</file>
<file>icons/48x48/tidal.png</file>
<file>icons/48x48/qobuz.png</file>
<file>icons/48x48/multimedia-player-ipod-standard-black.png</file>
<file>icons/32x32/albums.png</file>
<file>icons/32x32/alsa.png</file>
<file>icons/32x32/application-exit.png</file>
@@ -362,7 +365,6 @@
<file>icons/32x32/view-refresh.png</file>
<file>icons/32x32/library-music.png</file>
<file>icons/32x32/vlc.png</file>
<file>icons/32x32/xine.png</file>
<file>icons/32x32/zoom-in.png</file>
<file>icons/32x32/zoom-out.png</file>
<file>icons/32x32/scrobble.png</file>
@@ -371,6 +373,8 @@
<file>icons/32x32/love.png</file>
<file>icons/32x32/subsonic.png</file>
<file>icons/32x32/tidal.png</file>
<file>icons/32x32/qobuz.png</file>
<file>icons/32x32/multimedia-player-ipod-standard-black.png</file>
<file>icons/22x22/albums.png</file>
<file>icons/22x22/alsa.png</file>
<file>icons/22x22/application-exit.png</file>
@@ -457,7 +461,6 @@
<file>icons/22x22/view-refresh.png</file>
<file>icons/22x22/library-music.png</file>
<file>icons/22x22/vlc.png</file>
<file>icons/22x22/xine.png</file>
<file>icons/22x22/zoom-in.png</file>
<file>icons/22x22/zoom-out.png</file>
<file>icons/22x22/scrobble.png</file>
@@ -466,5 +469,7 @@
<file>icons/22x22/love.png</file>
<file>icons/22x22/subsonic.png</file>
<file>icons/22x22/tidal.png</file>
<file>icons/22x22/qobuz.png</file>
<file>icons/22x22/multimedia-player-ipod-standard-black.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

BIN
data/icons/22x22/qobuz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
data/icons/32x32/qobuz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
data/icons/48x48/qobuz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
data/icons/64x64/qobuz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
data/icons/full/qobuz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

BIN
data/pictures/star-off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

BIN
data/pictures/star-on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

View File

@@ -62,7 +62,9 @@ CREATE TABLE device_%deviceid_songs (
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT
cue_path TEXT,
rating INTEGER DEFAULT -1
);
@@ -72,7 +74,7 @@ CREATE INDEX idx_device_%deviceid_songs_comp_artist ON device_%deviceid_songs (c
CREATE VIRTUAL TABLE device_%deviceid_fts USING fts5(
ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment,
tokenize = "unicode61 remove_diacritics 0"
tokenize = "unicode61 remove_diacritics 1"
);
UPDATE devices SET schema_version=1 WHERE ROWID=%deviceid;
UPDATE devices SET schema_version=2 WHERE ROWID=%deviceid;

View File

@@ -180,7 +180,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS tidal_artists_songs_fts USING fts5(
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
tokenize = "unicode61 remove_diacritics 1"
);
@@ -195,7 +195,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS tidal_albums_songs_fts USING fts5(
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
tokenize = "unicode61 remove_diacritics 1"
);
@@ -210,7 +210,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS tidal_songs_fts USING fts5(
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
tokenize = "unicode61 remove_diacritics 1"
);

231
data/schema/schema-13.sql Normal file
View File

@@ -0,0 +1,231 @@
ALTER TABLE %allsongstables ADD COLUMN rating INTEGER DEFAULT -1;
ALTER TABLE playlists ADD COLUMN dynamic_playlist_type INTEGER;
ALTER TABLE playlists ADD COLUMN dynamic_playlist_backend TEXT;
ALTER TABLE playlists ADD COLUMN dynamic_playlist_data BLOB;
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1
);
CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1
);
CREATE TABLE IF NOT EXISTS qobuz_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1
);
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_artists_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 1"
);
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_albums_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 1"
);
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 1"
);
UPDATE schema_version SET version=13;

View File

@@ -13,7 +13,7 @@ CREATE VIRTUAL TABLE %allsongstables_fts USING fts5(
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
tokenize = "unicode61 remove_diacritics 1"
);
@@ -28,7 +28,7 @@ CREATE VIRTUAL TABLE playlist_items_fts_ USING fts5(
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
tokenize = "unicode61 remove_diacritics 1"
);

View File

@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version (
DELETE FROM schema_version;
INSERT INTO schema_version (version) VALUES (12);
INSERT INTO schema_version (version) VALUES (13);
CREATE TABLE IF NOT EXISTS directories (
path TEXT NOT NULL,
@@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS songs (
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT 0,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
@@ -70,178 +70,9 @@ CREATE TABLE IF NOT EXISTS songs (
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT
cue_path TEXT,
);
CREATE TABLE IF NOT EXISTS tidal_artists_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT 0,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT
);
CREATE TABLE IF NOT EXISTS tidal_albums_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT 0,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT
);
CREATE TABLE IF NOT EXISTS tidal_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT 0,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT
rating INTEGER DEFAULT -1
);
@@ -254,7 +85,7 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT 0,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
@@ -298,7 +129,363 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT
cue_path TEXT,
rating INTEGER DEFAULT -1
);
CREATE TABLE IF NOT EXISTS tidal_artists_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1
);
CREATE TABLE IF NOT EXISTS tidal_albums_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1
);
CREATE TABLE IF NOT EXISTS tidal_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1
);
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1
);
CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1
);
CREATE TABLE IF NOT EXISTS qobuz_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1
);
@@ -309,7 +496,11 @@ CREATE TABLE IF NOT EXISTS playlists (
ui_order INTEGER NOT NULL DEFAULT 0,
special_type TEXT,
ui_path TEXT,
is_favorite INTEGER NOT NULL DEFAULT 0
is_favorite INTEGER NOT NULL DEFAULT 0,
dynamic_playlist_type INTEGER,
dynamic_playlist_backend TEXT,
dynamic_playlist_data BLOB
);
@@ -371,7 +562,9 @@ CREATE TABLE IF NOT EXISTS playlist_items (
effective_albumartist TEXT,
effective_originalyear INTEGER,
cue_path TEXT
cue_path TEXT,
rating INTEGER DEFAULT -1
);
@@ -410,52 +603,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS songs_fts USING fts5(
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
);
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_artists_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
);
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_albums_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
);
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
tokenize = "unicode61 remove_diacritics 1"
);
@@ -470,11 +618,11 @@ CREATE VIRTUAL TABLE IF NOT EXISTS subsonic_songs_fts USING fts5(
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
tokenize = "unicode61 remove_diacritics 1"
);
CREATE VIRTUAL TABLE IF NOT EXISTS playlist_items_fts_ USING fts5(
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_artists_songs_fts USING fts5(
ftstitle,
ftsalbum,
@@ -485,7 +633,82 @@ CREATE VIRTUAL TABLE IF NOT EXISTS playlist_items_fts_ USING fts5(
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
tokenize = "unicode61 remove_diacritics 1"
);
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_albums_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 1"
);
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 1"
);
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_artists_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 1"
);
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_albums_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 1"
);
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_songs_fts USING fts5(
ftstitle,
ftsalbum,
ftsartist,
ftsalbumartist,
ftscomposer,
ftsperformer,
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 1"
);
@@ -500,6 +723,6 @@ CREATE VIRTUAL TABLE IF NOT EXISTS %allsongstables_fts USING fts5(
ftsgrouping,
ftsgenre,
ftscomment,
tokenize = "unicode61 remove_diacritics 0"
tokenize = "unicode61 remove_diacritics 1"
);

View File

@@ -0,0 +1,42 @@
#frame {
border: 1px solid palette(mid);
border-radius: 5px;
}
#container {
margin: 2px;
}
#remove {
border-top-left-radius: 0px;
border-top-right-radius: 5px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 5px;
border: 0px solid transparent;
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 %light,
stop:0.4 %light,
stop:0.6 %dark,
stop:1 %dark);
margin-left: 5px;
padding: 0px 5px;
}
#remove:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 %light2,
stop:0.4 %light2,
stop:0.6 %base,
stop:1 %base);
border: 0px solid transparent;
}
#remove:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 %base,
stop:0.4 %base,
stop:0.6 %light2,
stop:1 %light2);
border: 0px solid transparent;
}

3
debian/control vendored
View File

@@ -24,10 +24,7 @@ Build-Depends: debhelper (>= 11),
libgstreamer-plugins-base1.0-dev,
libcdio-dev,
libgpod-dev,
libimobiledevice-dev,
libmtp-dev,
libplist-dev,
libusbmuxd-dev,
libchromaprint-dev,
libfftw3-dev
Standards-Version: 4.2.1

38
debian/copyright vendored
View File

@@ -12,7 +12,6 @@ Files: src/core/timeconstants.h
ext/libstrawberry-common/core/logging.h
ext/libstrawberry-common/core/messagehandler.cpp
ext/libstrawberry-common/core/messagehandler.h
ext/libstrawberry-common/core/override.h
Copyright: 2011, 2012, David Sansome <me@davidsansome.com>
License: Apache-2.0
@@ -122,8 +121,14 @@ Files: src/core/main.cpp
src/context/contextalbumsview.h
src/widgets/playingwidget.cpp
src/widgets/playingwidget.h
src/widgets/osdpretty.cpp
src/widgets/osdpretty.h
src/osd/osdbase.cpp
src/osd/osdbase.h
src/osd/osdpretty.cpp
src/osd/osdpretty.h
src/osd/osddbus.cpp
src/osd/osddbus.h
src/osd/osdmac.cpp
src/osd/osdmac.h
src/dialogs/about.cpp
src/dialogs/about.h
src/playlist/playlist.cpp
@@ -171,12 +176,12 @@ Files: src/core/main.cpp
src/settings/shortcutssettingspage.h
src/settings/appearancesettingspage.cpp
src/settings/appearancesettingspage.h
src/organise/organise.cpp
src/organise/organise.h
src/organise/organisedialog.cpp
src/organise/organisedialog.h
src/organise/organiseerrordialog.cpp
src/organise/organiseerrordialog.h
src/organize/organize.cpp
src/organize/organize.h
src/organize/organizedialog.cpp
src/organize/organizedialog.h
src/organize/organizeerrordialog.cpp
src/organize/organizeerrordialog.h
src/transcoder/transcoder.cpp
src/transcoder/transcoder.h
src/musicbrainz/musicbrainzclient.cpp
@@ -213,21 +218,6 @@ Copyright: 2017, 2018, Jonas Kvinge <jonas@jkvinge.net>
2003-2005, Mark Kretschmann <markey@web.de>
License: GPL-2+
Files: src/engine/xineengine.cpp
src/engine/xineengine.h
Copyright: 2017, 2018, Jonas Kvinge <jonas@jkvinge.net>
2005, Ian Monroe <ian@monroe.nu>
2005, Christophe Thommeret <hftom@free.fr>
2005, 2006, Mark Kretschmann <markey@web.de>
2004, 2005, Max Howell <max.howell@methylblue.com>
2003, 2004, J. Kofler <kaffeine@gmx.net>
License: GPL-2+
Files: src/engine/xinescope.c
src/engine/xinescope.h
Copyright: 2004, Max Howell <max.howell@methylblue.com>
License: GPL-2+
Files: src/widgets/fancytabwidget.cpp
src/widgets/fancytabwidget.h
Copyright: 2018, Vikram Ambrose <ambroseworks@gmail.com>

1
debian/rules vendored
View File

@@ -5,7 +5,6 @@
override_dh_auto_clean:
rm -f dist/macos/Info.plist
rm -f dist/macos/create-dmg.sh
rm -f dist/unix/PKGBUILD
rm -f dist/unix/strawberry.spec
rm -f dist/scripts/maketarball.sh

1
dist/CMakeLists.txt vendored
View File

@@ -3,7 +3,6 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/PKGBUILD.in ${CMAKE_CURRENT_SOUR
if (APPLE)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/create-dmg.sh.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/create-dmg.sh)
endif (APPLE)
if (WIN32)

View File

@@ -34,6 +34,10 @@
<string>public.app-category.music</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.4</string>
<key>SUFeedURL</key>
<string>https://www.strawberrymusicplayer.org/sparkle-macos</string>
<key>SUPublicEDKey</key>
<string>3IRScV8YtNVnx7zoeJAXvg28Kh1gN/Pyl2iPM467pG8=</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>

View File

@@ -1,24 +0,0 @@
#!/bin/sh
version="@STRAWBERRY_VERSION_PACKAGE@"
if [ -z "$1" ]; then
echo "Usage: $0 <bundle.app> (append)"
exit 1
fi
name=$(basename "$1" | perl -pe 's/(.*).app/\1/')
bundle_dir="$1"
temp_dir="dmg/$name"
if [ -z "$2" ]; then
output_file="$name-$version.dmg"
else
output_file="$name-$2-$version.dmg"
fi
rm -rf "$temp_dir"
rm -f "$output_file"
mkdir -p "$temp_dir"
/usr/local/bin/create-dmg --volname "$name" --background "@CMAKE_SOURCE_DIR@/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon $bundle_dir 150 218 --window-size 600 450 $output_file $bundle_dir

View File

@@ -21,17 +21,17 @@ import logging
import os
import re
import subprocess
import commands
import sys
import traceback
LOGGER = logging.getLogger('macdeploy')
LIBRARY_SEARCH_PATH = ['/usr/local/lib']
LIBRARY_SEARCH_PATH = ['/usr/local/lib', '/usr/local/opt/icu4c/lib']
FRAMEWORK_SEARCH_PATH = [
'/Library/Frameworks',
os.path.join(os.environ['HOME'], 'Library/Frameworks')
os.path.join(os.environ['HOME'], 'Library/Frameworks'),
'/Library/Frameworks/Sparkle.framework/Versions'
]
QT_PLUGINS = [
@@ -66,56 +66,57 @@ GSTREAMER_SEARCH_PATH = [
GSTREAMER_PLUGINS = [
'libgstapetag.so',
'libgstapp.so',
'libgstaudioconvert.so',
'libgstaudiofx.so',
'libgstaudiomixer.so',
'libgstaudioparsers.so',
'libgstaudiorate.so',
'libgstaudioresample.so',
'libgstaudiotestsrc.so',
'libgstaudiovisualizers.so',
'libgstauparse.so',
'libgstautoconvert.so',
'libgstautodetect.so',
'libgstcoreelements.so',
'libgstequalizer.so',
'libgstgio.so',
'libgsticydemux.so',
'libgstid3demux.so',
'libgstlevel.so',
'libgstosxaudio.so',
'libgstplayback.so',
'libgstrawparse.so',
'libgstrealmedia.so',
'libgstreplaygain.so',
'libgstsoup.so',
'libgstspectrum.so',
'libgsttypefindfunctions.so',
'libgstvolume.so',
'libgstxingmux.so',
'libgsttcp.so',
'libgstudp.so',
'libgstpbtypes.so',
'libgstrtp.so',
'libgstrtsp.so',
'libgstapetag.dylib',
'libgstapp.dylib',
'libgstaudioconvert.dylib',
'libgstaudiofx.dylib',
'libgstaudiomixer.dylib',
'libgstaudioparsers.dylib',
'libgstaudiorate.dylib',
'libgstaudioresample.dylib',
'libgstaudiotestsrc.dylib',
'libgstaudiovisualizers.dylib',
'libgstauparse.dylib',
'libgstautoconvert.dylib',
'libgstautodetect.dylib',
'libgstcoreelements.dylib',
'libgstequalizer.dylib',
'libgstgio.dylib',
'libgsticydemux.dylib',
'libgstid3demux.dylib',
'libgstlevel.dylib',
'libgstosxaudio.dylib',
'libgstplayback.dylib',
'libgstrawparse.dylib',
'libgstrealmedia.dylib',
'libgstreplaygain.dylib',
'libgstsoup.dylib',
'libgstspectrum.dylib',
'libgsttypefindfunctions.dylib',
'libgstvolume.dylib',
'libgstxingmux.dylib',
'libgsttcp.dylib',
'libgstudp.dylib',
'libgstpbtypes.dylib',
'libgstrtp.dylib',
'libgstrtsp.dylib',
'libgstflac.so',
'libgstwavparse.so',
'libgstfaac.so',
'libgstfaad.so',
'libgstogg.so',
'libgstopus.so',
'libgstopusparse.so',
'libgstasf.so',
'libgstspeex.so',
'libgsttaglib.so',
'libgstvorbis.so',
'libgstisomp4.so',
'libgstlibav.so',
'libgstaiff.so',
'libgstlame.so',
'libgstflac.dylib',
'libgstwavparse.dylib',
'libgstfaac.dylib',
'libgstfaad.dylib',
'libgstogg.dylib',
'libgstopus.dylib',
'libgstopusparse.dylib',
'libgstasf.dylib',
'libgstspeex.dylib',
'libgsttaglib.dylib',
'libgstvorbis.dylib',
'libgstisomp4.dylib',
'libgstlibav.dylib',
'libgstaiff.dylib',
'libgstlame.dylib',
'libgstmusepack.dylib',
]
@@ -154,14 +155,8 @@ class InstallNameToolError(Error):
class CouldNotFindGstreamerPluginError(Error):
pass
class CouldNotFindXinePluginError(Error):
pass
class CouldNotFindVLCPluginError(Error):
pass
if len(sys.argv) < 2:
print 'Usage: %s <bundle.app>' % sys.argv[0]
print('Usage: %s <bundle.app>' % sys.argv[0])
bundle_dir = sys.argv[1]
@@ -181,27 +176,40 @@ fixed_frameworks = set()
def GetBrokenLibraries(binary):
#print "Checking libs for binary: %s" % binary
output = subprocess.Popen([OTOOL, '-L', binary], stdout=subprocess.PIPE).communicate()[0]
#print("Checking libs for binary: %s" % binary)
output = subprocess.Popen([OTOOL, '-L', binary], stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
broken_libs = {'frameworks': [], 'libs': []}
for line in [x.split(' ')[0].lstrip() for x in output.split('\n')[1:]]:
#print "Checking line: %s" % line
#print("Checking line: %s" % line)
if not line: # skip empty lines
continue
if os.path.basename(binary) == os.path.basename(line):
#print "mnope %s-%s" % (os.path.basename(binary), os.path.basename(line))
#print("mnope %s-%s" % (os.path.basename(binary), os.path.basename(line)))
continue
if re.match(r'^\s*/System/', line):
#print("system framework: %s" % line)
continue # System framework
elif re.match(r'^\s*/usr/lib/', line):
#print "unix style system lib"
#print("unix style system lib: %s" % line)
continue # unix style system library
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*@rpath', line) or re.match(r'^\s*@loader_path', line):
# Potentially already fixed library
relative_path = os.path.join(*line.split('/')[3:])
if not os.path.exists(os.path.join(frameworks_dir, relative_path)):
broken_libs['frameworks'].append(relative_path)
if line.count('/') == 1:
relative_path = os.path.join(*line.split('/')[1:])
if not os.path.exists(os.path.join(frameworks_dir, relative_path)):
broken_libs['libs'].append(relative_path)
elif line.count('/') == 2:
relative_path = os.path.join(*line.split('/')[2:])
if not os.path.exists(os.path.join(frameworks_dir, relative_path)):
broken_libs['libs'].append(relative_path)
elif line.count('/') >= 3:
relative_path = os.path.join(*line.split('/')[3:])
if not os.path.exists(os.path.join(frameworks_dir, relative_path)):
broken_libs['frameworks'].append(relative_path)
else:
print("GetBrokenLibraries Error: %s" % line)
elif re.search(r'\w+\.framework', line):
#print("framework: %s" % line)
broken_libs['frameworks'].append(line)
else:
broken_libs['libs'].append(line)
@@ -261,14 +269,21 @@ def FixFramework(path):
def FixLibrary(path):
if path in fixed_libraries or FindSystemLibrary(os.path.basename(path)) is not None:
if path in fixed_libraries:
return
else:
fixed_libraries.add(path)
# Always bundle libraries provided by homebrew (/usr/local).
if not re.match(r'^\s*/usr/local', path) and FindSystemLibrary(os.path.basename(path)) is not None:
return
fixed_libraries.add(path)
abs_path = FindLibrary(path)
if abs_path == "":
print "Could not resolve %s, not fixing!" % path
print("Could not resolve %s, not fixing!" % path)
return
broken_libs = GetBrokenLibraries(abs_path)
FixAllLibraries(broken_libs)
@@ -416,7 +431,7 @@ def FindSystemLibrary(library_name):
def FixLibraryInstallPath(library_path, library):
system_library = FindSystemLibrary(os.path.basename(library_path))
if system_library is None:
if system_library is None or re.match(r'^\s*/usr/local', library_path):
new_path = '@executable_path/../Frameworks/%s' % os.path.basename(library_path)
FixInstallPath(library_path, library, new_path)
else:
@@ -425,21 +440,14 @@ def FixLibraryInstallPath(library_path, library):
def FixFrameworkInstallPath(library_path, library):
parts = library_path.split(os.sep)
full_path = ""
for i, part in enumerate(parts):
if re.match(r'\w+\.framework', part):
full_path = os.path.join(*parts[i:])
break
new_path = '@executable_path/../Frameworks/%s' % full_path
FixInstallPath(library_path, library, new_path)
def FindXinePlugin(name):
for path in XINEPLUGIN_SEARCH_PATH:
if os.path.exists(path):
for dir, dirs, files in os.walk(path):
if name in files:
return os.path.join(dir, name)
raise CouldNotFindXinePluginError(name)
if full_path:
new_path = '@executable_path/../Frameworks/%s' % full_path
FixInstallPath(library_path, library, new_path)
def FindQtPlugin(name):
@@ -476,7 +484,7 @@ def main():
try:
FixPlugin('strawberry-tagreader', '.')
except:
print 'Failed to find blob: %s' % traceback.format_exc()
print('Failed to find blob: %s' % traceback.format_exc())
for plugin in GSTREAMER_PLUGINS:
FixPlugin(FindGstreamerPlugin(plugin), 'gstreamer')
@@ -489,11 +497,11 @@ def main():
#FixPlugin(FindQtPlugin(plugin), os.path.dirname(plugin))
if len(sys.argv) <= 2:
print 'Would run %d commands:' % len(commands)
print('Would run %d commands:' % len(commands))
for command in commands:
print ' '.join(command)
print(' '.join(command))
#print 'OK?'
#print('OK?')
#raw_input()
for command in commands:

14
dist/macos/macversion.sh vendored Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
macos_version=$(sw_vers -productVersion| awk -F '[.]' '{print $2}')
macos_codenames=(
["13"]="highsierra"
["14"]="mojave"
["15"]="catalina"
)
if [[ -n "${macos_codenames[$macos_version]}" ]]; then
echo "${macos_codenames[$macos_version]}"
else
echo "unknown"
fi

View File

@@ -38,7 +38,6 @@ tar -cJf $name-$version.tar.xz \
--exclude="$root/debian/changelog" \
--exclude="$root/dist/scripts/maketarball.sh" \
--exclude="$root/dist/unix/PKGBUILD" \
--exclude="$root/dist/macos/create-dmg.sh" \
--exclude="$root/dist/macos/Info.plist" \
--exclude="$root/dist/windows/windres.rc" \
--exclude="$root/src/translations/translations.pot" \

View File

@@ -16,85 +16,61 @@
# You should have received a copy of the GNU General Public License
# along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
sizes="128x128 64x64 48x48 32x32 22x22"
sizes="full 128 64 48 32 22"
#
#for i in full/*
#do
# source=$i
# file=`basename $i`
# id=`identify "$i"` || exit 1
# if [ "$id" = "" ] ; then
# echo "ERROR: Cannot determine format and geometry for image: \"$i\"."
# continue
# fi
# g=`echo $id | awk '{print $3}'` || exit 1
# if [ "$g" = "" ] ; then
# echo "ERROR: Cannot determine geometry for image: \"$i\"."
# continue
# fi
# Geometry can be 563x144+0+0 or 75x98
# we need to get rid of the plus (+) and the x characters:
# w=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $1}'` || exit 1
# if [ "$w" = "" ] ; then
# echo "ERROR: Cannot determine width for image: \"$x\"."
# continue
# fi
# h=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $2}'` || exit 1
# if [ "$h" = "" ] ; then
# echo "ERROR: Cannot determine height for image: \"$x\"."
# continue
# fi
# for x in $sizes
# do
# dest="$x/$file"
# if [ -f $dest ]; then
# continue
# fi
# x_w=$(echo $x | cut -d 'x' -f1)
# x_h=$(echo $x | cut -d 'x' -f2)
# if [ "$w" -lt "$x_w" ] || [ "$h" -lt "$x_h" ]; then
# continue
# fi
#echo "convert -verbose -resize $x $source $dest"
#convert -verbose -resize $x $source $dest
# done
#done
for i in $sizes
for s in $sizes
do
for x in $i/*
if [ "$s" = "full" ]; then
dir=$s
else
dir=${s}x${s}
fi
if ! [ -d "$dir" ]; then
echo "Missing $dir directory."
continue
fi
for f in ${dir}/*
do
file=`basename $x`
file=`basename $f`
for y in $sizes
do
if [ "$y" = "$i" ]; then
if [ "$s" = "$y" ]; then
continue
fi
if ! [ -f "$y/$file" ]; then
echo "Warning: $y/$file does not exist, but $x exists."
if [ "$y" = "full" ]; then
dir2=$y
else
dir2=${y}x${y}
fi
if [ "$dir2" = "full" ]; then
continue
fi
if ! [ "$s" = "full" ] && [ $y -gt $s ]; then
continue
fi
if ! [ -f "${dir2}/$file" ]; then
echo "Warning: ${dir2}/$file does not exist, but ${dir}/${file} exists."
fi
done
id=`identify "$x"` || exit 1
if [ "$dir" = "full" ]; then
continue
fi
id=`identify "$f"` || exit 1
if [ "$id" = "" ] ; then
echo "ERROR: Cannot determine format and geometry for image: \"$x\"."
echo "ERROR: Cannot determine format and geometry for image: \"$f\"."
continue
fi
g=`echo $id | awk '{print $3}'` || exit 1
if [ "$g" = "" ] ; then
echo "ERROR: Cannot determine geometry for image: \"$x\"."
echo "ERROR: Cannot determine geometry for image: \"$f\"."
continue
fi
@@ -102,17 +78,17 @@ do
# we need to get rid of the plus (+) and the x characters:
w=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $1}'` || exit 1
if [ "$w" = "" ] ; then
echo "ERROR: Cannot determine width for image: \"$x\"."
echo "ERROR: Cannot determine width for image: \"$f\"."
continue
fi
h=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $2}'` || exit 1
if [ "$h" = "" ] ; then
echo "ERROR: Cannot determine height for image: \"$x\"."
echo "ERROR: Cannot determine height for image: \"$f\"."
continue
fi
if ! [ "${h}x${w}" = "$i" ]; then
echo "Warning: $x is not $i, but ${h}x${w}!"
if ! [ "${h}x${w}" = "$dir" ]; then
echo "Warning: $f is not $dir, but ${h}x${w}!"
fi
done

View File

@@ -23,15 +23,11 @@ depends=(
gstreamer
gst-plugins-base
gst-plugins-good
xine-lib
vlc
chromaprint
libgpod
libcdio
libmtp
libusbmuxd
libplist
libimobiledevice
fftw
)
optdepends=(

View File

@@ -12,3 +12,28 @@ Categories=AudioVideo;Player;Qt;Audio;
StartupNotify=false
MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf;x-scheme-handler/tidal;
StartupWMClass=strawberry
Actions=Play;Pause;Stop;StopAfterCurrent;Previous;Next;
[Desktop Action Play]
Name=Play
Exec=strawberry --play
[Desktop Action Pause]
Name=Pause
Exec=strawberry --pause
[Desktop Action Stop]
Name=Stop
Exec=strawberry --stop
[Desktop Action StopAfterCurrent]
Name=Stop after this track
Exec=strawberry --stop-after-current
[Desktop Action Previous]
Name=Previous
Exec=strawberry --previous
[Desktop Action Next]
Name=Next
Exec=strawberry --next

View File

@@ -1,7 +1,7 @@
Name: strawberry
Version: @STRAWBERRY_VERSION_RPM_V@
Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@
Summary: A music player and music collection organiser
Summary: A music player and music collection organizer
Group: Applications/Multimedia
License: GPL-3.0+
URL: https://www.strawberrymusicplayer.org/
@@ -73,7 +73,6 @@ BuildRequires: pkgconfig(libmtp)
BuildRequires: pkgconfig(libnotify)
BuildRequires: pkgconfig(libudf)
%if 0%{?suse_version} || 0%{?fedora_version}
BuildRequires: pkgconfig(libxine)
BuildRequires: pkgconfig(libvlc)
%endif
@@ -81,13 +80,6 @@ BuildRequires: pkgconfig(libvlc)
Requires: libQt5Sql5-sqlite
%endif
%if 0%{?suse_version} && 0%{?suse_version} < 1500
Requires(post): update-desktop-files
Requires(post): gtk3-tools
Requires(postun): update-desktop-files
Requires(postun): gtk3-tools
%endif
%description
Strawberry is a music player and music collection organizer.
It is a fork of Clementine. The name is inspired by the band Strawbs.
@@ -107,7 +99,7 @@ Features:
- Support for multiple backends
- Audio analyzer
- Audio equalizer
- Transfer music to iPod, iPhone, MTP or mass-storage USB player
- Transfer music to iPod, MTP or mass-storage USB player
- Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
- Streaming support for Subsonic
@@ -115,62 +107,34 @@ Features:
%setup -qn %{name}-@STRAWBERRY_VERSION_PACKAGE@
%build
%if 0%{?suse_version} || 0%{?mageia}
%{cmake} ..
%{cmake} -DCMAKE_BUILD_TYPE:STRING=Release
%if 0%{?centos} || 0%{?mageia}
%make_build
%else
mkdir -p %{_target_platform}
pushd %{_target_platform}
%{cmake} ..
popd
%endif
%if 0%{?suse_version} || 0%{?mageia}
%if 0%{?suse_version} && 0%{?suse_version} < 1500
make %{?_smp_mflags}
%else
%make_build
%endif
%else
%make_build -C %{_target_platform}
%cmake_build
%endif
%install
%if 0%{?suse_version}
%cmake_install
%if 0%{?centos}
%make_install
%else
%if 0%{?mageia}
%make_install -C build
%else
%make_install -C %{_target_platform}
%endif
%if 0%{?mageia}
%make_install -C build
%else
%cmake_install
%endif
%if 0%{?suse_version} && 0%{?suse_version} < 1500
rm -f %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
%endif
%if 0%{?suse_version}
%suse_update_desktop_file org.strawberrymusicplayer.strawberry Qt AudioVideo Audio Player
%endif
%if 0%{?suse_version} && 0%{?suse_version} < 1500
%post
%desktop_database_post
%icon_theme_cache_post
%postun
%desktop_database_postun
%icon_theme_cache_postun
%endif
%check
desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicplayer.strawberry.desktop
%if 0%{?fedora_version}
appstream-util validate-relax --nonet %{buildroot}%{_metainfodir}/org.strawberrymusicplayer.strawberry.appdata.xml
%else
%if ! 0%{?suse_version} || ( 0%{?suse_version} && 0%{?suse_version} >= 1500 )
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
%endif
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
%endif
%files
@@ -184,9 +148,7 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicpl
%if 0%{?fedora_version}
%{_metainfodir}/*.appdata.xml
%else
%if ! 0%{?suse_version} || ( 0%{?suse_version} && 0%{?suse_version} >= 1500 )
%{_datadir}/metainfo/*.appdata.xml
%endif
%{_datadir}/metainfo/*.appdata.xml
%endif
%{_mandir}/man1/%{name}.1.*
%{_mandir}/man1/%{name}-tagreader.1.*

View File

@@ -18,6 +18,10 @@
!define debug
!endif
!if "@BUILD_WITH_QT6@" == "ON"
!define with_qt6
!endif
!ifdef debug
!define PRODUCT_NAME "Strawberry Music Player Debug"
!define PRODUCT_NAME_SHORT "Strawberry"
@@ -91,17 +95,33 @@ SetCompressor /SOLID lzma
Name "${PRODUCT_NAME}"
!ifdef arch_x86
!ifdef debug
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x86.exe"
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x86.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x86.exe"
!endif
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x86.exe"
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x86.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x86.exe"
!endif
!endif
!endif
!ifdef arch_x64
!ifdef debug
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x64.exe"
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x64.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x64.exe"
!endif
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x64.exe"
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x64.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x64.exe"
!endif
!endif
!endif
@@ -154,7 +174,27 @@ Section "Strawberry" Strawberry
File "strawberry.exe"
File "strawberry-tagreader.exe"
File "strawberry.ico"
File "sqlite3.exe"
!ifdef arch_x86
File "libgcc_s_sjlj-1.dll"
File "libcrypto-1_1.dll"
File "libssl-1_1.dll"
!endif
!ifdef arch_x64
File "libgcc_s_seh-1.dll"
File "libcrypto-1_1-x64.dll"
File "libssl-1_1-x64.dll"
!endif
File "avcodec-58.dll"
File "avfilter-7.dll"
File "avformat-58.dll"
File "avresample-4.dll"
File "avutil-56.dll"
File "libbrotlicommon.dll"
File "libbrotlidec.dll"
File "libbz2.dll"
File "libcdio-19.dll"
File "libchromaprint.dll"
@@ -194,21 +234,41 @@ Section "Strawberry" Strawberry
File "libnettle-8.dll"
File "libogg-0.dll"
File "libopus-0.dll"
File "liborc-0.4-0.dll"
File "libpcre-1.dll"
File "libpcre2-16-0.dll"
File "libpng16-16.dll"
File "libprotobuf-23.dll"
File "libprotobuf-24.dll"
File "libpsl-5.dll"
File "libsoup-2.4-1.dll"
File "libspeex-1.dll"
File "libsqlite3-0.dll"
File "libssp-0.dll"
File "libstdc++-6.dll"
File "libtag.dll"
File "libtasn1-6.dll"
File "libunistring-2.dll"
File "libvorbis-0.dll"
File "libvorbisenc-2.dll"
File "libwavpack-1.dll"
File "libwinpthread-1.dll"
File "libxml2-2.dll"
File "libzstd.dll"
File "postproc-55.dll"
File "swresample-3.dll"
File "swscale-5.dll"
File "zlib1.dll"
!ifdef with_qt6
File "Qt6Concurrent.dll"
File "Qt6Core.dll"
File "Qt6Gui.dll"
File "Qt6Network.dll"
File "Qt6Sql.dll"
File "Qt6Widgets.dll"
File "Qt6WinExtras.dll"
File "libqtsparkle-qt6.dll"
!else
File "Qt5Concurrent.dll"
File "Qt5Core.dll"
File "Qt5Gui.dll"
@@ -216,24 +276,17 @@ Section "Strawberry" Strawberry
File "Qt5Sql.dll"
File "Qt5Widgets.dll"
File "Qt5WinExtras.dll"
File "zlib1.dll"
File "libzstd.dll"
File "libtasn1-6.dll"
File "libbrotlicommon.dll"
File "libbrotlidec.dll"
File "libpsl-5.dll"
File "liborc-0.4-0.dll"
!ifdef arch_x86
File "libgcc_s_sjlj-1.dll"
File "libcrypto-1_1.dll"
File "libssl-1_1.dll"
File "libqtsparkle-qt5.dll"
!endif
!ifdef arch_x64
File "libgcc_s_seh-1.dll"
File "libcrypto-1_1-x64.dll"
File "libssl-1_1-x64.dll"
!ifdef debug
File "gdb.exe"
File "libdl.dll"
File "libexpat-1.dll"
File "libmman.dll"
File "libmpfr-6.dll"
File "libreadline8.dll"
File "libtermcap.dll"
!endif
File "killproc.exe"
@@ -318,7 +371,7 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgsttypefindfunctions.dll" "gstreamer-plugins\libgsttypefindfunctions.dll"
File "/oname=libgstgio.dll" "gstreamer-plugins\libgstgio.dll"
File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.dll"
;File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
File "/oname=libgsticydemux.dll" "gstreamer-plugins\libgsticydemux.dll"
File "/oname=libgstid3demux.dll" "gstreamer-plugins\libgstid3demux.dll"
@@ -359,6 +412,12 @@ Section "Start menu items" startmenu
SectionEnd
Section "Tidal URL Scheme"
WriteRegStr HKCR "tidal" "URL Protocol" ""
WriteRegStr HKCR "tidal" "" "URL:tidal"
WriteRegStr HKCR 'tidal\shell\open\command' '' '"${PRODUCT_INSTALL_DIR}\strawberry.exe" "%1"'
SectionEnd
Section "Uninstaller"
; Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
@@ -383,7 +442,27 @@ Section "Uninstall"
Delete "$INSTDIR\strawberry.ico"
Delete "$INSTDIR\strawberry.exe"
Delete "$INSTDIR\strawberry-tagreader.exe"
Delete "$INSTDIR\sqlite3.exe"
!ifdef arch_x86
Delete "$INSTDIR\libgcc_s_sjlj-1.dll"
Delete "$INSTDIR\libcrypto-1_1.dll"
Delete "$INSTDIR\libssl-1_1.dll"
!endif
!ifdef arch_x64
Delete "$INSTDIR\libgcc_s_seh-1.dll"
Delete "$INSTDIR\libcrypto-1_1-x64.dll"
Delete "$INSTDIR\libssl-1_1-x64.dll"
!endif
Delete "$INSTDIR\avcodec-58.dll"
Delete "$INSTDIR\avfilter-7.dll"
Delete "$INSTDIR\avformat-58.dll"
Delete "$INSTDIR\avresample-4.dll"
Delete "$INSTDIR\avutil-56.dll"
Delete "$INSTDIR\libbrotlicommon.dll"
Delete "$INSTDIR\libbrotlidec.dll"
Delete "$INSTDIR\libbz2.dll"
Delete "$INSTDIR\libcdio-19.dll"
Delete "$INSTDIR\libchromaprint.dll"
@@ -423,21 +502,29 @@ Section "Uninstall"
Delete "$INSTDIR\libnettle-8.dll"
Delete "$INSTDIR\libogg-0.dll"
Delete "$INSTDIR\libopus-0.dll"
Delete "$INSTDIR\liborc-0.4-0.dll"
Delete "$INSTDIR\libpcre-1.dll"
Delete "$INSTDIR\libpcre2-16-0.dll"
Delete "$INSTDIR\libpng16-16.dll"
Delete "$INSTDIR\libprotobuf-23.dll"
Delete "$INSTDIR\libprotobuf-24.dll"
Delete "$INSTDIR\libpsl-5.dll"
Delete "$INSTDIR\libqtsparkle-qt5.dll"
Delete "$INSTDIR\libqtsparkle-qt6.dll"
Delete "$INSTDIR\libsoup-2.4-1.dll"
Delete "$INSTDIR\libspeex-1.dll"
Delete "$INSTDIR\libsqlite3-0.dll"
Delete "$INSTDIR\libssp-0.dll"
Delete "$INSTDIR\libstdc++-6.dll"
Delete "$INSTDIR\libtag.dll"
Delete "$INSTDIR\libtasn1-6.dll"
Delete "$INSTDIR\libunistring-2.dll"
Delete "$INSTDIR\libvorbis-0.dll"
Delete "$INSTDIR\libvorbisenc-2.dll"
Delete "$INSTDIR\libwavpack-1.dll"
Delete "$INSTDIR\libwinpthread-1.dll"
Delete "$INSTDIR\libxml2-2.dll"
Delete "$INSTDIR\libzstd.dll"
Delete "$INSTDIR\postproc-55.dll"
Delete "$INSTDIR\Qt5Concurrent.dll"
Delete "$INSTDIR\Qt5Core.dll"
Delete "$INSTDIR\Qt5Gui.dll"
@@ -445,24 +532,25 @@ Section "Uninstall"
Delete "$INSTDIR\Qt5Sql.dll"
Delete "$INSTDIR\Qt5Widgets.dll"
Delete "$INSTDIR\Qt5WinExtras.dll"
Delete "$INSTDIR\Qt6Concurrent.dll"
Delete "$INSTDIR\Qt6Core.dll"
Delete "$INSTDIR\Qt6Gui.dll"
Delete "$INSTDIR\Qt6Network.dll"
Delete "$INSTDIR\Qt6Sql.dll"
Delete "$INSTDIR\Qt6Widgets.dll"
Delete "$INSTDIR\Qt6WinExtras.dll"
Delete "$INSTDIR\swresample-3.dll"
Delete "$INSTDIR\swscale-5.dll"
Delete "$INSTDIR\zlib1.dll"
Delete "$INSTDIR\libzstd.dll"
Delete "$INSTDIR\libtasn1-6.dll"
Delete "$INSTDIR\libbrotlicommon.dll"
Delete "$INSTDIR\libbrotlidec.dll"
Delete "$INSTDIR\libpsl-5.dll"
Delete "$INSTDIR\liborc-0.4-0.dll"
!ifdef arch_x86
Delete "$INSTDIR\libgcc_s_sjlj-1.dll"
Delete "$INSTDIR\libcrypto-1_1.dll"
Delete "$INSTDIR\libssl-1_1.dll"
!endif
!ifdef arch_x64
Delete "$INSTDIR\libgcc_s_seh-1.dll"
Delete "$INSTDIR\libcrypto-1_1-x64.dll"
Delete "$INSTDIR\libssl-1_1-x64.dll"
!ifdef debug
Delete "$INSTDIR\gdb.exe"
Delete "$INSTDIR\libdl.dll"
Delete "$INSTDIR\libexpat-1.dll"
Delete "$INSTDIR\libmman.dll"
Delete "$INSTDIR\libmpfr-6.dll"
Delete "$INSTDIR\libreadline8.dll"
Delete "$INSTDIR\libtermcap.dll"
!endif
Delete "$INSTDIR\gio-modules\libgiognutls.dll"

View File

@@ -9,7 +9,7 @@ link_directories(
${GSTREAMER_BASE_LIBRARY_DIRS}
${GSTREAMER_AUDIO_LIBRARY_DIRS}
${FFTW3_LIBRARY_DIRS}
${Qt5Core_LIBRARY_DIRS}
${QtCore_LIBRARY_DIRS}
)
add_library(gstmoodbar STATIC ${SOURCES})
@@ -21,7 +21,7 @@ target_include_directories(gstmoodbar SYSTEM PRIVATE
${GSTREAMER_BASE_INCLUDE_DIRS}
${GSTREAMER_AUDIO_INCLUDE_DIRS}
${FFTW3_INCLUDE_DIR}
${Qt5Core_INCLUDE_DIRS}
${QtCore_INCLUDE_DIRS}
)
target_include_directories(gstmoodbar PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
@@ -33,5 +33,5 @@ target_link_libraries(gstmoodbar PRIVATE
${GSTREAMER_BASE_LIBRARIES}
${GSTREAMER_AUDIO_LIBRARIES}
${FFTW3_FFTW_LIBRARY}
${Qt5Core_LIBRARIES}
${QtCore_LIBRARIES}
)

View File

@@ -115,7 +115,7 @@ static void gst_fastspectrum_init (GstFastSpectrum * spectrum) {
spectrum->interval = DEFAULT_INTERVAL;
spectrum->bands = DEFAULT_BANDS;
spectrum->channel_data_initialised = false;
spectrum->channel_data_initialized = false;
g_mutex_init (&spectrum->lock);
@@ -137,14 +137,14 @@ static void gst_fastspectrum_alloc_channel_data (GstFastSpectrum * spectrum) {
QMutexLocker l(klass->fftw_lock);
spectrum->plan = fftw_plan_dft_r2c_1d(nfft, spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
}
spectrum->channel_data_initialised = true;
spectrum->channel_data_initialized = true;
}
static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
GstFastSpectrumClass* klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
if (spectrum->channel_data_initialised) {
if (spectrum->channel_data_initialized) {
{
QMutexLocker l(klass->fftw_lock);
fftw_destroy_plan(spectrum->plan);
@@ -154,7 +154,7 @@ static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
delete[] spectrum->input_ring_buffer;
delete[] spectrum->spect_magnitude;
spectrum->channel_data_initialised = false;
spectrum->channel_data_initialized = false;
}
}
@@ -179,7 +179,7 @@ static void gst_fastspectrum_reset_state (GstFastSpectrum * spectrum) {
static void gst_fastspectrum_finalize (GObject * object) {
GstFastSpectrum *spectrum = GST_FASTSPECTRUM (object);
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(object);
gst_fastspectrum_reset_state (spectrum);
g_mutex_clear (&spectrum->lock);
@@ -190,7 +190,7 @@ static void gst_fastspectrum_finalize (GObject * object) {
static void gst_fastspectrum_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) {
GstFastSpectrum *filter = GST_FASTSPECTRUM (object);
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
switch (prop_id) {
case PROP_INTERVAL:{
@@ -222,7 +222,7 @@ static void gst_fastspectrum_set_property (GObject * object, guint prop_id, cons
static void gst_fastspectrum_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) {
GstFastSpectrum *filter = GST_FASTSPECTRUM (object);
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
switch (prop_id) {
case PROP_INTERVAL:
@@ -240,7 +240,7 @@ static void gst_fastspectrum_get_property (GObject * object, guint prop_id, GVal
static gboolean gst_fastspectrum_start (GstBaseTransform * trans) {
GstFastSpectrum *spectrum = GST_FASTSPECTRUM (trans);
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
gst_fastspectrum_reset_state (spectrum);
@@ -250,7 +250,7 @@ static gboolean gst_fastspectrum_start (GstBaseTransform * trans) {
static gboolean gst_fastspectrum_stop (GstBaseTransform * trans) {
GstFastSpectrum *spectrum = GST_FASTSPECTRUM (trans);
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
gst_fastspectrum_reset_state (spectrum);
@@ -334,7 +334,7 @@ static void input_data_mixed_int16_max (const guint8 * _in, double * out, guint
static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInfo * info) {
GstFastSpectrum *spectrum = GST_FASTSPECTRUM (base);
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(base);
GstFastSpectrumInputData input_data = nullptr;
g_mutex_lock (&spectrum->lock);
@@ -392,7 +392,7 @@ static void gst_fastspectrum_run_fft (GstFastSpectrum * spectrum, guint input_po
static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, GstBuffer *buffer) {
GstFastSpectrum *spectrum = GST_FASTSPECTRUM (trans);
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
guint rate = GST_AUDIO_FILTER_RATE (spectrum);
guint bps = GST_AUDIO_FILTER_BPS (spectrum);
guint bpf = GST_AUDIO_FILTER_BPF (spectrum);
@@ -422,7 +422,7 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
/* If we don't have a FFT context yet (or it was reset due to parameter
* changes) get one and allocate memory for everything
*/
if (!spectrum->channel_data_initialised) {
if (!spectrum->channel_data_initialized) {
GST_DEBUG_OBJECT (spectrum, "allocating for bands %u", bands);
gst_fastspectrum_alloc_channel_data (spectrum);

View File

@@ -65,7 +65,7 @@ struct GstFastSpectrum {
GstClockTime message_ts; /* starttime for next message */
/* <private> */
bool channel_data_initialised;
bool channel_data_initialized;
double* input_ring_buffer;
double* fft_input;
fftw_complex* fft_output;

View File

@@ -20,20 +20,24 @@ if(APPLE)
list(APPEND SOURCES core/scoped_nsautorelease_pool.mm)
endif(APPLE)
qt5_wrap_cpp(MOC ${HEADERS})
if(BUILD_WITH_QT6)
qt6_wrap_cpp(MOC ${HEADERS})
else()
qt5_wrap_cpp(MOC ${HEADERS})
endif()
link_directories(
${GLIB_LIBRARY_DIRS}
${Qt5Core_LIBRARY_DIRS}
${Qt5Network_LIBRARY_DIRS}
${QtCore_LIBRARY_DIRS}
${QtNetwork_LIBRARY_DIRS}
)
add_library(libstrawberry-common STATIC ${SOURCES} ${MOC})
target_include_directories(libstrawberry-common SYSTEM PRIVATE
${GLIB_INCLUDE_DIRS}
${Qt5Core_INCLUDE_DIRS}
${Qt5Network_INCLUDE_DIRS}
${QtCore_INCLUDE_DIRS}
${QtNetwork_INCLUDE_DIRS}
)
target_include_directories(libstrawberry-common PRIVATE
@@ -46,8 +50,8 @@ target_include_directories(libstrawberry-common PRIVATE
target_link_libraries(libstrawberry-common PRIVATE
${CMAKE_THREAD_LIBS_INIT}
${GLIB_LIBRARIES}
${Qt5Core_LIBRARIES}
${Qt5Network_LIBRARIES}
${QtCore_LIBRARIES}
${QtNetwork_LIBRARIES}
)
if(Backtrace_FOUND)

View File

@@ -1,123 +0,0 @@
/* This file is part of Strawberry.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CONCURRENTRUN_H
#define CONCURRENTRUN_H
#include <functional>
#include <QFuture>
#include <QRunnable>
#include <QThreadPool>
/*
The aim of ThreadFunctor classes and ConcurrentRun::Run() functions is to
complete QtConcurrentRun, which lack support for using a particular
QThreadPool, as it always uses QThreadPool::globalInstance().
This is problematic when we do not want to share the same thread pool over
all the application, but want to keep the convenient QtConcurrent::run()
functor syntax.
With ConcurrentRun::Run(), time critical changes can be performed in their
own pool, which is not empty by other actions (as it happens when using
QtConcurrentRun::run()).
ThreadFunctor classes are used to store a functor and its arguments, and
Run() functions are used for convenience: to directly create a new
ThreadFunctor object and start it.
*/
/*
Base abstract classes ThreadFunctorBase and ThreadFunctor (for void and
non-void result):
*/
template<typename ReturnType>
class ThreadFunctorBase : public QFutureInterface<ReturnType>, public QRunnable {
public:
ThreadFunctorBase() {}
QFuture<ReturnType> Start(QThreadPool* thread_pool) {
this->setRunnable(this);
this->reportStarted();
Q_ASSERT(thread_pool);
QFuture<ReturnType> future = this->future();
thread_pool->start(this, 0 /* priority: currently we do not support changing the priority. Might be added later if needed */);
return future;
}
void run() override = 0;
};
template <typename ReturnType, typename... Args>
class ThreadFunctor : public ThreadFunctorBase<ReturnType> {
public:
explicit ThreadFunctor(std::function<ReturnType (Args...)> function, Args... args)
: function_(std::bind(function, args...)) {
}
void run() override {
this->reportResult(function_());
this->reportFinished();
}
private:
std::function<ReturnType()> function_;
};
// Partial specialisation for void return type.
template <typename... Args>
class ThreadFunctor <void, Args...> : public ThreadFunctorBase<void> {
public:
explicit ThreadFunctor(std::function<void (Args...)> function, Args... args)
: function_(std::bind(function, args...)) {
}
void run() override {
function_();
this->reportFinished();
}
private:
std::function<void()> function_;
};
/*
Run functions
*/
namespace ConcurrentRun {
// Empty argument form.
template <typename ReturnType>
QFuture<ReturnType> Run(QThreadPool* threadpool, std::function<ReturnType ()> function) {
return (new ThreadFunctor<ReturnType>(function))->Start(threadpool);
}
// Function object with arguments form.
template <typename ReturnType, typename... Args>
QFuture<ReturnType> Run(QThreadPool* threadpool, std::function<ReturnType (Args...)> function, const Args&... args) {
return (new ThreadFunctor<ReturnType, Args...>(function, args...))->Start(threadpool);
}
// Support passing C function pointers instead of function objects.
template <typename ReturnType, typename... Args>
QFuture<ReturnType> Run(QThreadPool* threadpool, ReturnType (*function) (Args...), const Args&... args) {
return Run(threadpool, std::function<ReturnType (Args...)>(function), args...);
}
}
#endif // CONCURRENTRUN_H

View File

@@ -21,7 +21,7 @@
#include <functional>
#include <memory>
// Helper for lazy initialisation of objects.
// Helper for lazy initialization of objects.
// Usage:
// Lazy<Foo> my_lazy_object([]() { return new Foo; });
@@ -34,33 +34,32 @@ class Lazy {
Lazy() : init_([]() { return new T; }) {}
T* get() const {
CheckInitialised();
CheckInitialized();
return ptr_.get();
}
typename std::add_lvalue_reference<T>::type operator*() const {
CheckInitialised();
CheckInitialized();
return *ptr_;
}
T* operator->() const { return get(); }
// Returns true if the object is not yet initialised.
// Returns true if the object is not yet initialized.
explicit operator bool() const { return ptr_; }
// Deletes the underlying object and will re-run the initialisation function
// if the object is requested again.
void reset() { ptr_.reset(nullptr); }
// Deletes the underlying object and will re-run the initialization function if the object is requested again.
void reset() { ptr_.reset(); }
private:
void CheckInitialised() const {
void CheckInitialized() const {
if (!ptr_) {
ptr_.reset(init_());
ptr_.reset(init_(), [](T*obj) { obj->deleteLater(); });
}
}
const std::function<T*()> init_;
mutable std::unique_ptr<T> ptr_;
mutable std::shared_ptr<T> ptr_;
};
#endif // LAZY_H

View File

@@ -35,7 +35,8 @@
#include <QMap>
#include <QString>
#include <QStringList>
#include <QRegExp>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QDateTime>
#include <QIODevice>
#include <QBuffer>
@@ -306,11 +307,12 @@ QString LinuxDemangle(const QString &symbol);
QString LinuxDemangle(const QString &symbol) {
QRegExp regex("\\(([^+]+)");
if (!symbol.contains(regex)) {
QRegularExpression regex("\\(([^+]+)");
QRegularExpressionMatch match = regex.match(symbol);
if (!match.hasMatch()) {
return symbol;
}
QString mangled_function = regex.cap(1);
QString mangled_function = match.captured(1);
return CXXDemangle(mangled_function);
}

View File

@@ -102,10 +102,10 @@ void _MessageHandlerBase::WriteMessage(const QByteArray &data) {
// Sorry.
if (flush_abstract_socket_) {
((static_cast<QAbstractSocket*>(device_))->*(flush_abstract_socket_))();
((qobject_cast<QAbstractSocket*>(device_))->*(flush_abstract_socket_))();
}
else if (flush_local_socket_) {
((static_cast<QLocalSocket*>(device_))->*(flush_local_socket_))();
((qobject_cast<QLocalSocket*>(device_))->*(flush_local_socket_))();
}
}

View File

@@ -1,33 +0,0 @@
/* This file is part of Strawberry.
Copyright 2012, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef OVERRIDE_H
#define OVERRIDE_H
// Defines the OVERRIDE macro as C++11's override control keyword if
// it is available.
#ifndef __has_extension
#define __has_extension(x) 0
#endif
#if __has_extension(cxx_override_control) // Clang feature checking macro.
# define OVERRIDE override
#else
# define OVERRIDE
#endif
#endif // OVERRIDE_H

View File

@@ -172,7 +172,7 @@ template <typename HandlerType>
WorkerPool<HandlerType>::~WorkerPool() {
for (const Worker &worker : workers_) {
if (worker.local_socket_ && worker.process_) {
disconnect(worker.process_, SIGNAL(error(QProcess::ProcessError)), this, SLOT(ProcessError(QProcess::ProcessError)));
disconnect(worker.process_, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(ProcessError(QProcess::ProcessError)));
// The worker is connected. Close his socket and wait for him to exit.
qLog(Debug) << "Closing worker socket";
@@ -263,7 +263,7 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
worker->process_ = new QProcess(this);
connect(worker->local_server_, SIGNAL(newConnection()), SLOT(NewConnection()));
connect(worker->process_, SIGNAL(error(QProcess::ProcessError)), SLOT(ProcessError(QProcess::ProcessError)));
connect(worker->process_, SIGNAL(errorOccurred(QProcess::ProcessError)), SLOT(ProcessError(QProcess::ProcessError)));
// Create a server, find an unused name and start listening
forever {

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.0)
set(MESSAGES tagreadermessages.proto)
set(SOURCES fmpsparser.cpp tagreader.cpp)
set(SOURCES tagreader.cpp)
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
@@ -17,8 +17,8 @@ add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE
${GLIB_INCLUDE_DIRS}
${PROTOBUF_INCLUDE_DIRS}
${Qt5Core_INCLUDE_DIRS}
${Qt5Network_INCLUDE_DIRS}
${QtCore_INCLUDE_DIRS}
${QtNetwork_INCLUDE_DIRS}
)
target_include_directories(libstrawberry-tagreader PRIVATE
@@ -34,6 +34,11 @@ target_link_libraries(libstrawberry-tagreader PRIVATE
${GLIB_LIBRARIES}
${PROTOBUF_LIBRARY}
${TAGLIB_LIBRARIES}
${Qt5Core_LIBRARIES}
${QtCore_LIBRARIES}
${QtNetwork_LIBRARIES}
libstrawberry-common
)
if(BUILD_WITH_QT6)
target_link_libraries(libstrawberry-tagreader PRIVATE Qt6::Core5Compat)
endif()

View File

@@ -1,130 +0,0 @@
/* This file is part of Strawberry.
Copyright 2010, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <functional>
#include <QList>
#include <QVariant>
#include <QString>
#include <QChar>
#include "fmpsparser.h"
using std::placeholders::_1;
using std::placeholders::_2;
FMPSParser::FMPSParser() :
// The float regex ends with (?:$|(?=::|;;)) to ensure it matches all the way
// up to the end of the value. Without it, it would match a string that
// starts with a number, like "123abc".
float_re_("\\s*([+-]?\\d+(?:\\.\\d+)?)\\s*(?:$|(?=::|;;))"),
// Matches any character except unescaped slashes, colons and semicolons.
string_re_("((?:[^\\\\;:]|(?:\\\\[\\\\:;]))+)(?:$|(?=::|;;))"),
// Used for replacing escaped characters.
escape_re_("\\\\([\\\\:;])") {}
// Parses a list of things (of type T) that are separated by two consecutive
// Separator characters. Each individual thing is parsed by the F function.
// For example, to parse this data:
// foo::bar::baz
// Use:
// QVariantList ret;
// ParseContainer<':'>(data, ParseValue, &ret);
// ret will then contain "foo", "bar", and "baz".
// Returns the number of characters that were consumed from data.
//
// You can parse lists of lists by using different separator characters:
// ParseContainer<';'>(data, ParseContainer<':'>, &ret);
template <char Separator, typename F, typename T>
static int ParseContainer(const QStringRef& data, F f, QList<T>* ret) {
ret->clear();
T value;
int pos = 0;
while (pos < data.length()) {
const int len = data.length() - pos;
int matched_len = f(QStringRef(data.string(), data.position() + pos, len), &value);
if (matched_len == -1 || matched_len > len)
break;
ret->append(value);
pos += matched_len;
// Expect two separators in a row
if (pos + 2 <= data.length() && data.at(pos) == Separator && data.at(pos+1) == Separator) {
pos += 2;
} else {
break;
}
}
return pos;
}
bool FMPSParser::Parse(const QString &data) {
result_ = Result();
// Only return success if we matched the whole string
return ParseListList(data, &result_) == data.length();
}
int FMPSParser::ParseValueRef(const QStringRef& data, QVariant* ret) const {
// Try to match a float
int pos = float_re_.indexIn(*data.string(), data.position());
if (pos == data.position()) {
*ret = float_re_.cap(1).toDouble();
return float_re_.matchedLength();
}
// Otherwise try to match a string
pos = string_re_.indexIn(*data.string(), data.position());
if (pos == data.position()) {
// Replace escape sequences with their actual characters
QString value = string_re_.cap(1);
value.replace(escape_re_, "\\1");
*ret = value;
return string_re_.matchedLength();
}
return -1;
}
// Parses an inner list - a list of values
int FMPSParser::ParseListRef(const QStringRef &data, QVariantList *ret) const {
return ParseContainer<':'>(data, std::bind(&FMPSParser::ParseValueRef, this, _1, _2), ret);
}
// Parses an outer list - a list of lists
int FMPSParser::ParseListListRef(const QStringRef &data, Result *ret) const {
return ParseContainer<';'>(data, std::bind(&FMPSParser::ParseListRef, this, _1, _2), ret);
}
// Convenience functions that take QStrings instead of QStringRefs. Use the QStringRef versions if possible, they're faster.
int FMPSParser::ParseValue(const QString &data, QVariant *ret) const {
return ParseValueRef(QStringRef(&data), ret);
}
int FMPSParser::ParseList(const QString &data, QVariantList *ret) const {
return ParseListRef(QStringRef(&data), ret);
}
int FMPSParser::ParseListList(const QString &data, Result *ret) const {
return ParseListListRef(QStringRef(&data), ret);
}

View File

@@ -1,64 +0,0 @@
/* This file is part of Strawberry.
Copyright 2010, David Sansome <me@davidsansome.com>
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FMPSPARSER_H
#define FMPSPARSER_H
#include "config.h"
#include <QList>
#include <QMetaType>
#include <QString>
#include <QRegExp>
class QVariant;
class FMPSParser {
public:
FMPSParser();
// A FMPS result is a list of lists of values (where a value is a string or
// a float).
typedef QList<QVariantList> Result;
// Parses a FMPS value and returns true on success.
bool Parse(const QString &data);
// Gets the result of the last successful Parse.
Result result() const { return result_; }
// Returns true if result() is empty.
bool is_empty() const { return result().isEmpty() || result()[0].isEmpty(); }
// Internal functions, public for unit tests
int ParseValue(const QString &data, QVariant *ret) const;
int ParseValueRef(const QStringRef &data, QVariant *ret) const;
int ParseList(const QString &data, QVariantList *ret) const;
int ParseListRef(const QStringRef &data, QVariantList *ret) const;
int ParseListList(const QString &data, Result *ret) const;
int ParseListListRef(const QStringRef &data, Result *ret) const;
private:
QRegExp float_re_;
QRegExp string_re_;
QRegExp escape_re_;
Result result_;
};
#endif // FMPSPARSER_H

View File

@@ -90,8 +90,6 @@
#include "core/logging.h"
#include "core/messagehandler.h"
#include "fmpsparser.h"
#include "core/timeconstants.h"
class FileRefFactory {
@@ -177,11 +175,11 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
song->set_basefilename(DataCommaSizeFromQString(info.fileName()));
song->set_url(url.constData(), url.size());
song->set_filesize(info.size());
song->set_mtime(info.lastModified().toTime_t());
song->set_mtime(info.lastModified().toSecsSinceEpoch());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
song->set_ctime(info.birthTime().isValid() ? info.birthTime().toTime_t() : info.lastModified().toTime_t());
song->set_ctime(info.birthTime().isValid() ? info.birthTime().toSecsSinceEpoch() : info.lastModified().toSecsSinceEpoch());
#else
song->set_ctime(info.created().toTime_t());
song->set_ctime(info.created().toSecsSinceEpoch());
#endif
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
@@ -237,14 +235,14 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
else if (TagLib::WavPack::File *file_wavpack = dynamic_cast<TagLib::WavPack::File *>(fileref->file())) {
song->set_bitdepth(file_wavpack->audioProperties()->bitsPerSample());
if (file_wavpack->tag()) {
if (file_wavpack->APETag()) {
ParseAPETag(file_wavpack->APETag()->itemListMap(), nullptr, &disc, &compilation, song);
}
if (tag) Decode(tag->comment(), nullptr, song->mutable_comment());
}
else if (TagLib::APE::File *file_ape = dynamic_cast<TagLib::APE::File*>(fileref->file())) {
if (file_ape->tag()) {
if (file_ape->APETag()) {
ParseAPETag(file_ape->APETag()->itemListMap(), nullptr, &disc, &compilation, song);
}
song->set_bitdepth(file_ape->audioProperties()->bitsPerSample());
@@ -300,15 +298,6 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
}
}
// Parse FMPS frames
for (uint i = 0; i < map["TXXX"].size(); ++i) {
const TagLib::ID3v2::UserTextIdentificationFrame *frame = dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>(map["TXXX"][i]);
if (frame && frame->description().startsWith("FMPS_")) {
ParseFMPSFrame(TStringToQString(frame->description()), TStringToQString(frame->fieldList()[1]), song);
}
}
}
}
@@ -342,6 +331,9 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
if (mp4_tag->item("\251grp").isValid()) {
Decode(mp4_tag->item("\251grp").toStringList().toString(" "), nullptr, song->mutable_grouping());
}
if (mp4_tag->item("\251lyr").isValid()) {
Decode(mp4_tag->item("\251lyr").toStringList().toString(" "), nullptr, song->mutable_lyrics());
}
if (mp4_tag->item(kMP4_OriginalYear_ID).isValid()) {
song->set_originalyear(TStringToQString(mp4_tag->item(kMP4_OriginalYear_ID).toStringList().toString('\n')).left(4).toInt());
@@ -355,6 +347,10 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
song->set_bitdepth(file_asf->audioProperties()->bitsPerSample());
if (file_asf->tag()) {
Decode(file_asf->tag()->comment(), nullptr, song->mutable_comment());
}
const TagLib::ASF::AttributeListMap &attributes_map = file_asf->tag()->attributeListMap();
if (attributes_map.contains(kASF_OriginalDate_ID)) {
@@ -372,7 +368,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
}
else if (TagLib::MPC::File* file_mpc = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
if (file_mpc->tag()) {
if (file_mpc->APETag()) {
ParseAPETag(file_mpc->APETag()->itemListMap(), nullptr, &disc, &compilation, song);
}
if (tag) Decode(tag->comment(), nullptr, song->mutable_comment());
@@ -395,7 +391,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
if (compilation.isEmpty()) {
// well, it wasn't set, but if the artist is VA assume it's a compilation
if (QStringFromStdString(song->artist()).toLower() == "various artists") {
if (QStringFromStdString(song->artist()).toLower() == "various artists" || QStringFromStdString(song->albumartist()).toLower() == "various artists") {
song->set_compilation(true);
}
}
@@ -516,33 +512,6 @@ void TagReader::ParseAPETag(const TagLib::APE::ItemListMap &map, const QTextCode
}
void TagReader::ParseFMPSFrame(const QString &name, const QString &value, pb::tagreader::SongMetadata *song) const {
qLog(Debug) << "Parsing FMPSFrame" << name << ", " << value;
FMPSParser parser;
if (!parser.Parse(value) || parser.is_empty()) return;
QVariant var;
if (name == "FMPS_PlayCount") {
var = parser.result()[0][0];
if (var.type() == QVariant::Double) {
song->set_playcount(var.toDouble());
}
}
else if (name == "FMPS_PlayCount_User") {
// Take a user playcount only if there's no playcount already set
if (song->playcount() == 0 && parser.result()[0].count() >= 2) {
var = parser.result()[0][1];
if (var.type() == QVariant::Double) {
song->set_playcount(var.toDouble());
}
}
}
}
void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const {
vorbis_comments->addField("COMPOSER", StdStringToTaglibString(song.composer()), true);
@@ -563,7 +532,8 @@ void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, con
bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetadata &song) const {
if (filename.isNull() || filename.isEmpty()) return false;
if (filename.isEmpty()) return false;
qLog(Debug) << "Saving tags to" << filename;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));;
if (!fileref || fileref->isNull()) return false;
@@ -617,6 +587,7 @@ bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetad
tag->setItem("disk", TagLib::MP4::Item(song.disc() <= 0 -1 ? 0 : song.disc(), 0));
tag->setItem("\251wrt", TagLib::StringList(song.composer().c_str()));
tag->setItem("\251grp", TagLib::StringList(song.grouping().c_str()));
tag->setItem("\251lyr", TagLib::StringList(song.lyrics().c_str()));
tag->setItem("aART", TagLib::StringList(song.albumartist().c_str()));
tag->setItem("cpil", TagLib::StringList(song.compilation() ? "1" : "0"));
}

View File

@@ -37,7 +37,6 @@
class QTextCodec;
#ifndef USE_SYSTEM_TAGLIB
using namespace Strawberry_TagLib;
#endif
@@ -67,7 +66,6 @@ class TagReader {
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
void ParseAPETag(const TagLib::APE::ItemListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
void ParseFMPSFrame(const QString &name, const QString &value, pb::tagreader::SongMetadata *song) const;
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const;
void SaveAPETag(TagLib::APE::Tag *tag, const pb::tagreader::SongMetadata &song) const;

View File

@@ -55,8 +55,8 @@ message SongMetadata {
optional string basefilename = 22;
optional FileType filetype = 23;
optional int32 filesize = 24;
optional int32 mtime = 25;
optional int32 ctime = 26;
optional int64 mtime = 25;
optional int64 ctime = 26;
optional int32 playcount = 27;
optional int32 skipcount = 28;

View File

@@ -4,14 +4,19 @@ set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
set(SOURCES main.cpp tagreaderworker.cpp)
qt5_wrap_cpp(MOC ${HEADERS})
qt5_add_resources(QRC data/data.qrc)
if(BUILD_WITH_QT6)
qt6_wrap_cpp(MOC ${HEADERS})
qt6_add_resources(QRC data/data.qrc)
else()
qt5_wrap_cpp(MOC ${HEADERS})
qt5_add_resources(QRC data/data.qrc)
endif()
link_directories(
${GLIB_LIBRARY_DIRS}
${TAGLIB_LIBRARY_DIRS}
${Qt5Core_LIBRARY_DIRS}
${Qt5Network_LIBRARY_DIRS}
${QtCore_LIBRARY_DIRS}
${QtNetwork_LIBRARY_DIRS}
)
add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
@@ -19,8 +24,8 @@ add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
target_include_directories(strawberry-tagreader SYSTEM PRIVATE
${GLIB_INCLUDE_DIRS}
${PROTOBUF_INCLUDE_DIRS}
${Qt5Core_INCLUDE_DIRS}
${Qt5Network_INCLUDE_DIRS}
${QtCore_INCLUDE_DIRS}
${QtNetwork_INCLUDE_DIRS}
)
target_include_directories(strawberry-tagreader PRIVATE
@@ -34,8 +39,8 @@ target_include_directories(strawberry-tagreader PRIVATE
target_link_libraries(strawberry-tagreader PRIVATE
${GLIB_LIBRARIES}
${TAGLIB_LIBRARIES}
${Qt5Core_LIBRARIES}
${Qt5Network_LIBRARIES}
${QtCore_LIBRARIES}
${QtNetwork_LIBRARIES}
libstrawberry-common
libstrawberry-tagreader
)

View File

@@ -1,10 +1,9 @@
name: strawberry
version: '0.6.13+git'
adopt-info: strawberry
summary: music player and collection organizer
description: |
Strawberry is a music player and collection organizer.
It is a fork of Clementine released in 2018 aimed at music collectors,
audio enthusiasts and audiophiles
It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles
grade: stable
confinement: strict
@@ -43,7 +42,7 @@ parts:
alsa-lib:
plugin: autotools
source: https://www.alsa-project.org/files/pub/lib/alsa-lib-1.2.1.2.tar.bz2
source: https://www.alsa-project.org/files/pub/lib/alsa-lib-1.2.3.tar.bz2
configflags:
- --prefix=/usr
- --sysconfdir=/etc
@@ -71,6 +70,9 @@ parts:
after:
- alsa-lib
- desktop-qt5
override-pull: |
snapcraftctl pull
snapcraftctl set-version "$(git describe --tags | sed 's/^v//' | cut -d "-" -f1-2)"
override-build: |
cmake ../src -DCMAKE_INSTALL_PREFIX=/usr
make -j $(getconf _NPROCESSORS_ONLN)
@@ -84,6 +86,7 @@ parts:
- gcc
- g++
- protobuf-compiler
- gettext
- libglib2.0-dev
- libgnutls28-dev
- libdbus-1-dev
@@ -96,17 +99,14 @@ parts:
- qtbase5-dev
- qtbase5-dev-tools
- qtbase5-private-dev
- qttools5-dev
- libqt5x11extras5-dev
- libgstreamer1.0-dev
- libgstreamer-plugins-base1.0-dev
- libxine2-dev
- libvlc-dev
- libcdio-dev
- libgpod-dev
- libimobiledevice-dev
- libmtp-dev
- libplist-dev
- libusbmuxd-dev
- libchromaprint-dev
- libfftw3-dev
@@ -132,10 +132,6 @@ parts:
- libcdio17
- libgpod4
- libmtp9
- libimobiledevice6
- libplist3
- libusbmuxd4
- libxine2
- libvlc5
- libvlccore9
- libtag1v5
@@ -144,7 +140,6 @@ parts:
- libx11-6
- libxcomposite1
- libxcursor1
- libxinerama1
- libxrandr2
- libdb5.3
- libglu1
@@ -163,6 +158,10 @@ parts:
- gstreamer1.0-plugins-bad
- gstreamer1.0-plugins-ugly
- gstreamer1.0-libav
- qt5-gtk-platformtheme
- plasma-integration
- kde-style-breeze
- qtwayland5
apps:
strawberry:
@@ -183,7 +182,7 @@ apps:
- x11
- wayland
- alsa
- pulseaudio
- audio-playback
- removable-media
- optical-drive
- raw-usb

View File

@@ -33,12 +33,10 @@ set(SOURCES
core/thread.cpp
core/urlhandler.cpp
core/utilities.cpp
core/scangiomodulepath.cpp
core/iconloader.cpp
core/qtsystemtrayicon.cpp
core/standarditemiconloader.cpp
core/systemtrayicon.cpp
core/screensaver.cpp
core/scopedtransaction.cpp
core/translations.cpp
@@ -96,6 +94,7 @@ set(SOURCES
playlist/playlistview.cpp
playlist/songloaderinserter.cpp
playlist/songplaylistitem.cpp
playlist/dynamicplaylistcontrols.cpp
queue/queue.cpp
queue/queueview.cpp
@@ -111,6 +110,20 @@ set(SOURCES
playlistparsers/xmlparser.cpp
playlistparsers/xspfparser.cpp
smartplaylists/playlistgenerator.cpp
smartplaylists/playlistgeneratorinserter.cpp
smartplaylists/playlistquerygenerator.cpp
smartplaylists/smartplaylistquerywizardplugin.cpp
smartplaylists/smartplaylistsearch.cpp
smartplaylists/smartplaylistsearchpreview.cpp
smartplaylists/smartplaylistsearchterm.cpp
smartplaylists/smartplaylistsearchtermwidget.cpp
smartplaylists/smartplaylistsmodel.cpp
smartplaylists/smartplaylistsviewcontainer.cpp
smartplaylists/smartplaylistsview.cpp
smartplaylists/smartplaylistwizard.cpp
smartplaylists/smartplaylistwizardplugin.cpp
covermanager/albumcovermanager.cpp
covermanager/albumcovermanagerlist.cpp
covermanager/albumcoverloader.cpp
@@ -168,6 +181,10 @@ set(SOURCES
dialogs/edittagdialog.cpp
dialogs/trackselectiondialog.cpp
dialogs/addstreamdialog.cpp
dialogs/userpassdialog.cpp
dialogs/deleteconfirmationdialog.cpp
dialogs/lastfmimportdialog.cpp
dialogs/snapdialog.cpp
widgets/autoexpandingtreeview.cpp
widgets/busyindicator.cpp
@@ -183,8 +200,6 @@ set(SOURCES
widgets/linetextedit.cpp
widgets/multiloadingindicator.cpp
widgets/playingwidget.cpp
widgets/osd.cpp
widgets/osdpretty.cpp
widgets/renametablineedit.cpp
widgets/volumeslider.cpp
widgets/stickyslider.cpp
@@ -193,6 +208,10 @@ set(SOURCES
widgets/tracksliderpopup.cpp
widgets/tracksliderslider.cpp
widgets/loginstatewidget.cpp
widgets/ratingwidget.cpp
osd/osdbase.cpp
osd/osdpretty.cpp
musicbrainz/acoustidclient.cpp
musicbrainz/musicbrainzclient.cpp
@@ -220,11 +239,12 @@ set(SOURCES
scrobbler/lastfmscrobbler.cpp
scrobbler/librefmscrobbler.cpp
scrobbler/listenbrainzscrobbler.cpp
scrobbler/lastfmimport.cpp
organise/organise.cpp
organise/organiseformat.cpp
organise/organisedialog.cpp
organise/organiseerrordialog.cpp
organize/organize.cpp
organize/organizeformat.cpp
organize/organizedialog.cpp
organize/organizeerrordialog.cpp
)
@@ -295,6 +315,7 @@ set(HEADERS
playlist/playlistitemmimedata.h
playlist/songloaderinserter.h
playlist/songmimedata.h
playlist/dynamicplaylistcontrols.h
queue/queue.h
queue/queueview.h
@@ -308,6 +329,18 @@ set(HEADERS
playlistparsers/plsparser.h
playlistparsers/xspfparser.h
smartplaylists/playlistgenerator.h
smartplaylists/playlistgeneratorinserter.h
smartplaylists/playlistgeneratormimedata.h
smartplaylists/smartplaylistquerywizardplugin.h
smartplaylists/smartplaylistsearchpreview.h
smartplaylists/smartplaylistsearchtermwidget.h
smartplaylists/smartplaylistsmodel.h
smartplaylists/smartplaylistsviewcontainer.h
smartplaylists/smartplaylistsview.h
smartplaylists/smartplaylistwizard.h
smartplaylists/smartplaylistwizardplugin.h
covermanager/albumcovermanager.h
covermanager/albumcovermanagerlist.h
covermanager/albumcoverloader.h
@@ -364,6 +397,10 @@ set(HEADERS
dialogs/edittagdialog.h
dialogs/trackselectiondialog.h
dialogs/addstreamdialog.h
dialogs/userpassdialog.h
dialogs/deleteconfirmationdialog.h
dialogs/lastfmimportdialog.h
dialogs/snapdialog.h
widgets/autoexpandingtreeview.h
widgets/busyindicator.h
@@ -378,8 +415,6 @@ set(HEADERS
widgets/linetextedit.h
widgets/multiloadingindicator.h
widgets/playingwidget.h
widgets/osd.h
widgets/osdpretty.h
widgets/renametablineedit.h
widgets/volumeslider.h
widgets/stickyslider.h
@@ -389,6 +424,10 @@ set(HEADERS
widgets/tracksliderslider.h
widgets/loginstatewidget.h
widgets/qsearchfield.h
widgets/ratingwidget.h
osd/osdbase.h
osd/osdpretty.h
musicbrainz/acoustidclient.h
musicbrainz/musicbrainzclient.h
@@ -414,10 +453,11 @@ set(HEADERS
scrobbler/lastfmscrobbler.h
scrobbler/librefmscrobbler.h
scrobbler/listenbrainzscrobbler.h
scrobbler/lastfmimport.h
organise/organise.h
organise/organisedialog.h
organise/organiseerrordialog.h
organize/organize.h
organize/organizedialog.h
organize/organizeerrordialog.h
)
@@ -434,9 +474,17 @@ set(UI
playlist/playlistlistcontainer.ui
playlist/playlistsaveoptionsdialog.ui
playlist/playlistsequence.ui
playlist/dynamicplaylistcontrols.ui
queue/queueview.ui
smartplaylists/smartplaylistquerysearchpage.ui
smartplaylists/smartplaylistquerysortpage.ui
smartplaylists/smartplaylistsearchpreview.ui
smartplaylists/smartplaylistsearchtermwidget.ui
smartplaylists/smartplaylistsviewcontainer.ui
smartplaylists/smartplaylistwizardfinishpage.ui
covermanager/albumcoverexport.ui
covermanager/albumcovermanager.ui
covermanager/albumcoversearcher.ui
@@ -465,18 +513,22 @@ set(UI
dialogs/edittagdialog.ui
dialogs/trackselectiondialog.ui
dialogs/addstreamdialog.ui
dialogs/userpassdialog.ui
dialogs/lastfmimportdialog.ui
dialogs/snapdialog.ui
widgets/trackslider.ui
widgets/osdpretty.ui
widgets/fileview.ui
widgets/loginstatewidget.ui
osd/osdpretty.ui
internet/internettabsview.ui
internet/internetcollectionviewcontainer.ui
internet/internetsearchview.ui
organise/organisedialog.ui
organise/organiseerrordialog.ui
organize/organizedialog.ui
organize/organizeerrordialog.ui
)
@@ -521,10 +573,12 @@ optional_source(HAVE_ALSA
engine/alsadevicefinder.cpp
)
# X11
optional_source(X11_FOUND
# DBUS
optional_source(HAVE_DBUS
SOURCES
widgets/osd_x11.cpp
osd/osddbus.cpp
HEADERS
osd/osddbus.h
)
# GStreamer
@@ -533,15 +587,6 @@ optional_source(HAVE_GSTREAMER
HEADERS engine/gststartup.h engine/gstengine.h engine/gstenginepipeline.h engine/gstelementdeleter.h
)
# Xine
optional_source(HAVE_XINE
SOURCES engine/xineengine.cpp
HEADERS engine/xineengine.h
)
optional_source(XINE_ANALYZER
SOURCES engine/xinescope.c
)
# VLC
optional_source(HAVE_VLC
SOURCES engine/vlcengine.cpp
@@ -553,7 +598,7 @@ if(UNIX AND HAVE_DBUS)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus)
optional_source(HAVE_DBUS
SOURCES core/mpris.cpp core/mpris2.cpp core/dbusscreensaver.cpp
SOURCES core/mpris.cpp core/mpris2.cpp
HEADERS core/mpris.h core/mpris2.h
)
optional_source(HAVE_UDISKS2
@@ -561,31 +606,63 @@ if(UNIX AND HAVE_DBUS)
HEADERS device/udisks2lister.h
)
# MPRIS 2.0 DBUS interfaces
qt5_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.Player.xml
core/mpris2.h mpris::Mpris2 core/mpris2_player Mpris2Player)
qt5_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.xml
core/mpris2.h mpris::Mpris2 core/mpris2_root Mpris2Root)
qt5_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.TrackList.xml
core/mpris2.h mpris::Mpris2 core/mpris2_tracklist Mpris2TrackList)
if (BUILD_WITH_QT6)
# MPRIS 2.1 DBUS interfaces
qt5_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.Playlists.xml
core/mpris2.h mpris::Mpris2 core/mpris2_playlists Mpris2Playlists)
# MPRIS 2.0 DBUS interfaces
qt6_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.Player.xml
core/mpris2.h mpris::Mpris2 core/mpris2_player Mpris2Player)
qt6_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.xml
core/mpris2.h mpris::Mpris2 core/mpris2_root Mpris2Root)
qt6_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.TrackList.xml
core/mpris2.h mpris::Mpris2 core/mpris2_tracklist Mpris2TrackList)
# org.freedesktop.Notifications DBUS interface
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.Notifications.xml
dbus/notification)
# MPRIS 2.1 DBUS interfaces
qt6_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.Playlists.xml
core/mpris2.h mpris::Mpris2 core/mpris2_playlists Mpris2Playlists)
# org.gnome.SettingsDaemon interface
qt5_add_dbus_interface(SOURCES
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
dbus/gnomesettingsdaemon)
# org.freedesktop.Notifications DBUS interface
qt6_add_dbus_interface(SOURCES
dbus/org.freedesktop.Notifications.xml
dbus/notification)
# org.gnome.SettingsDaemon interface
qt6_add_dbus_interface(SOURCES
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
dbus/gnomesettingsdaemon)
else()
# MPRIS 2.0 DBUS interfaces
qt5_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.Player.xml
core/mpris2.h mpris::Mpris2 core/mpris2_player Mpris2Player)
qt5_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.xml
core/mpris2.h mpris::Mpris2 core/mpris2_root Mpris2Root)
qt5_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.TrackList.xml
core/mpris2.h mpris::Mpris2 core/mpris2_tracklist Mpris2TrackList)
# MPRIS 2.1 DBUS interfaces
qt5_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.Playlists.xml
core/mpris2.h mpris::Mpris2 core/mpris2_playlists Mpris2Playlists)
# org.freedesktop.Notifications DBUS interface
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.Notifications.xml
dbus/notification)
# org.gnome.SettingsDaemon interface
qt5_add_dbus_interface(SOURCES
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
dbus/gnomesettingsdaemon)
endif()
# org.freedesktop.Avahi.Server interface
add_custom_command(
@@ -626,21 +703,39 @@ if(UNIX AND HAVE_DBUS)
PROPERTIES NO_NAMESPACE dbus/udisks2drive INCLUDE dbus/metatypes.h)
set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml
PROPERTIES NO_NAMESPACE dbus/udisks2job INCLUDE dbus/metatypes.h)
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.DBus.ObjectManager.xml
dbus/objectmanager)
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Filesystem.xml
dbus/udisks2filesystem)
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Block.xml
dbus/udisks2block)
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Drive.xml
dbus/udisks2drive)
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Job.xml
dbus/udisks2job)
if(BUILD_WITH_QT6)
qt6_add_dbus_interface(SOURCES
dbus/org.freedesktop.DBus.ObjectManager.xml
dbus/objectmanager)
qt6_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Filesystem.xml
dbus/udisks2filesystem)
qt6_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Block.xml
dbus/udisks2block)
qt6_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Drive.xml
dbus/udisks2drive)
qt6_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Job.xml
dbus/udisks2job)
else()
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.DBus.ObjectManager.xml
dbus/objectmanager)
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Filesystem.xml
dbus/udisks2filesystem)
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Block.xml
dbus/udisks2block)
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Drive.xml
dbus/udisks2drive)
qt5_add_dbus_interface(SOURCES
dbus/org.freedesktop.UDisks2.Job.xml
dbus/udisks2job)
endif()
endif(HAVE_UDISKS2)
endif(UNIX AND HAVE_DBUS)
@@ -688,21 +783,6 @@ optional_source(HAVE_GIO
HEADERS device/giolister.h
)
# imobiledevice backend and device
optional_source(HAVE_IMOBILEDEVICE
SOURCES
device/afcdevice.cpp
device/afcfile.cpp
device/afctransfer.cpp
device/ilister.cpp
device/imobiledeviceconnection.cpp
HEADERS
device/afcdevice.h
device/afcfile.h
device/afctransfer.h
device/ilister.h
)
# mtp device
optional_source(HAVE_LIBMTP
SOURCES
@@ -783,9 +863,8 @@ optional_source(APPLE
core/mac_utilities.mm
core/mac_startup.mm
core/macsystemtrayicon.mm
core/macscreensaver.cpp
core/macfslistener.mm
widgets/osd_mac.mm
osd/osdmac.mm
widgets/qsearchfield_mac.mm
engine/macosdevicefinder.cpp
globalshortcuts/globalshortcutbackend-macos.mm
@@ -793,6 +872,7 @@ optional_source(APPLE
HEADERS
core/macsystemtrayicon.h
core/macfslistener.h
osd/osdmac.h
globalshortcuts/globalshortcutbackend-macos.h
)
@@ -810,7 +890,6 @@ optional_source(WIN32
SOURCES
engine/directsounddevicefinder.cpp
engine/mmdevicefinder.cpp
widgets/osd_win.cpp
core/windows7thumbbar.cpp
HEADERS
core/windows7thumbbar.h
@@ -822,13 +901,17 @@ optional_source(HAVE_SUBSONIC
subsonic/subsonicurlhandler.cpp
subsonic/subsonicbaserequest.cpp
subsonic/subsonicrequest.cpp
subsonic/subsonicscrobblerequest.cpp
settings/subsonicsettingspage.cpp
scrobbler/subsonicscrobbler.cpp
HEADERS
subsonic/subsonicservice.h
subsonic/subsonicurlhandler.h
subsonic/subsonicbaserequest.h
subsonic/subsonicrequest.h
subsonic/subsonicscrobblerequest.h
settings/subsonicsettingspage.h
scrobbler/subsonicscrobbler.h
UI
settings/subsonicsettingspage.ui
)
@@ -856,6 +939,27 @@ optional_source(HAVE_TIDAL
settings/tidalsettingspage.ui
)
optional_source(HAVE_QOBUZ
SOURCES
qobuz/qobuzservice.cpp
qobuz/qobuzurlhandler.cpp
qobuz/qobuzbaserequest.cpp
qobuz/qobuzrequest.cpp
qobuz/qobuzstreamurlrequest.cpp
qobuz/qobuzfavoriterequest.cpp
settings/qobuzsettingspage.cpp
HEADERS
qobuz/qobuzservice.h
qobuz/qobuzurlhandler.h
qobuz/qobuzbaserequest.h
qobuz/qobuzrequest.h
qobuz/qobuzstreamurlrequest.h
qobuz/qobuzfavoriterequest.h
settings/qobuzsettingspage.h
UI
settings/qobuzsettingspage.ui
)
# Moodbar
optional_source(HAVE_MOODBAR
SOURCES
@@ -881,9 +985,15 @@ optional_source(HAVE_MOODBAR
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
qt5_wrap_cpp(MOC ${HEADERS})
qt5_wrap_ui(UIC ${UI})
qt5_add_resources(QRC ${RESOURCES})
if(BUILD_WITH_QT6)
qt6_wrap_cpp(MOC ${HEADERS})
qt6_wrap_ui(UIC ${UI})
qt6_add_resources(QRC ${RESOURCES})
else()
qt5_wrap_cpp(MOC ${HEADERS})
qt5_wrap_ui(UIC ${UI})
qt5_add_resources(QRC ${RESOURCES})
endif()
if(HAVE_TRANSLATIONS)
@@ -926,6 +1036,8 @@ link_directories(
${QT_LIBRARY_DIRS}
${SINGLEAPPLICATION_LIBRARY_DIRS}
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
${QTSPARKLE_LIBRARY_DIRS}
${Iconv_LIBRARY_DIRS}
)
if(HAVE_ALSA)
@@ -947,10 +1059,6 @@ if(HAVE_GSTREAMER)
)
endif(HAVE_GSTREAMER)
if(HAVE_XINE)
link_directories(${LIBXINE_LIBRARY_DIRS})
endif()
if(HAVE_VLC)
link_directories(${LIBVLC_LIBRARY_DIRS})
endif()
@@ -976,21 +1084,14 @@ if(HAVE_AUDIOCD)
endif(HAVE_AUDIOCD)
if(HAVE_LIBGPOD)
link_directories(${LIBGPOD_LIBRARIES})
link_directories(${LIBGPOD_LIBRARY_DIRS})
link_directories(${GDK_PIXBUF_LIBRARY_DIRS})
endif(HAVE_LIBGPOD)
if(HAVE_LIBMTP)
link_directories(${LIBMTP_LIBRARY_DIRS})
endif(HAVE_LIBMTP)
if(HAVE_IMOBILEDEVICE)
link_directories(
${LIBUSBMUXD_LIBRARY_DIRS}
${LIBPLIST_LIBRARY_DIRS}
${LIBIMOBILEDEVICE_LIBRARY_DIRS}
)
endif(HAVE_IMOBILEDEVICE)
add_library(strawberry_lib STATIC
${SOURCES}
${MOC}
@@ -1033,6 +1134,8 @@ target_link_libraries(strawberry_lib PUBLIC
${QT_LIBRARIES}
${SINGLEAPPLICATION_LIBRARIES}
${SINGLECOREAPPLICATION_LIBRARIES}
${QTSPARKLE_LIBRARIES}
${Iconv_LIBRARY}
libstrawberry-common
libstrawberry-tagreader
)
@@ -1070,11 +1173,6 @@ if(HAVE_MOODBAR)
target_link_libraries(strawberry_lib PRIVATE gstmoodbar)
endif()
if(HAVE_XINE)
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBXINE_INCLUDE_DIRS})
target_link_libraries(strawberry_lib PRIVATE ${LIBXINE_LIBRARIES})
endif()
if(HAVE_VLC)
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBVLC_INCLUDE_DIRS})
target_link_libraries(strawberry_lib PRIVATE ${LIBVLC_LIBRARIES})
@@ -1106,8 +1204,8 @@ if(HAVE_AUDIOCD)
endif(HAVE_AUDIOCD)
if(HAVE_LIBGPOD)
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS})
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES})
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS} ${GDK_PIXBUF_INCLUDE_DIRS})
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES} ${GDK_PIXBUF_LIBRARIES})
endif(HAVE_LIBGPOD)
if(HAVE_LIBMTP)
@@ -1115,19 +1213,6 @@ if(HAVE_LIBMTP)
target_link_libraries(strawberry_lib PRIVATE ${LIBMTP_LIBRARIES})
endif(HAVE_LIBMTP)
if(HAVE_IMOBILEDEVICE)
target_include_directories(strawberry_lib SYSTEM PRIVATE
${LIBUSBMUXD_INCLUDE_DIRS}
${LIBPLIST_INCLUDE_DIRS}
${LIBIMOBILEDEVICE_INCLUDE_DIRS}
)
target_link_libraries(strawberry_lib PRIVATE
${LIBUSBMUXD_LIBRARIES}
${LIBPLIST_LIBRARIES}
${LIBIMOBILEDEVICE_LIBRARIES}
)
endif(HAVE_IMOBILEDEVICE)
if(APPLE)
target_link_libraries(strawberry_lib PRIVATE
@@ -1149,11 +1234,10 @@ if(WIN32)
target_link_libraries(strawberry_lib PRIVATE dsound)
endif(WIN32)
if(UNIX AND NOT APPLE)
if(X11_FOUND)
# Hack: the Gold linker pays attention to the order that libraries are specified on the link line.
# -lX11 and -ldl are provided earlier in the link command but they're actually used by libraries that appear after them, so they end up getting ignored.
# This appends them to the very end of the link line, ensuring they're always used.
find_package(X11)
if(FREEBSD)
target_link_libraries(strawberry_lib PRIVATE ${X11_X11_LIB})
else()
@@ -1196,6 +1280,10 @@ if(NOT APPLE)
install(TARGETS strawberry RUNTIME DESTINATION bin)
endif()
if(HAVE_TRANSLATIONS AND INSTALL_TRANSLATIONS AND INSTALL_TRANSLATIONS_FILES)
install(FILES ${INSTALL_TRANSLATIONS_FILES} DESTINATION share/strawberry/translations)
endif()
if(APPLE)
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/../dist/macos/Info.plist")
endif (APPLE)

View File

@@ -40,7 +40,7 @@
// INSTRUCTIONS Base2D
// 1. do anything that depends on height() in init(), Base2D will call it before you are shown
// 2. otherwise you can use the constructor to initialise things
// 2. otherwise you can use the constructor to initialize things
// 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it
// 4. if you want to manipulate the scope, reimplement transform()
// 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included

View File

@@ -99,10 +99,14 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
void AnalyzerContainer::mouseReleaseEvent(QMouseEvent *e) {
if (engine_->type() != Engine::EngineType::GStreamer && engine_->type() != Engine::EngineType::Xine) return;
if (engine_->type() != Engine::EngineType::GStreamer) return;
if (e->button() == Qt::RightButton) {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
context_menu_->popup(e->globalPosition().toPoint());
#else
context_menu_->popup(e->globalPos());
#endif
}
}
@@ -132,7 +136,7 @@ void AnalyzerContainer::ChangeAnalyzer(int id) {
QObject *instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this));
if (!instance) {
qLog(Warning) << "Couldn't initialise a new" << analyzer_types_[id]->className();
qLog(Warning) << "Couldn't initialize a new" << analyzer_types_[id]->className();
return;
}

View File

@@ -28,11 +28,11 @@
#include <QPoint>
#include <QMenu>
#include <QAction>
#include <QActionGroup>
#include "engine/engine_fwd.h"
class QTimer;
class QActionGroup;
class QMouseEvent;
class QWheelEvent;

View File

@@ -325,7 +325,7 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, uint amount) {
void BlockAnalyzer::paletteChange(const QPalette&) {
const QColor bg = palette().color(QPalette::Background);
const QColor bg = palette().color(QPalette::Window);
const QColor fg = ensureContrast(bg, palette().color(QPalette::Highlight));
topbarpixmap_.fill(fg);
@@ -343,12 +343,12 @@ void BlockAnalyzer::paletteChange(const QPalette&) {
p.fillRect(0, y * (kHeight + 1), kWidth, kHeight, QColor(r + static_cast<int>(dr * y), g + static_cast<int>(dg * y), b + static_cast<int>(db * y)));
{
const QColor bg2 = palette().color(QPalette::Background).darker(112);
const QColor bg2 = palette().color(QPalette::Window).darker(112);
// make a complimentary fadebar colour
// TODO dark is not always correct, dumbo!
int h, s, v;
palette().color(QPalette::Background).darker(150).getHsv(&h, &s, &v);
palette().color(QPalette::Window).darker(150).getHsv(&h, &s, &v);
const QColor fg2(QColor::fromHsv(h + 120, s, v));
const double dr2 = fg2.red() - bg2.red();
@@ -358,7 +358,7 @@ void BlockAnalyzer::paletteChange(const QPalette&) {
// Precalculate all fade-bar pixmaps
for (uint y = 0; y < kFadeSize; ++y) {
fade_bars_[y].fill(palette().color(QPalette::Background));
fade_bars_[y].fill(palette().color(QPalette::Window));
QPainter f(&fade_bars_[y]);
for (int z = 0; static_cast<uint>(z) < rows_; ++z) {
const double Y = 1.0 - (log10(kFadeSize - y) / log10(kFadeSize));
@@ -377,7 +377,7 @@ void BlockAnalyzer::drawBackground() {
return;
}
const QColor bg = palette().color(QPalette::Background);
const QColor bg = palette().color(QPalette::Window);
const QColor bgdark = bg.darker(112);
background_.fill(bg);

View File

@@ -87,7 +87,7 @@ void BoomAnalyzer::resizeEvent(QResizeEvent* e) {
barPixmap_ = QPixmap(kColumnWidth - 2, HEIGHT);
canvas_ = QPixmap(size());
canvas_.fill(palette().color(QPalette::Background));
canvas_.fill(palette().color(QPalette::Window));
QPainter p(&barPixmap_);
for (uint y = 0; y < HEIGHT; ++y) {
@@ -120,7 +120,7 @@ void BoomAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) {
const uint MAX_HEIGHT = height() - 1;
QPainter canvas_painter(&canvas_);
canvas_.fill(palette().color(QPalette::Background));
canvas_.fill(palette().color(QPalette::Window));
Analyzer::interpolate(scope, scope_);

View File

@@ -92,7 +92,7 @@ class RainbowAnalyzer : public Analyzer::Base {
}
private:
// "constants" that get initialised in the constructor
// "constants" that get initialized in the constructor
float band_scale_[kRainbowBands];
QPen colors_[kRainbowBands];

View File

@@ -40,6 +40,7 @@
#include "collectionbackend.h"
#include "collectionmodel.h"
#include "playlist/playlistmanager.h"
#include "scrobbler/lastfmimport.h"
const char *SCollection::kSongsTable = "songs";
const char *SCollection::kDirsTable = "directories";
@@ -106,10 +107,12 @@ void SCollection::Init() {
connect(watcher_, SIGNAL(SubdirsDiscovered(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList)));
connect(watcher_, SIGNAL(SubdirsMTimeUpdated(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList)));
connect(watcher_, SIGNAL(CompilationsNeedUpdating()), backend_, SLOT(UpdateCompilations()));
connect(backend_, SIGNAL(SongsStatisticsChanged(SongList)), SLOT(SongsStatisticsChanged(SongList)));
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song)));
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
connect(app_->lastfm_import(), SIGNAL(UpdateLastPlayed(QString, QString, QString, int)), backend_, SLOT(UpdateLastPlayed(QString, QString, QString, int)));
connect(app_->lastfm_import(), SIGNAL(UpdatePlayCount(QString, QString, int)), backend_, SLOT(UpdatePlayCount(QString, QString, int)));
// This will start the watcher checking for updates
backend_->LoadDirectoriesAsync();
@@ -131,7 +134,7 @@ void SCollection::Exit() {
void SCollection::ExitReceived() {
QObject *obj = static_cast<QObject*>(sender());
QObject *obj = qobject_cast<QObject*>(sender());
disconnect(obj, nullptr, this, nullptr);
qLog(Debug) << obj << "successfully exited.";
wait_for_exit_.removeAll(obj);
@@ -179,7 +182,3 @@ void SCollection::CurrentSongChanged(const Song &song) { // FIXME
}
}
void SCollection::SongsStatisticsChanged(const SongList &songs) {
Q_UNUSED(songs);
}

View File

@@ -79,7 +79,6 @@ class SCollection : public QObject {
void IncrementalScan();
void CurrentSongChanged(const Song &song);
void SongsStatisticsChanged(const SongList& songs);
void Stopped();
signals:

View File

@@ -38,12 +38,14 @@
#include <QUrl>
#include <QFileInfo>
#include <QDateTime>
#include <QRegExp>
#include <QRegularExpression>
#include <QSqlDatabase>
#include <QSqlQuery>
#include "core/logging.h"
#include "core/database.h"
#include "core/scopedtransaction.h"
#include "smartplaylists/smartplaylistsearch.h"
#include "directory.h"
#include "collectionbackend.h"
@@ -108,15 +110,15 @@ void CollectionBackend::UpdateTotalAlbumCountAsync() {
metaObject()->invokeMethod(this, "UpdateTotalAlbumCount", Qt::QueuedConnection);
}
void CollectionBackend::IncrementPlayCountAsync(int id) {
void CollectionBackend::IncrementPlayCountAsync(const int id) {
metaObject()->invokeMethod(this, "IncrementPlayCount", Qt::QueuedConnection, Q_ARG(int, id));
}
void CollectionBackend::IncrementSkipCountAsync(int id, float progress) {
void CollectionBackend::IncrementSkipCountAsync(const int id, const float progress) {
metaObject()->invokeMethod(this, "IncrementSkipCount", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, progress));
}
void CollectionBackend::ResetStatisticsAsync(int id) {
void CollectionBackend::ResetStatisticsAsync(const int id) {
metaObject()->invokeMethod(this, "ResetStatistics", Qt::QueuedConnection, Q_ARG(int, id));
}
@@ -133,7 +135,7 @@ void CollectionBackend::LoadDirectories() {
}
void CollectionBackend::ChangeDirPath(int id, const QString &old_path, const QString &new_path) {
void CollectionBackend::ChangeDirPath(const int id, const QString &old_path, const QString &new_path) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
@@ -201,7 +203,7 @@ DirectoryList CollectionBackend::GetAllDirectories() {
}
SubdirectoryList CollectionBackend::SubdirsInDirectory(int id) {
SubdirectoryList CollectionBackend::SubdirsInDirectory(const int id) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db = db_->Connect();
@@ -209,7 +211,7 @@ SubdirectoryList CollectionBackend::SubdirsInDirectory(int id) {
}
SubdirectoryList CollectionBackend::SubdirsInDirectory(int id, QSqlDatabase &db) {
SubdirectoryList CollectionBackend::SubdirsInDirectory(const int id, QSqlDatabase &db) {
QSqlQuery q(db);
q.prepare(QString("SELECT path, mtime FROM %1 WHERE directory_id = :dir").arg(subdirs_table_));
@@ -222,7 +224,7 @@ SubdirectoryList CollectionBackend::SubdirsInDirectory(int id, QSqlDatabase &db)
Subdirectory subdir;
subdir.directory_id = id;
subdir.path = q.value(0).toString();
subdir.mtime = q.value(1).toUInt();
subdir.mtime = q.value(1).toLongLong();
subdirs << subdir;
}
@@ -327,7 +329,7 @@ void CollectionBackend::RemoveDirectory(const Directory &dir) {
}
SongList CollectionBackend::FindSongsInDirectory(int id) {
SongList CollectionBackend::FindSongsInDirectory(const int id) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
@@ -581,7 +583,7 @@ void CollectionBackend::DeleteSongs(const SongList &songs) {
}
void CollectionBackend::MarkSongsUnavailable(const SongList &songs, bool unavailable) {
void CollectionBackend::MarkSongsUnavailable(const SongList &songs, const bool unavailable) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
@@ -700,7 +702,7 @@ SongList CollectionBackend::ExecCollectionQuery(CollectionQuery *query) {
}
Song CollectionBackend::GetSongById(int id) {
Song CollectionBackend::GetSongById(const int id) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
return GetSongById(id, db);
@@ -749,7 +751,7 @@ SongList CollectionBackend::GetSongsByForeignId(const QStringList &ids, const QS
}
Song CollectionBackend::GetSongById(int id, QSqlDatabase &db) {
Song CollectionBackend::GetSongById(const int id, QSqlDatabase &db) {
SongList list = GetSongsById(QStringList() << QString::number(id), db);
if (list.isEmpty()) return Song();
return list.first();
@@ -851,7 +853,11 @@ Song CollectionBackend::GetSongBySongId(const QString &song_id, QSqlDatabase &db
SongList CollectionBackend::GetSongsBySongId(const QStringList &song_ids, QSqlDatabase &db) {
QString in = song_ids.join(",");
QStringList song_ids2;
for (const QString &song_id : song_ids) {
song_ids2 << "'" + song_id + "'";
}
QString in = song_ids2.join(",");
QSqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE SONG_ID IN (%2)").arg(songs_table_, in));
@@ -999,10 +1005,8 @@ void CollectionBackend::UpdateCompilations(QSqlQuery &find_song, QSqlQuery &upda
CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist, const bool compilation_required, const QueryOptions &opt) {
AlbumList ret;
CollectionQuery query(opt);
query.SetColumnSpec("album, artist, albumartist, compilation, compilation_detected, art_automatic, art_manual, url");
query.SetColumnSpec("url, artist, albumartist, album, compilation_effective, art_automatic, art_manual");
query.SetOrderBy("album");
if (compilation_required) {
@@ -1015,23 +1019,23 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
{
QMutexLocker l(db_->Mutex());
if (!ExecQuery(&query)) return ret;
if (!ExecQuery(&query)) return AlbumList();
}
QString last_album;
QString last_artist;
QString last_album_artist;
QMap<QString, Album> albums;
while (query.Next()) {
bool is_compilation = query.Value(3).toBool() | query.Value(4).toBool();
bool is_compilation = query.Value(4).toBool();
Album info;
info.artist = is_compilation ? QString() : query.Value(1).toString();
info.album_artist = is_compilation ? QString() : query.Value(2).toString();
info.album_name = query.Value(0).toString();
info.first_url = QUrl::fromEncoded(query.Value(7).toByteArray());
info.first_url = QUrl::fromEncoded(query.Value(0).toByteArray());
if (!is_compilation) {
info.artist = query.Value(1).toString();
info.album_artist = query.Value(2).toString();
}
info.album_name = query.Value(3).toString();
QString art_automatic = query.Value(5).toString();
if (art_automatic.contains(QRegExp("..+:.*"))) {
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
}
else {
@@ -1039,24 +1043,30 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
}
QString art_manual = query.Value(6).toString();
if (art_manual.contains(QRegExp("..+:.*"))) {
if (art_manual.contains(QRegularExpression("..+:.*"))) {
info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
}
else {
info.art_manual = QUrl::fromLocalFile(art_manual);
}
if ((info.artist == last_artist || info.album_artist == last_album_artist) && info.album_name == last_album)
continue;
QString effective_albumartist = info.album_artist.isEmpty() ? info.artist : info.album_artist;
QString key;
if (!effective_albumartist.isEmpty()) {
key.append(effective_albumartist);
}
if (!info.album_name.isEmpty()) {
if (!key.isEmpty()) key.append("-");
key.append(info.album_name);
}
ret << info;
if (key.isEmpty()) continue;
if (!albums.contains(key)) albums.insert(key, info);
last_album = info.album_name;
last_artist = info.artist;
last_album_artist = info.album_artist;
}
return ret;
return albums.values();
}
@@ -1163,7 +1173,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QStrin
}
void CollectionBackend::ForceCompilation(const QString &album, const QList<QString> &artists, bool on) {
void CollectionBackend::ForceCompilation(const QString &album, const QList<QString> &artists, const bool on) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
@@ -1174,7 +1184,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QList<QStri
CollectionQuery query;
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
query.AddWhere("album", album);
if (!artist.isNull() && !artist.isEmpty()) query.AddWhere("artist", artist);
if (!artist.isEmpty()) query.AddWhere("artist", artist);
if (!ExecQuery(&query)) return;
@@ -1219,7 +1229,7 @@ bool CollectionBackend::ExecQuery(CollectionQuery *q) {
return !db_->CheckErrors(q->Exec(db_->Connect(), songs_table_, fts_table_));
}
void CollectionBackend::IncrementPlayCount(int id) {
void CollectionBackend::IncrementPlayCount(const int id) {
if (id == -1) return;
@@ -1228,7 +1238,7 @@ void CollectionBackend::IncrementPlayCount(int id) {
QSqlQuery q(db);
q.prepare(QString("UPDATE %1 SET playcount = playcount + 1, lastplayed = :now WHERE ROWID = :id").arg(songs_table_));
q.bindValue(":now", QDateTime::currentDateTime().toTime_t());
q.bindValue(":now", QDateTime::currentDateTime().toSecsSinceEpoch());
q.bindValue(":id", id);
q.exec();
if (db_->CheckErrors(q)) return;
@@ -1238,7 +1248,7 @@ void CollectionBackend::IncrementPlayCount(int id) {
}
void CollectionBackend::IncrementSkipCount(int id, float progress) {
void CollectionBackend::IncrementSkipCount(const int id, const float progress) {
Q_UNUSED(progress);
@@ -1258,7 +1268,7 @@ void CollectionBackend::IncrementSkipCount(int id, float progress) {
}
void CollectionBackend::ResetStatistics(int id) {
void CollectionBackend::ResetStatistics(const int id) {
if (id == -1) return;
@@ -1297,3 +1307,152 @@ void CollectionBackend::DeleteAll() {
emit DatabaseReset();
}
SongList CollectionBackend::FindSongs(const SmartPlaylistSearch &search) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
// Build the query
QString sql = search.ToSql(songs_table());
// Run the query
SongList ret;
QSqlQuery query(db);
query.prepare(sql);
query.exec();
if (db_->CheckErrors(query)) return ret;
// Read the results
while (query.next()) {
Song song;
song.InitFromQuery(query, true);
ret << song;
}
return ret;
}
SongList CollectionBackend::GetAllSongs() {
// Get all the songs!
return FindSongs(SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::Sort_FieldAsc, SmartPlaylistSearchTerm::Field_Artist, -1));
}
SongList CollectionBackend::GetSongsBy(const QString &artist, const QString &album, const QString &title) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
SongList songs;
QSqlQuery q(db);
if (album.isEmpty()) {
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE artist = :artist COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(songs_table_));
}
else {
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE artist = :artist COLLATE NOCASE AND album = :album COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(songs_table_));
}
q.bindValue(":artist", artist);
if (!album.isEmpty()) q.bindValue(":album", album);
q.bindValue(":title", title);
q.exec();
if (db_->CheckErrors(q)) return SongList();
while (q.next()) {
Song song(source_);
song.InitFromQuery(q, true);
songs << song;
}
return songs;
}
void CollectionBackend::UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const int lastplayed) {
SongList songs = GetSongsBy(artist, album, title);
if (songs.isEmpty()) {
qLog(Debug) << "Could not find a matching song in the database for" << artist << album << title;
return;
}
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
for (const Song &song : songs) {
QSqlQuery q(db);
q.prepare(QString("UPDATE %1 SET lastplayed = :lastplayed WHERE ROWID = :id").arg(songs_table_));
q.bindValue(":lastplayed", lastplayed);
q.bindValue(":id", song.id());
q.exec();
if (db_->CheckErrors(q)) continue;
}
emit SongsStatisticsChanged(SongList() << songs);
}
void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &title, const int playcount) {
SongList songs = GetSongsBy(artist, QString(), title);
if (songs.isEmpty()) {
qLog(Debug) << "Could not find a matching song in the database for" << artist << title;
return;
}
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
for (const Song &song : songs) {
QSqlQuery q(db);
q.prepare(QString("UPDATE %1 SET playcount = :playcount WHERE ROWID = :id").arg(songs_table_));
q.bindValue(":playcount", playcount);
q.bindValue(":id", song.id());
q.exec();
if (db_->CheckErrors(q)) continue;
}
emit SongsStatisticsChanged(SongList() << songs);
}
void CollectionBackend::UpdateSongRating(const int id, const float rating) {
if (id == -1) return;
QList<int> id_list;
id_list << id;
UpdateSongsRating(id_list, rating);
}
void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const float rating) {
if (id_list.isEmpty()) return;
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
QStringList id_str_list;
for (int i : id_list) {
id_str_list << QString::number(i);
}
QString ids = id_str_list.join(",");
QSqlQuery q(db);
q.prepare(QString("UPDATE %1 SET rating = :rating WHERE ROWID IN (%2)").arg(songs_table_, ids));
q.bindValue(":rating", rating);
q.exec();
if (db_->CheckErrors(q)) return;
SongList new_song_list = GetSongsById(id_str_list, db);
emit SongsRatingChanged(new_song_list);
}
void CollectionBackend::UpdateSongRatingAsync(const int id, const float rating) {
metaObject()->invokeMethod(this, "UpdateSongRating", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, rating));
}
void CollectionBackend::UpdateSongsRatingAsync(const QList<int>& ids, const float rating) {
metaObject()->invokeMethod(this, "UpdateSongsRating", Qt::QueuedConnection, Q_ARG(QList<int>, ids), Q_ARG(float, rating));
}

View File

@@ -40,6 +40,7 @@
class QThread;
class Database;
class SmartPlaylistSearch;
class CollectionBackendInterface : public QObject {
Q_OBJECT
@@ -80,10 +81,10 @@ class CollectionBackendInterface : public QObject {
virtual void UpdateTotalArtistCountAsync() = 0;
virtual void UpdateTotalAlbumCountAsync() = 0;
virtual SongList FindSongsInDirectory(int id) = 0;
virtual SubdirectoryList SubdirsInDirectory(int id) = 0;
virtual SongList FindSongsInDirectory(const int id) = 0;
virtual SubdirectoryList SubdirsInDirectory(const int id) = 0;
virtual DirectoryList GetAllDirectories() = 0;
virtual void ChangeDirPath(int id, const QString &old_path, const QString &new_path) = 0;
virtual void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) = 0;
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
@@ -99,7 +100,7 @@ class CollectionBackendInterface : public QObject {
virtual void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url) = 0;
virtual Album GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album) = 0;
virtual Song GetSongById(int id) = 0;
virtual Song GetSongById(const int id) = 0;
// Returns all sections of a song with the given filename. If there's just one section the resulting list will have it's size equal to 1.
virtual SongList GetSongsByUrl(const QUrl &url) = 0;
@@ -182,10 +183,16 @@ class CollectionBackend : public CollectionBackendInterface {
Song GetSongBySongId(const QString &song_id);
SongList GetSongsBySongId(const QStringList &song_ids);
SongList GetAllSongs();
SongList FindSongs(const SmartPlaylistSearch &search);
Song::Source Source() const;
void AddOrUpdateSongsAsync(const SongList &songs);
void UpdateSongRatingAsync(const int id, const float rating);
void UpdateSongsRatingAsync(const QList<int> &ids, const float rating);
public slots:
void Exit();
void LoadDirectories();
@@ -195,7 +202,7 @@ class CollectionBackend : public CollectionBackendInterface {
void AddOrUpdateSongs(const SongList &songs);
void UpdateMTimesOnly(const SongList &songs);
void DeleteSongs(const SongList &songs);
void MarkSongsUnavailable(const SongList &songs, bool unavailable = true);
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
void AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
void UpdateCompilations();
void UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url);
@@ -205,19 +212,27 @@ class CollectionBackend : public CollectionBackendInterface {
void ResetStatistics(const int id);
void SongPathChanged(const Song &song, const QFileInfo &new_file);
signals:
void DirectoryDiscovered(const Directory &dir, const SubdirectoryList &subdirs);
void DirectoryDeleted(const Directory &dir);
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const int lastplayed);
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
void SongsDiscovered(const SongList &songs);
void SongsDeleted(const SongList &songs);
void SongsStatisticsChanged(const SongList& songs);
void UpdateSongRating(const int id, const float rating);
void UpdateSongsRating(const QList<int> &id_list, const float rating);
signals:
void DirectoryDiscovered(Directory, SubdirectoryList);
void DirectoryDeleted(Directory);
void SongsDiscovered(SongList);
void SongsDeleted(SongList);
void SongsStatisticsChanged(SongList);
void DatabaseReset();
void TotalSongCountUpdated(const int total);
void TotalArtistCountUpdated(const int total);
void TotalAlbumCountUpdated(const int total);
void TotalSongCountUpdated(int);
void TotalArtistCountUpdated(int);
void TotalAlbumCountUpdated(int);
void SongsRatingChanged(SongList);
void ExitFinished();

View File

@@ -31,7 +31,7 @@
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QRegExp>
#include <QRegularExpression>
#include <QInputDialog>
#include <QList>
#include <QTimer>
@@ -63,27 +63,26 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
ui_->setupUi(this);
QString available_fields = Song::kFtsColumns.join(", ").replace(QRegExp("\\bfts"), "");
QString available_fields = Song::kFtsColumns.join(", ").replace(QRegularExpression("\\bfts"), "");
ui_->filter->setToolTip(
"<html><head/><body><p>" +
QString("<html><head/><body><p>") +
tr("Prefix a word with a field name to limit the search to that field, e.g.:") +
" " +
"<span style=\"font-weight:600;\">" +
QString(" ") +
QString("<span style=\"font-weight:600;\">") +
tr("artist") +
":" +
"</span><span style=\"font-style:italic;\">Strawbs</span>" +
" " +
QString(":") +
QString("</span><span style=\"font-style:italic;\">Strawbs</span>") +
QString(" ") +
tr("searches the collection for all artists that contain the word") +
"Strawbs" +
"." +
"</p><p><span style=\"font-weight:600;\">" +
QString(" Strawbs.") +
QString("</p><p><span style=\"font-weight:600;\">") +
tr("Available fields") +
": " +
QString(": ") +
"</span><span style=\"font-style:italic;\">" +
available_fields +
"</span>." +
"</p></body></html>"
QString("</span>.") +
QString("</p></body></html>")
);
connect(ui_->filter, SIGNAL(returnPressed()), SIGNAL(ReturnPressed()));
@@ -164,6 +163,17 @@ QString CollectionFilterWidget::group_by() {
}
QString CollectionFilterWidget::group_by_version() {
if (settings_prefix_.isEmpty()) {
return QString("group_by_version");
}
else {
return QString("%1_group_by_version").arg(settings_prefix_);
}
}
QString CollectionFilterWidget::group_by(const int number) { return group_by() + QString::number(number); }
void CollectionFilterWidget::UpdateGroupByActions() {
@@ -214,14 +224,26 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(QObject *parent) {
// read saved groupings
QSettings s;
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
QStringList saved = s.childKeys();
for (int i = 0; i < saved.size(); ++i) {
QByteArray bytes = s.value(saved.at(i)).toByteArray();
QDataStream ds(&bytes, QIODevice::ReadOnly);
CollectionModel::Grouping g;
ds >> g;
ret->addAction(CreateGroupByAction(saved.at(i), parent, g));
int version = s.value("version").toInt();
if (version == 1) {
QStringList saved = s.childKeys();
for (int i = 0; i < saved.size(); ++i) {
if (saved.at(i) == "version") continue;
QByteArray bytes = s.value(saved.at(i)).toByteArray();
QDataStream ds(&bytes, QIODevice::ReadOnly);
CollectionModel::Grouping g;
ds >> g;
ret->addAction(CreateGroupByAction(saved.at(i), parent, g));
}
}
else {
QStringList saved = s.childKeys();
for (int i = 0; i < saved.size(); ++i) {
if (saved.at(i) == "version") continue;
s.remove(saved.at(i));
}
}
s.endGroup();
QAction *sep2 = new QAction(parent);
sep2->setSeparator(true);
@@ -302,10 +324,18 @@ void CollectionFilterWidget::SetCollectionModel(CollectionModel *model) {
if (!settings_group_.isEmpty()) {
QSettings s;
s.beginGroup(settings_group_);
model_->SetGroupBy(CollectionModel::Grouping(
CollectionModel::GroupBy(s.value(group_by(1), int(CollectionModel::GroupBy_AlbumArtist)).toInt()),
CollectionModel::GroupBy(s.value(group_by(2), int(CollectionModel::GroupBy_AlbumDisc)).toInt()),
CollectionModel::GroupBy(s.value(group_by(3), int(CollectionModel::GroupBy_None)).toInt())));
int version = 0;
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
if (version == 1) {
model_->SetGroupBy(CollectionModel::Grouping(
CollectionModel::GroupBy(s.value(group_by(1), int(CollectionModel::GroupBy_AlbumArtist)).toInt()),
CollectionModel::GroupBy(s.value(group_by(2), int(CollectionModel::GroupBy_AlbumDisc)).toInt()),
CollectionModel::GroupBy(s.value(group_by(3), int(CollectionModel::GroupBy_None)).toInt())));
}
else {
model_->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_AlbumDisc, CollectionModel::GroupBy_None));
}
s.endGroup();
}
}
@@ -328,9 +358,11 @@ void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping &g)
// Save the settings
QSettings s;
s.beginGroup(settings_group_);
s.setValue(group_by_version(), 1);
s.setValue(group_by(1), int(g[0]));
s.setValue(group_by(2), int(g[1]));
s.setValue(group_by(3), int(g[2]));
s.endGroup();
}
// Now make sure the correct action is checked

View File

@@ -77,6 +77,7 @@ class CollectionFilterWidget : public QWidget {
void SetCollectionModel(CollectionModel *model);
QString group_by();
QString group_by_version();
QString group_by(const int number);
void ReloadSettings();

View File

@@ -33,7 +33,6 @@ class CollectionItem : public SimpleTreeItem<CollectionItem> {
Type_Divider,
Type_Container,
Type_Song,
Type_PlaylistContainer,
Type_LoadingIndicator,
};

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