Compare commits

...

119 Commits

Author SHA1 Message Date
Jonas Kvinge
eb6289dd1c Release 1.0.22 2023-12-09 23:21:35 +01:00
Jonas Kvinge
671df6eafb Update Changelog 2023-12-09 23:17:19 +01:00
Jonas Kvinge
579563b32c README: Fix emojis 2023-12-09 21:02:18 +01:00
Jonas Kvinge
135b93a5af StretchHeaderView: Set default section size
As of Qt 6.6.1, style changes are resetting the column sizes. To prevent this, we set a default section size.

Fixes #1328
2023-12-09 01:47:37 +01:00
BetterCallMolly
84c6e09c42 Player: Fix crossfade crash when decoding fails
When the decoding of a track fails, `current_item_` is set to an invalid address, if the Crossfade option is enabled, the `Player::TrackAboutToEnd` method does not check whether `current_item_` is a valid pointer or not, causing a segmentation fault.

Player: Removed extra space
2023-12-03 22:07:09 +01:00
Strawbs Bot
b4f9808d11 Update translations 2023-12-02 04:30:54 +01:00
Jonas Kvinge
d96d4224a2 Collection: Use normal pointer for watcher
Fixes #1316
2023-11-29 22:26:40 +01:00
Jonas Kvinge
f65927e308 metatypes: Register QAbstractSocket::SocketState 2023-11-29 22:25:03 +01:00
Jonas Kvinge
eeeea8566e nsi: Handle silent uninstall
Fixes #1323
2023-11-26 21:35:48 +01:00
Jonas Kvinge
54c42b276f GstEnginePipeline: Increase thread priority 2023-11-26 13:12:03 +01:00
Jonas Kvinge
8e4b4d6e41 nsi: Fix faad dll filename 2023-11-15 16:42:02 +01:00
Jonas Kvinge
ac9fd9070f GstEnginePipeline: Only set max size buffer if > 0
Fixes #1302
2023-11-12 22:04:31 +01:00
Jonas Kvinge
6348649bc6 GstEnginePipeline: Run QTimer::singleShot in main thread
Partial fix for #1302
2023-11-12 21:57:59 +01:00
Strawbs Bot
c95886d8db Update translations 2023-11-05 22:10:17 +01:00
Michał Walenciak
117b965a7b Translate number of tracks with nice plural forms 2023-11-05 19:37:56 +01:00
Michał Walenciak
1b6b5f9afa Use plurals 2023-11-05 19:37:56 +01:00
Michał Walenciak
83bc8d9e86 Select target language 2023-11-05 19:37:56 +01:00
Michał Walenciak
33f0421d3f Use 'n' for proper plurar form 2023-11-05 19:37:56 +01:00
Sergei B
661615e546 Allow drag and drop of songs to favorite playlists
- allows adding songs from active playlist
to any favorite by drag & drop
- after 500msec hovering with the songs over
desired playlist it becomes current
- drag & drop multiple songs is supported
2023-11-04 21:06:04 +01:00
Jonas Kvinge
c52fc90306 CI: Don't run SSH upload or macOS codesign on forks 2023-11-03 22:47:25 +01:00
Jonas Kvinge
eeb55fbc42 nsi: Update icu to 74 2023-11-01 23:07:47 +01:00
Jonas Kvinge
5d77eb1901 Change URL for KDSingleApplication submodule to https 2023-10-28 19:26:11 +02:00
Jonas Kvinge
654a94fe3d nsi: Add libabsl_kernel_timeout_internal.dll 2023-10-28 15:32:49 +02:00
Jonas Kvinge
4238708226 README: Update build instructions 2023-10-28 00:56:08 +02:00
Jonas Kvinge
5f02072bf3 CMake: Fix KDSingleApplication version check
Fixes #1300
2023-10-23 23:56:59 +02:00
Jonas Kvinge
eec5b448b6 CI: Add fftw3-devel for openSUSE 2023-10-23 19:45:50 +02:00
Jonas Kvinge
5cda756f92 CI: Remove get release version for macOS private 2023-10-23 19:37:40 +02:00
Jonas Kvinge
e9588dd85b CI: Remove SSH key setup from macOS private 2023-10-22 21:34:12 +02:00
Jonas Kvinge
dda0e4aa06 CI: Use MACOS_KEYCHAIN_PASSWORD 2023-10-22 21:13:28 +02:00
Jonas Kvinge
2282406166 CI: Build on self-hosted runner only on private repo 2023-10-22 18:51:23 +02:00
Jonas Kvinge
b3f0dee8e9 CI: Add "if: true" to easily disable steps 2023-10-22 17:20:28 +02:00
Jonas Kvinge
f9f7381247 CI: Fix APPLE_DEVELOPER_ID echo 2023-10-22 17:15:49 +02:00
Jonas Kvinge
48685325e6 Use KDSingleApplication as a submodule 2023-10-22 16:32:55 +02:00
Jonas Kvinge
e8bcaf415c CI: Only run SSH upload on this repo 2023-10-22 15:26:23 +02:00
Ondrej Mosnáček
c9197e8df7 CMake: Fix KDSingleApplication package name for Qt5
The name of the Qt5 KDSingleApplication CMake package is just
"KDSingleApplication", not "KDSingleApplication-qt".

Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
2023-10-22 14:24:52 +02:00
Jonas Kvinge
2bb09cf575 Song: Handle MP2 in Song::FiletypeByDescription 2023-10-21 05:07:25 +02:00
Jonas Kvinge
7bf4ad3884 Song: Handle MP2 in Song::FiletypeByExtension 2023-10-21 04:59:50 +02:00
Jonas Kvinge
5154d7ac84 Song: Rename MP3 to MPEG 2023-10-21 04:59:33 +02:00
Jonas Kvinge
9299653722 Turn on git revision 2023-10-21 04:57:53 +02:00
Jonas Kvinge
b0f0133d29 Release 1.0.21 2023-10-21 03:53:40 +02:00
Jonas Kvinge
9211b6f0c0 GstStartup: Remove macOS libsoup workarounds 2023-10-21 03:05:04 +02:00
Strawbs Bot
ab6a0ed6dd Update translations 2023-10-21 01:33:19 +02:00
Jonas Kvinge
c975c1e4aa CI: Remove unused homebrew and macports build 2023-10-20 00:53:49 +02:00
Jonas Kvinge
f9a593dc74 CI: Remove conflicting files for MSVC 2023-10-19 23:25:06 +02:00
Jonas Kvinge
9151520d50 CI: Remove uninstalling mingw 2023-10-18 23:19:50 +02:00
Jonas Kvinge
8f72d877bd CI: Manually sign libraries missing signature 2023-10-18 20:47:17 +02:00
Jonas Kvinge
e1990c9315 macgstcopy: Add rpath to gst plugin scanner 2023-10-18 20:46:24 +02:00
Jonas Kvinge
4cd5dcbfcf Update Changelog 2023-10-15 20:21:41 +02:00
Jonas Kvinge
3ee2125e8f CI: Verify code-signing 2023-10-15 16:51:13 +02:00
Jonas Kvinge
4652f3b449 CI: Manually codesign libsoup dependencies 2023-10-15 16:51:04 +02:00
Jonas Kvinge
697717eb1e CI: Use apple-actions/import-codesign-certs 2023-10-15 16:09:25 +02:00
Jonas Kvinge
cbde71f5f1 CI: Remove macOS lconvert and jenkins workaround 2023-10-15 07:48:20 +02:00
Jonas Kvinge
bf52afa21d GstStartup: Add back LIBSOUP3_LIBRARY_PATH 2023-10-15 07:46:49 +02:00
Jonas Kvinge
2083e008ab CI: Add macOS code-signing 2023-10-15 06:28:38 +02:00
Jonas Kvinge
2be0d23b1b macgstcopy: Change ID with install_name_tool 2023-10-14 23:24:56 +02:00
Jonas Kvinge
fda56dda25 main: Use Utilities::SetEnv 2023-10-14 23:18:16 +02:00
Jonas Kvinge
ed259781e9 CI: Remove rsync install and -DBUILD_WERROR 2023-10-14 23:17:58 +02:00
Jonas Kvinge
0c7fcd5a7a CMake: Fix USE_BUNDLE default 2023-10-14 22:09:14 +02:00
Jonas Kvinge
310b7b9065 CollectionQuery: Add F for float 2023-10-14 22:08:50 +02:00
Jonas Kvinge
cd534bbda7 CMake: Remove USE_BUNDLE_DIR 2023-10-14 03:30:09 +02:00
Jonas Kvinge
1a66eaf7bf GstStartup: Refactor environment code 2023-10-14 03:29:54 +02:00
Jonas Kvinge
a94d6e3dd8 CI: macgstcopy.sh copies libsoup now 2023-10-14 03:29:14 +02:00
Jonas Kvinge
54cfb2bbc4 main: Don't override library paths 2023-10-14 03:28:28 +02:00
Jonas Kvinge
00bc3f76cf macgstcopy: Copy libsoup 2023-10-14 03:27:57 +02:00
Jonas Kvinge
71a6d378d9 workerpool: Always search plugin path for tagreader on macOS 2023-10-14 03:27:32 +02:00
Jonas Kvinge
99a5aee8b3 GstEnginePipeline: Change debug logging for active/inactive 2023-10-13 23:38:19 +02:00
Jonas Kvinge
89d2a23dac CollectionBackend: Use QString::arg() 2023-10-13 23:06:29 +02:00
Jonas Kvinge
ee1bf47f5c DeviceInfo: Simplify hint 2023-10-13 22:58:53 +02:00
Jonas Kvinge
13ac20f8b3 Add/remove reference for parameters 2023-10-13 22:58:18 +02:00
Jonas Kvinge
adef05bbdf Use QString::arg() 2023-10-13 22:55:20 +02:00
Jonas Kvinge
f03ff452b8 SavePlaylistsDialog: Add parent to ctor 2023-10-13 22:53:27 +02:00
Jonas Kvinge
c39489060b Mpris2: Add static_cast 2023-10-13 22:52:36 +02:00
Jonas Kvinge
002fa8f4aa Fix mismatched definition 2023-10-13 22:49:20 +02:00
Jonas Kvinge
d2c747258c Song: Add MPC to FiletypeByMimetype and FiletypeByDescription 2023-10-12 01:16:40 +02:00
Jonas Kvinge
53e3664726 CI: Install hub in upload release step 2023-10-11 17:37:47 +02:00
Jonas Kvinge
26ff9f6b53 Update Changelog 2023-10-10 23:44:34 +02:00
Jonas Kvinge
f542f1c854 GstEnginePipeline: Remove volume sync for Auto
Workaround crash in #1123
2023-10-10 23:17:03 +02:00
Jonas Kvinge
33041ffa75 GstEnginePipeline: Delay seek when when resetting next URI
When seeking after the next URI is set, we set the state to READY to switch the URI back. The seek in after going to ready sometimes does not work, delay the seek to workaround this.

Fixes #1258
2023-10-10 23:00:11 +02:00
Jonas Kvinge
1493164df9 CollectionQuery: Strip off whitespaces after colon and simplify code
Fixes #1290
2023-10-10 19:15:20 +02:00
Strawbs Bot
8ffef558ff Update translations 2023-10-10 01:37:19 +02:00
Jonas Kvinge
8b3f44ffca Update Changelog 2023-10-10 01:32:33 +02:00
Jonas Kvinge
2706529006 DeviceDatabaseBackend: Add missing ebur128 fields 2023-10-10 01:27:39 +02:00
Jonas Kvinge
7e331a2055 DeviceManager: Fix creating connected device 2023-10-10 01:26:47 +02:00
Jonas Kvinge
505329730c Improve lyrics match 2023-10-08 23:55:05 +02:00
Jonas Kvinge
1a07404c10 Simplify RPM spec file 2023-10-08 16:15:59 +02:00
Jonas Kvinge
b02adc7758 Simplify RPM spec file 2023-10-08 13:54:36 +02:00
Jonas Kvinge
952252ebcd Simplify RPM spec file 2023-10-08 02:55:22 +02:00
Jonas Kvinge
eee0c40132 Playlist: Use InternetServicePtr 2023-10-07 17:05:51 +02:00
Jonas Kvinge
567bad33e1 Playlist: Use PlaylistItemPtr 2023-10-07 17:05:36 +02:00
Jonas Kvinge
b5c0e93989 FancyTabWidget: Use QApplication::style(), not style() 2023-10-07 15:36:49 +02:00
Jonas Kvinge
ac17df2a86 PlaylistContainer: Remove unused signals 2023-10-07 15:34:37 +02:00
Jonas Kvinge
a9a5899252 FancyTabWidgetProxyStyle: Create proxy style from application style 2023-10-07 15:23:41 +02:00
Jonas Kvinge
395d85c1b4 Move PlaylistProxyStyle to it's own file 2023-10-07 15:16:39 +02:00
Jonas Kvinge
52ba1ce17f PlaylistView: Fix build with Qt 5 2023-10-07 15:04:00 +02:00
Jonas Kvinge
604a246fe8 PlaylistProxyStyle: Use CE_HeaderLabel instead of CE_Header 2023-10-07 14:50:46 +02:00
Jonas Kvinge
e172c4871c PlaylistView: Create proxy style based on application style
Fixes #1275
2023-10-07 14:48:40 +02:00
Jonas Kvinge
2a9b32690d Update Changelog 2023-10-07 02:52:47 +02:00
Jonas Kvinge
76fa4745d0 GstEnginePipeline: Only update last known position when possible
Fixes flaky seeking where gst_element_query_position() returns -1 when seeking.
2023-10-07 02:47:12 +02:00
Strawbs Bot
6f4d26e9d3 Update translations 2023-10-04 00:33:42 +02:00
Jonas Kvinge
f40f43861d EngineBase: Use enum class for TrackChangeType 2023-10-03 20:18:52 +02:00
Jonas Kvinge
717ebbbb24 CI: Install openssh-clients for openSUSE 2023-10-02 21:32:46 +02:00
Jonas Kvinge
79c69e1b1e CollectionWatcher: Match extension case-insensitive 2023-10-02 17:39:10 +02:00
Jonas Kvinge
8fc95e08dc CollectionWatcher: Ignore compressed files
Fixes #1274
2023-10-02 17:23:47 +02:00
Jonas Kvinge
3f06528ba3 Fix version without git tags 2023-10-02 17:04:15 +02:00
Jonas Kvinge
0e44b10eec Add Ko-fi to funding options 2023-09-28 16:55:25 +02:00
Jonas Kvinge
d74fe92ce8 Use system KDSingleApplication when available 2023-09-27 20:19:43 +02:00
Jonas Kvinge
8037948f7f Dmg: Remove extra macdeployqt executable parameters
macdeployqt is patched and should handle .so libraries too now.
2023-09-26 23:46:11 +02:00
Jonas Kvinge
ab29170972 CI: Disable macOS homebrew build 2023-09-26 23:45:11 +02:00
Jonas Kvinge
6f843e2499 CI: Disable macports build
Issue #1278
2023-09-26 18:22:43 +02:00
Jonas Kvinge
6b8a816ce6 macgstcopy: Check for both .dylib and .so extensions for plugins
Require at least coreelements to be found in plugins directory
2023-09-26 17:04:20 +02:00
Jonas Kvinge
2c0541fb79 CI: Delete conflicting ICU case-insensitive 2023-09-26 00:34:13 +02:00
Jonas Kvinge
cb22890d79 CI: Simplify setting CMAKE_BUILD_TYPE and ENABLE_WIN32_CONSOLE 2023-09-26 00:34:13 +02:00
Jonas Kvinge
5a9346cc80 Add libebur128 to strawberry.spec 2023-09-25 20:50:43 +02:00
Jonas Kvinge
1c85220ffa Add libebur128-dev to debian control 2023-09-25 20:50:43 +02:00
Jonas Kvinge
39d4818def CI: Fix PPA upload 2023-09-25 20:16:26 +02:00
Jonas Kvinge
db0cc66ba0 CI: Fix Mageia rpm upload 2023-09-25 20:15:56 +02:00
Strawbs Bot
c10a64f08a Update translations 2023-09-25 02:37:58 +02:00
Jonas Kvinge
5a3d60b203 Turn on git revision 2023-09-25 00:10:41 +02:00
Jonas Kvinge
28b9bf1f76 Release 1.0.20 2023-09-25 00:03:45 +02:00
109 changed files with 8199 additions and 7643 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,4 @@
github: jonaski
patreon: jonaskvinge
ko_fi: jonaskvinge
custom: https://paypal.me/jonaskvinge

View File

@@ -5,6 +5,7 @@ jobs:
build-opensuse:
name: Build openSUSE
if: github.repository != 'strawberrymusicplayer/strawberry-private'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -38,6 +39,7 @@ jobs:
make
cmake
gettext-tools
openssh-clients
rsync
glibc-devel
libboost_headers-devel
@@ -59,6 +61,7 @@ jobs:
libgpod-devel
libmtp-devel
libchromaprint-devel
fftw3-devel
libebur128-devel
desktop-file-utils
update-desktop-files
@@ -102,6 +105,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Add safe git directory
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
- name: Create Build Environment
@@ -139,24 +143,25 @@ jobs:
/usr/src/packages/SRPMS/*.rpm
/usr/src/packages/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/source ${{secrets.BUILDS_PATH}}/opensuse/${{env.opensuse_subdir}}
- name: rsync source
if: (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /usr/src/packages/SOURCES/*.xz ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/source/
- name: rsync rpms
if: (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version != 'tumbleweed'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version != 'tumbleweed'
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /usr/src/packages/SRPMS/*.rpm /usr/src/packages/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/opensuse/${{env.opensuse_subdir}}/
build-fedora:
name: Build Fedora
if: github.repository != 'strawberrymusicplayer/strawberry-private'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -221,6 +226,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Add safe git directory
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
- name: Create Build Environment
@@ -249,21 +255,22 @@ jobs:
/github/home/rpmbuild/SRPMS/*.rpm
/github/home/rpmbuild/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/fedora/${{matrix.fedora_version}}
- name: rsync
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/fedora/${{matrix.fedora_version}}/
build-openmandriva:
name: Build OpenMandriva
if: github.repository != 'strawberrymusicplayer/strawberry-private'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -319,7 +326,7 @@ jobs:
lib64Qt6Test-devel
qt6-cmake
qt6-qtbase-tools
qt6-qttools
qt6-qttools-linguist
desktop-file-utils
appstream
appstream-util
@@ -328,6 +335,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Add safe git directory
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
- name: Create Build Environment
@@ -356,21 +364,22 @@ jobs:
/github/home/rpmbuild/SRPMS/*.rpm
/github/home/rpmbuild/RPMS/x86_64/*.rpm
- name: SSH key setup
if: matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/openmandriva/${{matrix.openmandriva_version}}
- name: rsync
if: matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/openmandriva/${{matrix.openmandriva_version}}/
build-mageia:
name: Build Mageia
if: github.repository != 'strawberrymusicplayer/strawberry-private'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -428,6 +437,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Add safe git directory
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
- name: Create Build Environment
@@ -452,25 +462,26 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: mageia-${{matrix.mageia_version}}
path:
path: |
/github/home/rpmbuild/SRPMS/*.rpm
/github/home/rpmbuild/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/mageia/${{matrix.mageia_version}}
- name: rsync
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/mageia/${{matrix.mageia_version}}/
build-debian:
name: Build Debian
if: github.repository != 'strawberrymusicplayer/strawberry-private'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -534,6 +545,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Add safe git directory
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
- name: Create Build Environment
@@ -550,21 +562,22 @@ jobs:
name: debian-${{matrix.debian_version}}
path: "*.deb"
- name: SSH key setup
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/debian/${{matrix.debian_version}}
- name: rsync
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var *.deb ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/debian/${{matrix.debian_version}}/
build-ubuntu:
name: Build Ubuntu
if: github.repository != 'strawberrymusicplayer/strawberry-private'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -631,6 +644,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Add safe git directory
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
- name: Create Build Environment
@@ -649,22 +663,22 @@ jobs:
*.deb
*.ddeb
- name: SSH key setup
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/ubuntu/${{matrix.ubuntu_version}}
- name: rsync
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var *.deb *.ddeb ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/ubuntu/${{matrix.ubuntu_version}}/
upload-ubuntu-ppa:
name: Upload Ubuntu PPA
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -730,6 +744,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Add safe git directory
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
- name: Create Build Environment
@@ -744,24 +759,26 @@ jobs:
gpg_private_key: ${{secrets.UBUNTU_PPA_GPG_PRIVATE_KEY}}
- name: dpkg-buildpackage
run: dpkg-buildpackage -S -d -k573D197B5EA20EDF
- name: Upload Unstable PPA
run: dput ppa:jonaski/strawberry-unstable ../*_source.changes
- name: Set is release
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
- name: Get release version
run: echo "release_version=$(git describe --tags --exact-match ${GITHUB_SHA} 2>/dev/null | head -1)" >> $GITHUB_ENV
- name: Upload Unstable PPA
if: env.is_release != '1' && env.release_version == ''
run: dput ppa:jonaski/strawberry-unstable ../*_source.changes
- name: Upload Stable PPA
if: env.is_release == '1' && env.release_version != ''
run: dput ppa:jonaski/strawberry ../*_source.changes
build-macos:
name: Build macOS
build-macos-public:
name: Build macOS Public
if: github.repository != 'strawberrymusicplayer/strawberry-private'
strategy:
fail-fast: false
matrix:
runner: [ 'macos-11', 'macos-arm64' ]
runner: [ 'macos-11' ]
buildtype: [ 'release' ]
runs-on: ${{ matrix.runner }}
@@ -779,7 +796,6 @@ jobs:
run: echo "cmake_buildtype=$(echo ${{env.buildtype}} | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}')" >> $GITHUB_ENV
- name: Uninstall homebrew
if: matrix.runner == 'macos-11'
run: |
curl -sfLO https://raw.githubusercontent.com/Homebrew/install/master/uninstall.sh
chmod +x ./uninstall.sh
@@ -790,6 +806,14 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Import certificate file
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE }}
p12-password: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
- name: Download macOS dependencies
run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-macos-dependencies/releases/latest/download/strawberry-macos-${{env.arch}}-${{env.buildtype}}.tar.xz
@@ -803,9 +827,6 @@ jobs:
- name: Update PATH
run: echo "${{env.prefix_path}}/bin" >> $GITHUB_PATH
- name: Change rpath for lconvert
run: sudo install_name_tool -change @rpath/QtCore.framework/Versions/A/QtCore /opt/strawberry_macos_${{env.arch}}_release/lib/QtCore.framework/QtCore /opt/strawberry_macos_${{env.arch}}_release/bin/lconvert
- name: Create Build Environment
run: cmake -E make_directory build
@@ -827,7 +848,7 @@ jobs:
-DENABLE_DBUS=OFF
-DICU_ROOT="${{env.prefix_path}}"
-DFFTW3_DIR="${{env.prefix_path}}"
-DCREATEDMG_SKIP_JENKINS=$(test "${{matrix.runner}}" = "macos-arm64" && echo "ON" || echo "OFF")
-DAPPLE_DEVELOPER_ID=$(test '${{github.repository}}' = 'strawberrymusicplayer/strawberry' && test '${{github.event.pull_request.base.repo.full_name}}' = '${{github.event.pull_request.head.repo.full_name}}' && echo "383J84DVB6" || echo "")
- name: Build
run: cmake --build build --config Release --parallel 4
@@ -836,30 +857,35 @@ jobs:
working-directory: build
run: make install
- name: Manually copy files not handled by macdeployqt
working-directory: build
run: |
mkdir -p strawberry.app/Contents/Frameworks/
cp ${{env.prefix_path}}/lib/libsoup-3.0.0.dylib strawberry.app/Contents/Frameworks/
- name: Deploy
env:
GIO_EXTRA_MODULES: ${{env.prefix_path}}/lib/gio/modules
GST_PLUGIN_SCANNER: ${{env.prefix_path}}/libexec/gstreamer-1.0/gst-plugin-scanner
GST_PLUGIN_PATH: ${{env.prefix_path}}/lib/gstreamer-1.0
LIBSOUP_LIBRARY_PATH: ${{env.prefix_path}}/lib/libsoup-3.0.0.dylib
working-directory: build
run: make deploy
- name: Codesign libsoup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
working-directory: build
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libsoup-3.0.0.dylib,libnghttp2.14.dylib,libpsl.5.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libzstd.1.dylib} strawberry.app
- name: Deploy check
working-directory: build
run: make deploycheck
- name: Verify code-signing
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
working-directory: build
run: codesign --deep -v strawberry.app
- name: Create DMG
working-directory: build
run: make dmg
- name: SSH key setup
if: matrix.runner == 'macos-11' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
@@ -872,6 +898,7 @@ jobs:
run: echo "release_version=$(git describe --tags --exact-match ${GITHUB_SHA} 2>/dev/null | head -1)" >> $GITHUB_ENV
- name: Set Upload path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
run: |
if [ "${{env.is_release}}" = "1" ] && ! [ "${{env.release_version}}" = "" ]; then
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_ENV
@@ -880,106 +907,52 @@ jobs:
fi
- name: Create server path
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{env.upload_path}}
- name: rsync
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{env.upload_path}}/
build-macos-homebrew:
name: Build macOS Homebrew
runs-on: macos-11
build-macos-private:
name: Build macOS Private
if: github.repository == 'strawberrymusicplayer/strawberry-private'
strategy:
fail-fast: false
matrix:
runner: [ 'macos-arm64' ]
buildtype: [ 'release' ]
runs-on: ${{ matrix.runner }}
steps:
- name: Remove installed brew packages
run: brew uninstall $(brew list)
- name: Set arch
shell: bash
run: echo "arch=$(uname -m)" >> $GITHUB_ENV
- name: Update packages
run: brew update
- name: Set buildtype
run: echo "buildtype=$(echo ${{matrix.buildtype}} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
- name: Upgrade packages
run: brew upgrade || true
- name: brew tap
run: brew tap homebrew/core
- name: Install packages
run: brew install pkg-config cmake ninja meson bison flex wget create-dmg gettext boost protobuf protobuf-c rsync glib glib-openssl glib-utils glib-networking gdk-pixbuf gobject-introspection orc libffi openssl sqlite fftw libmtp libplist libxml2 libsoup libogg libvorbis flac wavpack opus speex mpg123 lame twolame taglib chromaprint libebur128 libbs2b libcdio libopenmpt faad2 faac fdk-aac musepack game-music-emu qt6 || true
- name: Use modified gstreamer plugin formulas
run: |
wget https://files.strawberrymusicplayer.org/patches/gstreamer.rb
wget https://files.strawberrymusicplayer.org/patches/gst-plugins-base.rb
wget https://files.strawberrymusicplayer.org/patches/gst-plugins-good.rb
wget https://files.strawberrymusicplayer.org/patches/gst-plugins-bad.rb
wget https://files.strawberrymusicplayer.org/patches/gst-plugins-ugly.rb
wget https://files.strawberrymusicplayer.org/patches/gst-libav.rb
mv gstreamer.rb gst-plugins-{base,good,bad,ugly}.rb gst-libav.rb /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/g/
- name: Build and install gstreamer
env:
HOMEBREW_NO_INSTALL_FROM_API: 1
run: brew reinstall --build-from-source gstreamer
- name: Build and install gst-plugins-base
env:
HOMEBREW_NO_INSTALL_FROM_API: 1
run: brew reinstall --build-from-source gst-plugins-base
- name: Build and install gst-plugins-good
env:
HOMEBREW_NO_INSTALL_FROM_API: 1
run: brew reinstall --build-from-source gst-plugins-good
- name: Build and install gst-plugins-bad
env:
HOMEBREW_NO_INSTALL_FROM_API: 1
run: brew reinstall --build-from-source gst-plugins-bad
- name: Build and install gst-plugins-ugly
env:
HOMEBREW_NO_INSTALL_FROM_API: 1
run: brew reinstall --build-from-source gst-plugins-ugly
- name: Build and install gst-libav
env:
HOMEBREW_NO_INSTALL_FROM_API: 1
run: brew reinstall --build-from-source gst-libav
- name: Build libgpod
env:
PERL_MM_USE_DEFAULT: 1
run: |
git clone https://github.com/strawberrymusicplayer/strawberry-libgpod
cd strawberry-libgpod
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j 4
sudo make install
- name: Replace relative library @loader_path paths - macdeployqt does not understand these.
run: |
for library in $(find -E /usr/local/Cellar -type f -regex '.*/lib/.*\.framework/Versions/A/Qt(Core|Concurrent|Network|Sql|Widgets|Gui|DBus)' -o -name '*.dylib'); do
library_paths=$(otool -L "${library}" | sed -n "s/^\t\(.*\) (compatibility version [0-9]*\.[0-9]*\.[0-9]*, current version [0-9]*\.[0-9]*\.[0-9]*)/\1/p")
for library_path in ${library_paths}; do
if ! [ "$(echo "${library_path}" | grep "^@loader_path" || true)" = "" ]; then
new_library_path=$(echo "${library_path}" | sed -E 's/@loader_path(\/\.\.)+\/opt/\/usr\/local\/opt/g')
if ! [ "${new_library_path}" = "" ] && ! [ "${new_library_path}" = "${library_path}" ] && [ -e "${new_library_path}" ]; then
sudo install_name_tool -change "${library_path}" "${new_library_path}" "${library}"
else
echo "${library} points to ${library_path}, could not resolve to absolute path."
fi
fi
done
done
- name: Set cmake buildtype
run: echo "cmake_buildtype=$(echo ${{env.buildtype}} | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}')" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Unlock keychain
run: security unlock-keychain -p ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
- name: Set prefix path
run: echo "prefix_path=/opt/strawberry_macos_${{env.arch}}_${{env.buildtype}}" >> $GITHUB_ENV
- name: Update PATH
run: echo "${{env.prefix_path}}/bin" >> $GITHUB_PATH
- name: Create Build Environment
run: cmake -E make_directory build
@@ -987,8 +960,22 @@ jobs:
- name: Configure CMake
env:
MACOSX_DEPLOYMENT_TARGET: 11.0
PKG_CONFIG_PATH: /usr/local/lib/pkgconfig
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_WITH_QT6=ON -DBUILD_WERROR=OFF -DUSE_BUNDLE=ON -DCMAKE_PREFIX_PATH=/usr/local/opt/qt6/lib/cmake -DICU_ROOT=/usr/local/opt/icu4c -DPROTOBUF_INCLUDE_DIRS=/usr/local/include
PKG_CONFIG_PATH: ${{env.prefix_path}}/lib/pkgconfig
LDFLAGS: -L${{env.prefix_path}}/lib -Wl,-rpath,${{env.prefix_path}}/lib
run: >
cmake
--log-level="DEBUG"
-S .
-B build
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
-DCMAKE_PREFIX_PATH="${{env.prefix_path}}/lib/cmake"
-DBUILD_WITH_QT6=ON
-DBUILD_WERROR=OFF
-DUSE_BUNDLE=ON
-DENABLE_DBUS=OFF
-DICU_ROOT="${{env.prefix_path}}"
-DFFTW3_DIR="${{env.prefix_path}}"
-DAPPLE_DEVELOPER_ID="383J84DVB6"
- name: Build
run: cmake --build build --config Release --parallel 4
@@ -997,22 +984,12 @@ jobs:
working-directory: build
run: make install
- name: Remove problematic files
run: |
sudo rm -rf /usr/local/opt/qt6/share/qt/plugins/virtualkeyboard /usr/local/opt/qt6/share/qt/plugins/platforminputcontexts
sudo rm -f /usr/local/Cellar/qt/*/share/qt/plugins/imageformats/libqpdf.dylib
- name: Manually copy files not handled by macdeployqt
working-directory: build
run: |
mkdir -p strawberry.app/Contents/Frameworks/
cp /usr/local/lib/{libsoup-3.0.0.dylib,libswresample.4.dylib,libswscale.7.dylib,libpostproc.57.dylib} strawberry.app/Contents/Frameworks/
- name: Deploy
env:
GIO_EXTRA_MODULES: /usr/local/lib/gio/modules
GST_PLUGIN_SCANNER: /usr/local/opt/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner
GST_PLUGIN_PATH: /usr/local/lib/gstreamer-1.0
GIO_EXTRA_MODULES: ${{env.prefix_path}}/lib/gio/modules
GST_PLUGIN_SCANNER: ${{env.prefix_path}}/libexec/gstreamer-1.0/gst-plugin-scanner
GST_PLUGIN_PATH: ${{env.prefix_path}}/lib/gstreamer-1.0
LIBSOUP_LIBRARY_PATH: ${{env.prefix_path}}/lib/libsoup-3.0.0.dylib
working-directory: build
run: make deploy
@@ -1020,119 +997,54 @@ jobs:
working-directory: build
run: make deploycheck
- name: Verify code-signing
working-directory: build
run: codesign --deep -v strawberry.app
- name: Create DMG
working-directory: build
run: make dmg
- name: Set is release
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
build-macos-macports:
name: Build macOS Macports
runs-on: macos-11
steps:
- name: Install macports
- name: Set Upload path
run: |
macports_version=$(wget -q -O- 'https://github.com/macports/macports-base' | sed -n 's,.*releases/tag/\([^"&;]*\)".*,\1,p' | sed 's/^v//g' | sort -V | tail -1)
wget https://github.com/macports/macports-base/releases/download/v${macports_version}/MacPorts-${macports_version}-11-BigSur.pkg
sudo installer -pkg ./MacPorts-${macports_version}-11-BigSur.pkg -target /
if [ "${{env.is_release}}" = "1" ]; then
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_ENV
else
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_ENV
fi
- name: Uninstall homebrew
run: |
curl -sLO https://raw.githubusercontent.com/Homebrew/install/master/uninstall.sh
chmod +x ./uninstall.sh
sudo ./uninstall.sh --force
- name: Create server path
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{env.upload_path}}
- name: Update macports
run: sudo /opt/local/bin/port -v selfupdate
- name: Install packages
run: sudo /opt/local/bin/port -N -p install wget gettext glib2 pkgconfig cmake boost protobuf-cpp sqlite3 chromaprint libebur128 fftw taglib libcdio libmtp
- name: Install gstreamer
run: sudo /opt/local/bin/port -N -p install gstreamer1 gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good gstreamer1-gst-plugins-bad gstreamer1-gst-plugins-ugly gstreamer1-gst-libav
- name: Install gst-plugins-bad
run: sudo /opt/local/bin/port -N -p install gstreamer1-gst-plugins-bad +faac
- name: Install qt6-qtbase
run: sudo /opt/local/bin/port -N -p install qt6-qtbase
- name: Install qt6-qttools
run: sudo /opt/local/bin/port -N -p install qt6-qttools || true
- name: Install qt6-sqlite-plugin
run: sudo /opt/local/bin/port -N -p install qt6-sqlite-plugin
- name: Install libgpod
run: sudo /opt/local/bin/port -N -p install libgpod || true
- name: Add /opt/local/bin to PATH
run: echo "/opt/local/bin" >> $GITHUB_PATH
- name: Install create-dmg
shell: bash
run: |
mkdir -p ~/build
cd ~/build
git clone https://github.com/create-dmg/create-dmg
cd create-dmg
sudo make install
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Create Build Environment
run: /opt/local/bin/cmake -E make_directory build
- name: Configure CMake
env:
MACOSX_DEPLOYMENT_TARGET: 11.0
PKG_CONFIG_PATH: /opt/local/lib/pkgconfig
run: /opt/local/libexec/qt6/bin/qt-cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_WITH_QT6=ON -DBUILD_WERROR=OFF -DUSE_BUNDLE=ON -DENABLE_DBUS=OFF
- name: Build
run: /opt/local/bin/cmake --build build --config Release --parallel 4
- name: Install
working-directory: build
run: make install
- name: Deploy
env:
GIO_EXTRA_MODULES: /opt/local/lib/gio/modules
GST_PLUGIN_SCANNER: /opt/local/libexec/gstreamer-1.0/gst-plugin-scanner
GST_PLUGIN_PATH: /opt/local/lib/gstreamer-1.0
working-directory: build
run: make deploy
- name: Deploy check
working-directory: build
run: make deploycheck
- name: Create DMG
working-directory: build
run: make dmg
- name: rsync
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{env.upload_path}}/
build-windows-mingw:
name: Build Windows MinGW
if: github.repository != 'strawberrymusicplayer/strawberry-private'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [ 'i686', 'x86_64' ]
build_type: [ 'debug', 'release' ]
buildtype: [ 'debug', 'release' ]
container:
image: jonaski/strawberry-mxe-${{matrix.arch}}-${{matrix.build_type}}
image: jonaski/strawberry-mxe-${{matrix.arch}}-${{matrix.buildtype}}
steps:
- name: Install rsync
run: zypper -n --gpg-auto-import-keys in rsync
- name: Set cmake buildtype
shell: bash
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Add safe git directory
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
@@ -1143,14 +1055,6 @@ jobs:
- name: Link MXE directory
run: ln -s /strawberry-mxe ~/mxe-shared
- name: Set ENABLE_WIN32_CONSOLE (debug)
if: matrix.build_type == 'debug'
run: echo "win32_console=ON" >> $GITHUB_ENV
- name: Set ENABLE_WIN32_CONSOLE (release)
if: matrix.build_type == 'release'
run: echo "win32_console=OFF" >> $GITHUB_ENV
- name: Run CMake
env:
PKG_CONFIG_PATH: /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/lib/pkgconfig
@@ -1159,12 +1063,12 @@ jobs:
-S .
-B build
-DCMAKE_TOOLCHAIN_FILE="../cmake/Toolchain-${{matrix.arch}}-w64-mingw32-shared.cmake"
-DCMAKE_BUILD_TYPE="${{matrix.build_type}}"
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
-DCMAKE_PREFIX_PATH="/strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/qt6"
-DBUILD_WITH_QT6=ON
-DBUILD_WERROR=OFF
-DARCH="${{matrix.arch}}"
-DENABLE_WIN32_CONSOLE=${{env.win32_console}}
-DENABLE_WIN32_CONSOLE=$(test "${{matrix.buildtype}}" = "debug" && echo "ON" || echo "OFF")
-DENABLE_DBUS=OFF
-DENABLE_LIBGPOD=OFF
-DENABLE_LIBMTP=OFF
@@ -1172,7 +1076,7 @@ jobs:
-DProtobuf_PROTOC_EXECUTABLE="/strawberry-mxe/usr/x86_64-pc-linux-gnu/bin/protoc"
- name: Run Make
run: cmake --build build --config Release --parallel $(nproc)
run: cmake --build build --config "${{env.cmake_buildtype}}" --parallel $(nproc)
- name: Create directories
working-directory: build
@@ -1211,7 +1115,7 @@ jobs:
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/{sqlite3.exe,gst-launch-1.0.exe,gst-play-1.0.exe,gst-discoverer-1.0.exe,libsoup-3.0-0.dll,libnghttp2.dll} .
- name: Copy extra binaries (debug)
if: matrix.build_type == 'debug'
if: matrix.buildtype == 'debug'
working-directory: build
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/{gdb.exe,libreadline8.dll} .
@@ -1231,12 +1135,12 @@ jobs:
-R /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared
- name: Strip binaries
if: matrix.build_type == 'release'
if: matrix.buildtype == 'release'
working-directory: build
run: find . -type f \( -iname \*.dll -o -iname \*.exe \) -exec /strawberry-mxe/usr/bin/${{matrix.arch}}-w64-mingw32.shared-strip {} \;
- name: Strip gdb.exe
if: matrix.build_type == 'debug'
if: matrix.buildtype == 'debug'
working-directory: build
run: /strawberry-mxe/usr/bin/${{matrix.arch}}-w64-mingw32.shared-strip gdb.exe
@@ -1290,37 +1194,42 @@ jobs:
path: build/StrawberrySetup*.exe
- name: SSH key setup
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/windows/mingw
- name: rsync
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/StrawberrySetup*.exe ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/windows/mingw/
build-windows-msvc:
name: Build Windows MSVC
if: github.repository != 'strawberrymusicplayer/strawberry-private'
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
arch: [ 'x86', 'x86_64' ]
build_type: [ 'debug', 'release' ]
buildtype: [ 'debug', 'release' ]
steps:
- name: Set prefix path
shell: bash
run: |
echo "prefix_path_backslash=c:\strawberry_msvc_${{matrix.arch}}_${{matrix.build_type}}" >> $GITHUB_ENV
echo "prefix_path_forwardslash=c:/strawberry_msvc_${{matrix.arch}}_${{matrix.build_type}}" >> $GITHUB_ENV
echo "prefix_path_unix=/c/strawberry_msvc_${{matrix.arch}}_${{matrix.build_type}}" >> $GITHUB_ENV
echo "prefix_path_backslash=c:\strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}" >> $GITHUB_ENV
echo "prefix_path_forwardslash=c:/strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}" >> $GITHUB_ENV
echo "prefix_path_unix=/c/strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}" >> $GITHUB_ENV
- name: Set cmake buildtype
shell: bash
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
- name: Create downloads directory
shell: cmd
@@ -1329,12 +1238,12 @@ jobs:
- name: Download Windows MSVC dependencies
shell: cmd
working-directory: downloads
run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-msvc-dependencies/releases/latest/download/strawberry-msvc-${{matrix.arch}}-${{matrix.build_type}}.tar.xz
run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-msvc-dependencies/releases/latest/download/strawberry-msvc-${{matrix.arch}}-${{matrix.buildtype}}.tar.xz
- name: Extract Windows MSVC dependencies
shell: bash
working-directory: downloads
run: tar -C /c -xf strawberry-msvc-${{matrix.arch}}-${{matrix.build_type}}.tar.xz
run: tar -C /c -xf strawberry-msvc-${{matrix.arch}}-${{matrix.buildtype}}.tar.xz
- name: Update PATH
run: echo "${{env.prefix_path_backslash}}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
@@ -1343,19 +1252,15 @@ jobs:
shell: bash
run: cp /c/strawberry/c/bin/{patch.exe,strip.exe,strings.exe,objdump.exe} ${{env.prefix_path_unix}}/bin
- name: Uninstall conflicting mingw
shell: cmd
run: choco uninstall mingw -y -f
- name: Delete conflicting files
shell: bash
run: rm -rf /c/strawberry/c "/c/program files/OpenSSL" "/c/program files/postgresql"
run: rm -rf /c/msys64 /c/mingw32 /c/mingw64 /c/strawberry/c "/c/program files/OpenSSL" "/c/program files/postgresql"
- name: Delete conflicting icu
shell: bash
run: |
find "/c/program files (x86)/windows kits/" -name 'icu*.lib' -delete
find "/c/program files (x86)/windows kits/" -name 'icu*.h' -delete
find "/c/program files (x86)/windows kits/" -iname 'icu*.lib' -delete
find "/c/program files (x86)/windows kits/" -iname 'icu*.h' -delete
- name: Download NSIS LockedList plugin
shell: cmd
@@ -1409,6 +1314,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Add safe git directory
shell: bash
@@ -1435,10 +1341,9 @@ jobs:
-S .
-B build
-G "Ninja"
-DCMAKE_BUILD_TYPE="${{matrix.build_type}}"
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
-DCMAKE_PREFIX_PATH="${{env.prefix_path_forwardslash}}/lib/cmake"
-DBUILD_WITH_QT6=ON
-DBUILD_WERROR=OFF
-DARCH="${{matrix.arch}}"
-DENABLE_WIN32_CONSOLE=${{env.win32_console}}
-DUSE_TAGLIB=ON
@@ -1449,7 +1354,7 @@ jobs:
shell: cmd
env:
CL: "/MP"
run: cmake --build build --config ${{matrix.build_type}} --parallel 4
run: cmake --build build --config "${{env.cmake_buildtype}}" --parallel 4
- name: Copy extra binaries
shell: cmd
@@ -1651,8 +1556,8 @@ jobs:
rsync-windows-msvc-builds:
name: Rsync Windows MSVC builds
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
needs:
- build-windows-msvc
steps:
@@ -1680,8 +1585,8 @@ jobs:
upload-release:
name: Upload release
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
needs:
- build-opensuse
- build-fedora
@@ -1691,8 +1596,11 @@ jobs:
- build-windows-mingw
- build-windows-msvc
steps:
- name: Install rsync
run: sudo apt install -y rsync
- name: Install packages
env:
DEBIAN_FRONTEND: noninteractive
run: sudo apt install -y git rsync hub
- name: Checkout
uses: actions/checkout@v4

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "3rdparty/kdsingleapplication/KDSingleApplication"]
path = 3rdparty/kdsingleapplication/KDSingleApplication
url = https://github.com/KDAB/KDSingleApplication.git
branch = master

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.7)
set(SOURCES kdsingleapplication.cpp kdsingleapplication_localsocket.cpp)
set(HEADERS kdsingleapplication.h kdsingleapplication_localsocket_p.h)
set(SOURCES KDSingleApplication/src/kdsingleapplication.cpp KDSingleApplication/src/kdsingleapplication_localsocket.cpp)
set(HEADERS KDSingleApplication/src/kdsingleapplication.h KDSingleApplication/src/kdsingleapplication_localsocket_p.h)
qt_wrap_cpp(MOC ${HEADERS})
add_library(kdsingleapplication STATIC ${SOURCES} ${MOC})
target_compile_definitions(kdsingleapplication PRIVATE -DKDSINGLEAPPLICATION_STATIC_BUILD)

View File

@@ -1,6 +0,0 @@
KDSingleApplication is (C) 2019-2023, Klarälvdalens Datakonsult AB,
and is available under the terms of the MIT license.
See the full license text in the LICENSES folder.
Contact KDAB at <info@kdab.com> to inquire about commercial licensing.

View File

@@ -1,11 +0,0 @@
Copyright (c) <year> <owner>.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,53 +0,0 @@
# KDSingleApplication
`KDSingleApplication` is a helper class for single-instance policy applications
written by [KDAB](https://www.kdab.com).
## Usage
Currently the documentation is woefully lacking, but see the examples or tests
for inspiration. Basically it involves:
1. Create a `Q(Core|Gui)Application` object.
2. Create a `KDSingleApplication` object.
3. Check if the current instance is *primary* (or "master") or
*secondary* (or "slave") by calling `isPrimaryInstance`:
* the *primary* instance needs to listen from messages coming from the
secondary instances, by connecting a slot to the `messageReceived` signal;
* the *secondary* instances can send messages to the primary instance
by calling `sendMessage`.
## Licensing
KDSingleApplication is (C) 2019-2023, Klarälvdalens Datakonsult AB, and is available
under the terms of the [MIT license](LICENSES/MIT.txt).
Contact KDAB at <info@kdab.com> if you need different licensing options.
## Get Involved
KDAB will happily accept external contributions.
Please submit your contributions or issue reports from our GitHub space at
<https://github.com/KDAB/KDSingleApplication>.
## About KDAB
KDSingleApplication is supported and maintained by Klarälvdalens Datakonsult AB (KDAB).
The KDAB Group is the global No.1 software consultancy for Qt, C++ and
OpenGL applications across desktop, embedded and mobile platforms.
The KDAB Group provides consulting and mentoring for developing Qt applications
from scratch and in porting from all popular and legacy frameworks to Qt.
We continue to help develop parts of Qt and are one of the major contributors
to the Qt Project. We can give advanced or standard trainings anywhere
around the globe on Qt as well as C++, OpenGL, 3D and more.
Please visit <https://www.kdab.com> to meet the people who write code like this.
Stay up-to-date with KDAB product announcements:
* [KDAB Newsletter](https://news.kdab.com)
* [KDAB Blogs](https://www.kdab.com/category/blogs)
* [KDAB on Twitter](https://twitter.com/KDABQt)

View File

@@ -1,106 +0,0 @@
/*
This file is part of KDSingleApplication.
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
SPDX-License-Identifier: MIT
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#include "kdsingleapplication.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QFileInfo>
// TODO: make this pluggable.
#include "kdsingleapplication_localsocket_p.h"
// Avoiding dragging in Qt private APIs for now, so this does not inherit
// from QObjectPrivate.
class KDSingleApplicationPrivate
{
public:
explicit KDSingleApplicationPrivate(const QString &name, KDSingleApplication *q);
QString name() const
{
return m_name;
}
bool isPrimaryInstance() const
{
return m_impl.isPrimaryInstance();
}
bool sendMessage(const QByteArray &message, int timeout)
{
return m_impl.sendMessage(message, timeout);
}
private:
Q_DECLARE_PUBLIC(KDSingleApplication)
KDSingleApplication *q_ptr;
QString m_name;
KDSingleApplicationLocalSocket m_impl;
};
KDSingleApplicationPrivate::KDSingleApplicationPrivate(const QString &name, KDSingleApplication *q)
: q_ptr(q)
, m_name(name)
, m_impl(name)
{
if (Q_UNLIKELY(name.isEmpty()))
qFatal("KDSingleApplication requires a non-empty application name");
if (isPrimaryInstance()) {
QObject::connect(&m_impl, &KDSingleApplicationLocalSocket::messageReceived,
q, &KDSingleApplication::messageReceived);
}
}
static QString extractExecutableName(const QString &applicationFilePath)
{
return QFileInfo(applicationFilePath).fileName();
}
KDSingleApplication::KDSingleApplication(QObject *parent)
: KDSingleApplication(extractExecutableName(QCoreApplication::applicationFilePath()), parent)
{
}
KDSingleApplication::KDSingleApplication(const QString &name, QObject *parent)
: QObject(parent)
, d_ptr(new KDSingleApplicationPrivate(name, this))
{
}
QString KDSingleApplication::name() const
{
Q_D(const KDSingleApplication);
return d->name();
}
bool KDSingleApplication::isPrimaryInstance() const
{
Q_D(const KDSingleApplication);
return d->isPrimaryInstance();
}
bool KDSingleApplication::sendMessage(const QByteArray &message)
{
return sendMessageWithTimeout(message, 5000);
}
bool KDSingleApplication::sendMessageWithTimeout(const QByteArray &message, int timeout)
{
Q_ASSERT(!isPrimaryInstance());
Q_D(KDSingleApplication);
return d->sendMessage(message, timeout);
}
KDSingleApplication::~KDSingleApplication() = default;

View File

@@ -1,48 +0,0 @@
/*
This file is part of KDSingleApplication.
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
SPDX-License-Identifier: MIT
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#ifndef KDSINGLEAPPLICATION_H
#define KDSINGLEAPPLICATION_H
#include <QtCore/QObject>
#include <memory>
#include "kdsingleapplication_lib.h"
class KDSingleApplicationPrivate;
class KDSINGLEAPPLICATION_EXPORT KDSingleApplication : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(bool isPrimaryInstance READ isPrimaryInstance CONSTANT)
public:
explicit KDSingleApplication(QObject *parent = nullptr);
explicit KDSingleApplication(const QString &name, QObject *parent = nullptr);
~KDSingleApplication();
QString name() const;
bool isPrimaryInstance() const;
public Q_SLOTS:
// avoid default arguments and overloads, as they don't mix with connections
bool sendMessage(const QByteArray &message);
bool sendMessageWithTimeout(const QByteArray &message, int timeout);
Q_SIGNALS:
void messageReceived(const QByteArray &message);
private:
Q_DECLARE_PRIVATE(KDSingleApplication)
std::unique_ptr<KDSingleApplicationPrivate> d_ptr;
};
#endif // KDSINGLEAPPLICATION_H

View File

@@ -1,23 +0,0 @@
/*
This file is part of KDSingleApplication.
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
SPDX-License-Identifier: MIT
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#ifndef KDSINGLEAPPLICATION_LIB_H
#define KDSINGLEAPPLICATION_LIB_H
#include <QtCore/QtGlobal>
#if defined(KDSINGLEAPPLICATION_STATIC_BUILD)
#define KDSINGLEAPPLICATION_EXPORT
#elif defined(KDSINGLEAPPLICATION_SHARED_BUILD)
#define KDSINGLEAPPLICATION_EXPORT Q_DECL_EXPORT
#else
#define KDSINGLEAPPLICATION_EXPORT Q_DECL_IMPORT
#endif
#endif // KDSINGLEAPPLICATION_LIB_H

View File

@@ -1,324 +0,0 @@
/*
This file is part of KDSingleApplication.
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
SPDX-License-Identifier: MIT
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#include "kdsingleapplication_localsocket_p.h"
#include <QtCore/QDir>
#include <QtCore/QDeadlineTimer>
#include <QtCore/QTimer>
#include <QtCore/QLockFile>
#include <QtCore/QDataStream>
#include <QtCore/QtDebug>
#include <QtCore/QLoggingCategory>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
#include <chrono>
#include <algorithm>
#ifdef Q_OS_UNIX
// for ::getuid()
# include <sys/types.h>
# include <unistd.h>
#endif
#ifdef Q_OS_WIN
# include <qt_windows.h>
#endif
namespace {
static constexpr auto LOCALSOCKET_CONNECTION_TIMEOUT = std::chrono::seconds(5);
static constexpr char LOCALSOCKET_PROTOCOL_VERSION = 2;
} // namespace
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-declarations"
#endif
Q_LOGGING_CATEGORY(kdsaLocalSocket, "kdsingleapplication.localsocket", QtWarningMsg)
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#endif
KDSingleApplicationLocalSocket::KDSingleApplicationLocalSocket(const QString &name, QObject *parent)
: QObject(parent)
{
#if defined(Q_OS_UNIX)
/* cppcheck-suppress useInitializationList */
m_socketName = QStringLiteral("kdsingleapp-%1-%2")
.arg(::getuid())
.arg(name);
#elif defined(Q_OS_WIN)
// I'm not sure of a "global session identifier" on Windows; are
// multiple logins from the same user a possibility? For now, following this:
// https://docs.microsoft.com/en-us/windows/desktop/devnotes/getting-the-session-id-of-the-current-process
DWORD sessionId;
BOOL haveSessionId = ProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
m_socketName = QString::fromUtf8("kdsingleapp-%1-%2")
.arg(haveSessionId ? sessionId : 0)
.arg(name);
#else
#error "KDSingleApplication has not been ported to this platform"
#endif
const QString lockFilePath =
QDir::tempPath() + QLatin1Char('/') + m_socketName + QLatin1String(".lock");
qCDebug(kdsaLocalSocket) << "Socket name is" << m_socketName;
qCDebug(kdsaLocalSocket) << "Lock file path is" << lockFilePath;
std::unique_ptr<QLockFile> lockFile(new QLockFile(lockFilePath));
lockFile->setStaleLockTime(0);
if (!lockFile->tryLock()) {
// someone else has the lock => we're secondary
qCDebug(kdsaLocalSocket) << "Secondary instance";
return;
}
qCDebug(kdsaLocalSocket) << "Primary instance";
std::unique_ptr<QLocalServer> server = std::make_unique<QLocalServer>();
if (!server->listen(m_socketName)) {
// maybe the primary crashed, leaving a stale socket; delete it and try again
QLocalServer::removeServer(m_socketName);
if (!server->listen(m_socketName)) {
// TODO: better error handling.
qWarning("KDSingleApplication: unable to make the primary instance listen on %ls: %ls",
qUtf16Printable(m_socketName),
qUtf16Printable(server->errorString()));
return;
}
}
connect(server.get(), &QLocalServer::newConnection,
this, &KDSingleApplicationLocalSocket::handleNewConnection);
m_lockFile = std::move(lockFile);
m_localServer = std::move(server);
}
KDSingleApplicationLocalSocket::~KDSingleApplicationLocalSocket() = default;
bool KDSingleApplicationLocalSocket::isPrimaryInstance() const
{
return m_localServer != nullptr;
}
bool KDSingleApplicationLocalSocket::sendMessage(const QByteArray &message, int timeout)
{
Q_ASSERT(!isPrimaryInstance());
QLocalSocket socket;
qCDebug(kdsaLocalSocket) << "Preparing to send message" << message << "with timeout" << timeout;
QDeadlineTimer deadline(timeout);
// There is an inherent race here with the setup of the server side.
// Even if the socket lock is held by the server, the server may not
// be listening yet. So this connection may fail; keep retrying
// until we hit the timeout.
do {
socket.connectToServer(m_socketName);
if (socket.waitForConnected(static_cast<int>(deadline.remainingTime())))
break;
} while (!deadline.hasExpired());
qCDebug(kdsaLocalSocket) << "Socket state:" << socket.state() << "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
if (deadline.hasExpired()) {
qCWarning(kdsaLocalSocket) << "Connection timed out";
return false;
}
socket.write(&LOCALSOCKET_PROTOCOL_VERSION, 1);
{
QByteArray encodedMessage;
QDataStream ds(&encodedMessage, QIODevice::WriteOnly);
ds << message;
socket.write(encodedMessage);
}
qCDebug(kdsaLocalSocket) << "Wrote message in the socket"
<< "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
// There is no acknowledgement mechanism here.
// Should there be one?
while (socket.bytesToWrite() > 0) {
if (!socket.waitForBytesWritten(static_cast<int>(deadline.remainingTime()))) {
qCWarning(kdsaLocalSocket) << "Message to primary timed out";
return false;
}
}
qCDebug(kdsaLocalSocket) << "Bytes written, now disconnecting"
<< "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
socket.disconnectFromServer();
if (socket.state() == QLocalSocket::UnconnectedState) {
qCDebug(kdsaLocalSocket) << "Disconnected -- success!";
return true;
}
if (!socket.waitForDisconnected(static_cast<int>(deadline.remainingTime()))) {
qCWarning(kdsaLocalSocket) << "Disconnection from primary timed out";
return false;
}
qCDebug(kdsaLocalSocket) << "Disconnected -- success!";
return true;
}
void KDSingleApplicationLocalSocket::handleNewConnection()
{
Q_ASSERT(m_localServer);
QLocalSocket *socket = nullptr;
while ((socket = m_localServer->nextPendingConnection())) {
qCDebug(kdsaLocalSocket) << "Got new connection on" << m_socketName << "state" << socket->state();
Connection c(socket);
socket = c.socket.get();
c.readDataConnection = QObjectConnectionHolder(
connect(socket, &QLocalSocket::readyRead,
this, &KDSingleApplicationLocalSocket::readDataFromSecondary));
c.secondaryDisconnectedConnection = QObjectConnectionHolder(
connect(socket, &QLocalSocket::disconnected,
this, &KDSingleApplicationLocalSocket::secondaryDisconnected));
c.abortConnection = QObjectConnectionHolder(
connect(c.timeoutTimer.get(), &QTimer::timeout,
this, &KDSingleApplicationLocalSocket::abortConnectionToSecondary));
m_clients.push_back(std::move(c));
// Note that by the time we get here, the socket could've already been closed,
// and no signals emitted (hello, Windows!). Read what's already in the socket.
if (readDataFromSecondarySocket(socket))
return;
if (socket->state() == QLocalSocket::UnconnectedState)
secondarySocketDisconnected(socket);
}
}
template<typename Container>
static auto findConnectionBySocket(Container &container, QLocalSocket *socket)
{
auto i = std::find_if(container.begin(),
container.end(),
[socket](const auto &c) { return c.socket.get() == socket; });
Q_ASSERT(i != container.end());
return i;
}
template<typename Container>
static auto findConnectionByTimer(Container &container, QTimer *timer)
{
auto i = std::find_if(container.begin(),
container.end(),
[timer](const auto &c) { return c.timeoutTimer.get() == timer; });
Q_ASSERT(i != container.end());
return i;
}
void KDSingleApplicationLocalSocket::readDataFromSecondary()
{
QLocalSocket *socket = static_cast<QLocalSocket *>(sender());
readDataFromSecondarySocket(socket);
}
bool KDSingleApplicationLocalSocket::readDataFromSecondarySocket(QLocalSocket *socket)
{
auto i = findConnectionBySocket(m_clients, socket);
Connection &c = *i;
c.readData.append(socket->readAll());
qCDebug(kdsaLocalSocket) << "Got more data from a secondary. Data read so far:" << c.readData;
const QByteArray &data = c.readData;
if (data.size() >= 1) {
if (data[0] != LOCALSOCKET_PROTOCOL_VERSION) {
qCDebug(kdsaLocalSocket) << "Got an invalid protocol version";
m_clients.erase(i);
return true;
}
}
QDataStream ds(data);
ds.skipRawData(1);
ds.startTransaction();
QByteArray message;
ds >> message;
if (ds.commitTransaction()) {
qCDebug(kdsaLocalSocket) << "Got a complete message:" << message;
Q_EMIT messageReceived(message);
m_clients.erase(i);
return true;
}
return false;
}
void KDSingleApplicationLocalSocket::secondaryDisconnected()
{
QLocalSocket *socket = static_cast<QLocalSocket *>(sender());
secondarySocketDisconnected(socket);
}
void KDSingleApplicationLocalSocket::secondarySocketDisconnected(QLocalSocket *socket)
{
auto i = findConnectionBySocket(m_clients, socket);
Connection c = std::move(*i);
m_clients.erase(i);
qCDebug(kdsaLocalSocket) << "Secondary disconnected. Data read:" << c.readData;
}
void KDSingleApplicationLocalSocket::abortConnectionToSecondary()
{
QTimer *timer = static_cast<QTimer *>(sender());
auto i = findConnectionByTimer(m_clients, timer);
Connection c = std::move(*i);
m_clients.erase(i);
qCDebug(kdsaLocalSocket) << "Secondary timed out. Data read:" << c.readData;
}
KDSingleApplicationLocalSocket::Connection::Connection(QLocalSocket *_socket)
: socket(_socket)
, timeoutTimer(new QTimer)
{
timeoutTimer->start(LOCALSOCKET_CONNECTION_TIMEOUT);
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif

View File

@@ -1,126 +0,0 @@
/*
This file is part of KDSingleApplication.
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
SPDX-License-Identifier: MIT
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#ifndef KDSINGLEAPPLICATION_LOCALSOCKET_P_H
#define KDSINGLEAPPLICATION_LOCALSOCKET_P_H
#include <QtCore/QObject>
#include <QtCore/QByteArray>
#include <QtCore/QString>
QT_BEGIN_NAMESPACE
class QLockFile;
class QLocalServer;
class QLocalSocket;
class QTimer;
QT_END_NAMESPACE
#include <memory>
#include <vector>
struct QObjectDeleteLater
{
void operator()(QObject *o)
{
o->deleteLater();
}
};
class QObjectConnectionHolder
{
Q_DISABLE_COPY(QObjectConnectionHolder)
QMetaObject::Connection c;
public:
QObjectConnectionHolder()
{
}
explicit QObjectConnectionHolder(QMetaObject::Connection _c)
: c(std::move(_c))
{
}
~QObjectConnectionHolder()
{
QObject::disconnect(c);
}
QObjectConnectionHolder(QObjectConnectionHolder &&other) noexcept
: c(std::exchange(other.c, {}))
{
}
QObjectConnectionHolder &operator=(QObjectConnectionHolder &&other) noexcept
{
QObjectConnectionHolder moved(std::move(other));
swap(moved);
return *this;
}
void swap(QObjectConnectionHolder &other) noexcept
{
using std::swap;
swap(c, other.c);
}
};
class KDSingleApplicationLocalSocket : public QObject
{
Q_OBJECT
public:
explicit KDSingleApplicationLocalSocket(const QString &name,
QObject *parent = nullptr);
~KDSingleApplicationLocalSocket();
bool isPrimaryInstance() const;
public Q_SLOTS:
bool sendMessage(const QByteArray &message, int timeout);
Q_SIGNALS:
void messageReceived(const QByteArray &message);
private:
void handleNewConnection();
void readDataFromSecondary();
bool readDataFromSecondarySocket(QLocalSocket *socket);
void secondaryDisconnected();
void secondarySocketDisconnected(QLocalSocket *socket);
void abortConnectionToSecondary();
QString m_socketName;
std::unique_ptr<QLockFile> m_lockFile; // protects m_localServer
std::unique_ptr<QLocalServer> m_localServer;
struct Connection
{
explicit Connection(QLocalSocket *s);
std::unique_ptr<QLocalSocket, QObjectDeleteLater> socket;
std::unique_ptr<QTimer, QObjectDeleteLater> timeoutTimer;
QByteArray readData;
// socket/timeoutTimer are deleted via deleteLater (as we delete them
// in slots connected to their signals). Before the deleteLater is acted upon,
// they may emit further signals, triggering logic that it's not supposed
// to be triggered (as the Connection has already been destroyed).
// Use this Holder to break the connections.
QObjectConnectionHolder readDataConnection;
QObjectConnectionHolder secondaryDisconnectedConnection;
QObjectConnectionHolder abortConnection;
};
std::vector<Connection> m_clients;
};
#endif // KDSINGLEAPPLICATION_LOCALSOCKET_P_H

View File

@@ -102,12 +102,12 @@ endif()
option(USE_ICU "Use ICU" ON)
find_package(PkgConfig REQUIRED)
find_package(Boost REQUIRED)
find_package(Threads)
find_package(Threads REQUIRED)
find_package(Backtrace)
if(Backtrace_FOUND)
set(HAVE_BACKTRACE ON)
endif()
find_package(Boost REQUIRED)
if(USE_ICU)
find_package(ICU COMPONENTS uc i18n REQUIRED)
if(ICU_FOUND)
@@ -300,10 +300,33 @@ if(NOT TAGLIB_FOUND AND NOT TAGPARSER_FOUND)
endif()
# SingleApplication
add_subdirectory(3rdparty/kdsingleapplication)
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/kdsingleapplication)
set(SINGLEAPPLICATION_LIBRARIES kdsingleapplication)
add_definitions(-DKDSINGLEAPPLICATION_STATIC_BUILD)
if(QT_VERSION_MAJOR EQUAL 5)
set(KDSINGLEAPPLICATION_NAME "KDSingleApplication")
else()
set(KDSINGLEAPPLICATION_NAME "KDSingleApplication-qt${QT_VERSION_MAJOR}")
endif()
find_package(${KDSINGLEAPPLICATION_NAME})
if(TARGET KDAB::kdsingleapplication)
if(QT_VERSION_MAJOR EQUAL 5)
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication_VERSION}")
elseif(QT_VERSION_MAJOR EQUAL 6)
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication-qt6_VERSION}")
endif()
if(KDSINGLEAPPLICATION_VERSION VERSION_GREATER_EQUAL 1.0.95)
set(HAVE_KDSINGLEAPPLICATION_OPTIONS ON)
else()
set(HAVE_KDSINGLEAPPLICATION_OPTIONS OFF)
endif()
message(STATUS "Using system KDSingleApplication (Version ${KDSINGLEAPPLICATION_VERSION})")
set(SINGLEAPPLICATION_LIBRARIES KDAB::kdsingleapplication)
else()
message(STATUS "Using 3rdparty KDSingleApplication")
set(HAVE_KDSINGLEAPPLICATION_OPTIONS ON)
add_subdirectory(3rdparty/kdsingleapplication)
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/kdsingleapplication/KDSingleApplication/src)
set(SINGLEAPPLICATION_LIBRARIES kdsingleapplication)
add_definitions(-DKDSINGLEAPPLICATION_STATIC_BUILD)
endif()
if(APPLE)
add_subdirectory(3rdparty/SPMediaKeyTap)
@@ -440,19 +463,11 @@ optional_component(EBUR128 ON "EBU R 128 loudness normalization"
)
if(APPLE OR WIN32)
option(USE_BUNDLE "Bundle dependencies" ON)
set(USE_BUNDLE_DEFAULT ON)
else()
option(USE_BUNDLE "Bundle dependencies" OFF)
endif()
if(USE_BUNDLE AND NOT USE_BUNDLE_DIR)
if(LINUX)
set(USE_BUNDLE_DIR "../plugins")
endif()
if(APPLE)
set(USE_BUNDLE_DIR "../PlugIns")
endif()
set(USE_BUNDLE_DEFAULT OFF)
endif()
option(USE_BUNDLE "Bundle dependencies" ${USE_BUNDLE_DEFAULT})
if(NOT CMAKE_CROSSCOMPILING)
# Check that we have Qt with sqlite driver

View File

@@ -2,6 +2,44 @@ Strawberry Music Player
=======================
ChangeLog
Version 1.0.22 (2023.12.09):
Bugfixes:
* Fixed KDSingleApplication cmake version check.
* Fixed KDSingleApplication Qt 5 detection (#1299).
* Fixed timer started in wrong thread (#1302).
* Fixed erratic seeking behaviour if buffer duration is set to zero (#1302).
* Fixed SCollection related crash on exit with Qt 5 (#1316).
* Fixed track about to end related crash on playback failure (#1332).
* Fixed playlist column widths not remembered if stretch mode is off with Qt 6.6.1 and higher (#1328).
* (Windows) Properly handle silent uninstall (#1323).
Enhancements:
* Increase thread priority for playback threads.
* Allow drag and drop of songs to favorite playlists.
Version 1.0.21 (2023.10.21):
Bugfixes:
* Fixed seekbar position resetting to zero before showing actual position when seeking.
* Fixed compressed files showing up in collection (#1274).
* Fixed connecting devices (#1288).
* Fixed device schema missing ebur128 fields.
* Fixed collection search by tag not working with space between colon and search term (#1290).
* Fixed seeking when 5 seconds is remaining of the song resetting position to beginning (#1258).
* Fixed intermittent crash when seeking with Auto as output (#1123).
* (Windows) Fixed playlist header colors in dark mode (#1275).
Enhancements:
* Support using system KDSingleApplication when available.
* Improved lyrics matching.
* (macOS) Fully codesign binaries and DMG.
Version 1.0.20 (2023.09.24):
Bugfixes:
* Fixed appdata validation.
Version 1.0.19 (2023.09.24):
Bugfixes:

View File

@@ -21,7 +21,7 @@ Resources:
* Ubuntu Unstable PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry-unstable
* Translations: https://translate.zanata.org/iteration/view/strawberry/master
### :bangbang: Opening an issue:
### :bangbang: Opening an issue
* Read the FAQ: https://wiki.strawberrymusicplayer.org/wiki/FAQ
* Search for the issue to see if it is already solved, or if there is an open issue for it already. If there is an open issue already, you can comment on it if you have additional information that could be useful to us.
@@ -29,18 +29,19 @@ Resources:
* We do not take feature requests from users on GitHub. Any issues related to feature requests will be closed. This does not necessarily mean that we won't add new features, but we don't have time to take feature requests or answer questions about new features from users. It is still possible to suggest or discuss new features on the forum (https://forum.strawberrymusicplayer.org/).
* We do not maintain the Flatpak package. Do not report issues related to Flatpak unless the issue can be reproduced with a native package, use Flatpak support instead https://flatpak.org/about/
### :moneybag: Sponsoring:
### :moneybag: Sponsoring
The program is free software, released under GPL. If you like this program and can make use of it, consider sponsoring or donating to help fund the project.
There are currently 3 options for sponsoring:
1. [GitHub Sponsors](https://github.com/sponsors/jonaski)
2. [Patreon](https://www.patreon.com/jonaskvinge)
3. [PayPal](https://paypal.me/jonaskvinge)
3. [Ko-fi](https://ko-fi.com/jonaskvinge)
4. [PayPal](https://paypal.me/jonaskvinge)
Funding developers is a way to contribute to open source projects you appreciate, it helps developers get the resources they need, and recognize contributors working behind the scenes to make open source better for everyone.
### :heavy_check_mark: Features:
### :heavy_check_mark: Features
* Play and organize music
* Supports WAV, FLAC, WavPack, Ogg FLAC, Ogg Vorbis, Ogg Opus, Ogg Speex, MPC, TrueAudio, AIFF, MP4, MP3, ASF and Monkey's Audio.
@@ -100,7 +101,7 @@ You should also install the gstreamer plugins base and good, and optionally bad,
### Get the code:
git clone https://github.com/strawberrymusicplayer/strawberry
git clone --recursive https://github.com/strawberrymusicplayer/strawberry
### Compile and install:

View File

@@ -27,7 +27,7 @@ if(MACDEPLOYQT_EXECUTABLE)
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/Info.plist ${CMAKE_BINARY_DIR}/strawberry.app/Contents/
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/strawberry.icns ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources/
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macgstcopy.sh ${CMAKE_BINARY_DIR}/strawberry.app
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner -executable=strawberry.app/Contents/PlugIns/gio-modules/libgioopenssl.so -executable=strawberry.app/Contents/PlugIns/gio-modules/libgiognutls.so ${MACDEPLOYQT_CODESIGN}
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner ${MACDEPLOYQT_CODESIGN}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS strawberry strawberry-tagreader
)

View File

@@ -59,7 +59,7 @@ macro(add_po outfiles po_prefix)
# Convert the .po files to .qm files
add_custom_command(
OUTPUT ${_qm_filepath}
COMMAND ${QT_LCONVERT_EXECUTABLE} ARGS ${_po_filepath} -o ${_qm_filepath} -of qm
COMMAND ${QT_LCONVERT_EXECUTABLE} ARGS ${_po_filepath} -o ${_qm_filepath} -of qm -target-language ${_lang}
DEPENDS ${_po_filepath} ${_po_filepath}
)

View File

@@ -1,6 +1,6 @@
set(STRAWBERRY_VERSION_MAJOR 1)
set(STRAWBERRY_VERSION_MINOR 0)
set(STRAWBERRY_VERSION_PATCH 19)
set(STRAWBERRY_VERSION_PATCH 22)
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION OFF)
@@ -25,7 +25,7 @@ if(INCLUDE_GIT_REVISION AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
find_program(GIT_EXECUTABLE git)
if(NOT GIT_EXECUTABLE OR GIT_EXECUTABLE-NOTFOUND)
message(FATAL_ERROR "Missing GIT executable." )
message(FATAL_ERROR "Missing Git executable." )
endif()
# Get the current working branch
@@ -48,7 +48,7 @@ if(INCLUDE_GIT_REVISION AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
)
if(NOT ${GIT_CMD_RESULT_REVISION} EQUAL 0)
message(FATAL_ERROR "GIT command failed to get revision string '${GIT_REVISION}'")
message(FATAL_ERROR "Git command failed to get revision string '${GIT_REVISION}'")
endif()
endif()
@@ -67,7 +67,7 @@ if(GIT_REVISION)
list(LENGTH GIT_PARTS GIT_PARTS_LENGTH)
if(NOT GIT_PARTS_LENGTH EQUAL 3)
message(FATAL_ERROR "Failed to parse git revision string '${GIT_REVISION}'")
set(GIT_PARTS "${majorminorpatch};0;${GIT_REVISION}")
endif()
list(GET GIT_PARTS 0 GIT_TAGNAME)

View File

@@ -18,7 +18,7 @@ CREATE TABLE device_%deviceid_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,
@@ -83,7 +83,10 @@ CREATE TABLE device_%deviceid_songs (
musicbrainz_track_id TEXT,
musicbrainz_disc_id TEXT,
musicbrainz_release_group_id TEXT,
musicbrainz_work_id TEXT
musicbrainz_work_id TEXT,
ebur128_integrated_loudness_lufs REAL,
ebur128_loudness_range_lu REAL
);
@@ -96,4 +99,4 @@ CREATE VIRTUAL TABLE device_%deviceid_fts USING fts5(
tokenize = "unicode61 remove_diacritics 1"
);
UPDATE devices SET schema_version=4 WHERE ROWID=%deviceid;
UPDATE devices SET schema_version=5 WHERE ROWID=%deviceid;

3
debian/control.in vendored
View File

@@ -25,7 +25,8 @@ Build-Depends: debhelper (>= 11),
libgpod-dev,
libmtp-dev,
libchromaprint-dev,
libfftw3-dev
libfftw3-dev,
libebur128-dev
Standards-Version: 4.6.1
Package: strawberry

View File

@@ -23,6 +23,26 @@ if [ "${GST_PLUGIN_PATH}" = "" ]; then
exit 1
fi
if ! [ -e "${GIO_EXTRA_MODULES}/libgiognutls.so" ] && ! [ -e "${GIO_EXTRA_MODULES}/libgioopenssl.so" ]; then
echo "Error: Missing ${GIO_EXTRA_MODULES}/libgiognutls.so or ${GIO_EXTRA_MODULES}/libgioopenssl.so."
exit 1
fi
if ! [ -e "${GST_PLUGIN_SCANNER}" ]; then
echo "Error: Missing ${GST_PLUGIN_SCANNER}"
exit 1
fi
if ! [ -d "${GST_PLUGIN_PATH}" ]; then
echo "Error: GStreamer plugins path ${GST_PLUGIN_PATH} does not exist."
exit 1
fi
if ! [ -e "${GST_PLUGIN_PATH}/libgstcoreelements.so" ] && ! [ -e "${GST_PLUGIN_PATH}/libgstcoreelements.dylib" ]; then
echo "Error: Missing libgstcoreelements.{so,dylib} in GStreamer plugins path ${GST_PLUGIN_PATH}."
exit 1
fi
mkdir -p "${bundledir}/Contents/PlugIns/gio-modules" || exit 1
mkdir -p "${bundledir}/Contents/PlugIns/gstreamer" || exit 1
@@ -38,82 +58,91 @@ else
echo "Warning: Missing ${GIO_EXTRA_MODULES}/libgioopenssl.so"
fi
if ! [ -e "${GST_PLUGIN_SCANNER}" ]; then
echo "Error: Missing ${GST_PLUGIN_SCANNER}"
exit 1
fi
cp -v -f "${GST_PLUGIN_SCANNER}" "${bundledir}/Contents/PlugIns/" || exit 1
if ! [ -d "${GST_PLUGIN_PATH}" ]; then
echo "Error: GStreamer plugins path ${GST_PLUGIN_PATH} does not exist."
exit 1
fi
install_name_tool -add_rpath "@loader_path/../Frameworks" "${bundledir}/Contents/PlugIns/$(basename ${GST_PLUGIN_SCANNER})" || exit 1
gst_plugins="
libgstaes.dylib
libgstaiff.dylib
libgstapetag.dylib
libgstapp.dylib
libgstasf.dylib
libgstasfmux.dylib
libgstaudioconvert.dylib
libgstaudiofx.dylib
libgstaudiomixer.dylib
libgstaudioparsers.dylib
libgstaudiorate.dylib
libgstaudioresample.dylib
libgstaudiotestsrc.dylib
libgstautodetect.dylib
libgstbs2b.dylib
libgstcdio.dylib
libgstcoreelements.dylib
libgstdash.dylib
libgstequalizer.dylib
libgstfaac.dylib
libgstfaad.dylib
libgstfdkaac.dylib
libgstflac.dylib
libgstgio.dylib
libgsthls.dylib
libgsticydemux.dylib
libgstid3demux.dylib
libgstid3tag.dylib
libgstisomp4.dylib
libgstlame.dylib
libgstlibav.dylib
libgstmpg123.dylib
libgstmusepack.dylib
libgstogg.dylib
libgstopenmpt.dylib
libgstopus.dylib
libgstopusparse.dylib
libgstosxaudio.dylib
libgstpbtypes.dylib
libgstplayback.dylib
libgstreplaygain.dylib
libgstrtp.dylib
libgstrtsp.dylib
libgstsoup.dylib
libgstspectrum.dylib
libgstspeex.dylib
libgsttaglib.dylib
libgsttcp.dylib
libgsttwolame.dylib
libgsttypefindfunctions.dylib
libgstudp.dylib
libgstvolume.dylib
libgstvorbis.dylib
libgstwavenc.dylib
libgstwavpack.dylib
libgstwavparse.dylib
libgstxingmux.dylib
libgstaes
libgstaiff
libgstapetag
libgstapp
libgstasf
libgstasfmux
libgstaudioconvert
libgstaudiofx
libgstaudiomixer
libgstaudioparsers
libgstaudiorate
libgstaudioresample
libgstaudiotestsrc
libgstautodetect
libgstbs2b
libgstcdio
libgstcoreelements
libgstdash
libgstequalizer
libgstfaac
libgstfaad
libgstfdkaac
libgstflac
libgstgio
libgsthls
libgsticydemux
libgstid3demux
libgstid3tag
libgstisomp4
libgstlame
libgstlibav
libgstmpg123
libgstmusepack
libgstogg
libgstopenmpt
libgstopus
libgstopusparse
libgstosxaudio
libgstpbtypes
libgstplayback
libgstreplaygain
libgstrtp
libgstrtsp
libgstsoup
libgstspectrum
libgstspeex
libgsttaglib
libgsttcp
libgsttwolame
libgsttypefindfunctions
libgstudp
libgstvolume
libgstvorbis
libgstwavenc
libgstwavpack
libgstwavparse
libgstxingmux
"
gst_plugins=$(echo "$gst_plugins" | tr '\n' ' ' | sed -e 's/^ //g' | sed -e 's/ / /g')
for gst_plugin in $gst_plugins; do
if [ -e "${GST_PLUGIN_PATH}/${gst_plugin}" ]; then
cp -v -f "${GST_PLUGIN_PATH}/${gst_plugin}" "${bundledir}/Contents/PlugIns/gstreamer/" || exit 1
if [ -e "${GST_PLUGIN_PATH}/${gst_plugin}.dylib" ]; then
cp -v -f "${GST_PLUGIN_PATH}/${gst_plugin}.dylib" "${bundledir}/Contents/PlugIns/gstreamer/" || exit 1
install_name_tool -id "@rpath/${gst_plugin}.dylib" "${bundledir}/Contents/PlugIns/gstreamer/${gst_plugin}.dylib"
elif [ -e "${GST_PLUGIN_PATH}/${gst_plugin}.so" ]; then
cp -v -f "${GST_PLUGIN_PATH}/${gst_plugin}.so" "${bundledir}/Contents/PlugIns/gstreamer/" || exit 1
install_name_tool -id "@rpath/${gst_plugin}.so" "${bundledir}/Contents/PlugIns/gstreamer/${gst_plugin}.so"
else
echo "Warning: Missing gstreamer plugin ${gst_plugin}"
echo "Warning: Missing gstreamer plugin ${gst_plugin}."
fi
done
# libsoup is dynamically loaded by gstreamer, so it needs to be copied.
if [ "${LIBSOUP_LIBRARY_PATH}" = "" ]; then
echo "Warning: Set the LIBSOUP_LIBRARY_PATH environment variable for copying libsoup."
else
if [ -e "${LIBSOUP_LIBRARY_PATH}" ]; then
mkdir -p "${bundledir}/Contents/Frameworks" || exit 1
cp -v -f "${LIBSOUP_LIBRARY_PATH}" "${bundledir}/Contents/Frameworks/" || exit 1
install_name_tool -id "@rpath/$(basename ${LIBSOUP_LIBRARY_PATH})" "${bundledir}/Contents/Frameworks/$(basename ${LIBSOUP_LIBRARY_PATH})"
else
echo "Warning: Missing libsoup ${LIBSOUP_LIBRARY_PATH}."
fi
fi

View File

@@ -50,7 +50,10 @@
</screenshots>
<update_contact>eclipseo@fedoraproject.org</update_contact>
<releases>
<release version="1.0.18" date="2023-07-02"/>
<release version="1.0.22" date="2023-12-09"/>
<release version="1.0.21" date="2023-10-21"/>
<release version="1.0.20" date="2023-09-24"/>
<release version="1.0.19" date="2023-09-24"/>
<release version="1.0.18" date="2023-07-02"/>
</releases>
</component>

View File

@@ -1,9 +1,9 @@
Name: strawberry
Version: @STRAWBERRY_VERSION_RPM_V@
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos} || "%{?_vendor}" == "openmandriva"
Release: @STRAWBERRY_VERSION_RPM_R@%{?dist}
%else
%if 0%{?suse_version}
Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@
%else
Release: @STRAWBERRY_VERSION_RPM_R@%{?dist}
%endif
Summary: A music player and music collection organizer
Group: Productivity/Multimedia/Sound/Players
@@ -11,7 +11,7 @@ License: GPL-3.0+
URL: https://www.strawberrymusicplayer.org/
Source0: %{name}-@STRAWBERRY_VERSION_PACKAGE@.tar.xz
%if 0%{?suse_version} && 0%{?suse_version} > 1325
%if 0%{?suse_version}
BuildRequires: libboost_headers-devel
%else
BuildRequires: boost-devel
@@ -25,15 +25,13 @@ BuildRequires: gettext
BuildRequires: desktop-file-utils
%if 0%{?suse_version}
BuildRequires: update-desktop-files
%endif
%if 0%{?suse_version}
BuildRequires: appstream-glib
%else
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
%endif
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
BuildRequires: libappstream-glib
%else
%endif
%if 0%{?mageia} || "%{?_vendor}" == "openmandriva"
BuildRequires: appstream-util
%endif
%endif
BuildRequires: pkgconfig
BuildRequires: pkgconfig(glib-2.0)
@@ -44,48 +42,31 @@ BuildRequires: pkgconfig(dbus-1)
BuildRequires: pkgconfig(alsa)
BuildRequires: pkgconfig(protobuf)
BuildRequires: pkgconfig(sqlite3) >= 3.9
%if ! 0%{?centos} && ! 0%{?mageia}
BuildRequires: pkgconfig(taglib)
%endif
BuildRequires: pkgconfig(fftw3)
BuildRequires: pkgconfig(icu-uc)
BuildRequires: pkgconfig(icu-i18n)
%if "@QT_VERSION_MAJOR@" == "5" && ( 0%{?fedora} || 0%{?rhel_version} || 0%{?centos} )
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Core)
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Gui)
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Widgets)
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Concurrent)
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Network)
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Sql)
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@DBus)
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Test)
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@X11Extras)
%else
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Core)
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Gui)
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Widgets)
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Concurrent)
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Network)
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Sql)
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@DBus)
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Gui)
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Widgets)
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Test)
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@LinguistTools)
%if "@QT_VERSION_MAJOR@" == "5"
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@X11Extras)
%endif
%endif
%if 0%{?suse_version} || 0%{?fedora_version} || 0%{?mageia}
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@LinguistTools)
%endif
BuildRequires: pkgconfig(gstreamer-1.0)
BuildRequires: pkgconfig(gstreamer-app-1.0)
BuildRequires: pkgconfig(gstreamer-audio-1.0)
BuildRequires: pkgconfig(gstreamer-base-1.0)
BuildRequires: pkgconfig(gstreamer-tag-1.0)
%if ! 0%{?centos}
BuildRequires: pkgconfig(libchromaprint)
%endif
BuildRequires: pkgconfig(libpulse)
BuildRequires: pkgconfig(libcdio)
BuildRequires: pkgconfig(libebur128)
BuildRequires: pkgconfig(libgpod-1.0)
BuildRequires: pkgconfig(libmtp)
%if 0%{?suse_version} || 0%{?fedora_version}
@@ -138,22 +119,19 @@ Features:
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
export CXXFLAGS="-fPIC $RPM_OPT_FLAGS"
%endif
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
%if 0%{?centos} || (0%{?mageia} && 0%{?mageia} <= 7) || "%{?_vendor}" == "openmandriva"
%make_build
%if "%{?_vendor}" == "openmandriva"
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@ -DENABLE_TRANSLATIONS=OFF
%make_build
%else
%cmake_build
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
%cmake_build
%endif
%install
%if 0%{?centos}
%make_install
%if "%{?_vendor}" == "openmandriva"
%make_install -C build
%else
%if 0%{?mageia} || "%{?_vendor}" == "openmandriva"
%make_install -C build
%else
%cmake_install
%endif
%cmake_install
%endif
%if 0%{?suse_version}
@@ -162,10 +140,10 @@ Features:
%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}
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
%else
appstream-util validate-relax --nonet %{buildroot}%{_metainfodir}/org.strawberrymusicplayer.strawberry.appdata.xml
%endif
%files
@@ -176,13 +154,13 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicpl
%{_bindir}/strawberry-tagreader
%{_datadir}/applications/*.desktop
%{_datadir}/icons/hicolor/*/apps/strawberry.*
%if 0%{?fedora_version}
%{_metainfodir}/*.appdata.xml
%else
%{_datadir}/metainfo/*.appdata.xml
%endif
%{_mandir}/man1/%{name}.1.*
%{_mandir}/man1/%{name}-tagreader.1.*
%if 0%{?suse_version}
%{_datadir}/metainfo/*.appdata.xml
%else
%{_metainfodir}/*.appdata.xml
%endif
%changelog
* @RPM_DATE@ Jonas Kvinge <jonas@jkvinge.net> - @STRAWBERRY_VERSION_RPM_V@

View File

@@ -136,8 +136,6 @@ SetCompressor /SOLID lzma
ReserveFile "${NSISDIR}\Plugins\x86-unicode\INetC.dll"
!endif
!define LockedListPATH $InstallDir
; Installer pages
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE COPYING
@@ -184,19 +182,22 @@ FunctionEnd
Function CheckPreviousInstall
ReadRegStr $R0 ${PRODUCT_UNINST_ROOT_KEY} ${PRODUCT_UNINST_KEY} "UninstallString"
StrCmp $R0 "" done
StrCmp $R0 "" Done
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
"${PRODUCT_NAME} is already installed. $\n$\nClick `OK` to remove the \
previous version or `Cancel` to cancel this upgrade." \
IDOK uninst
${IfNot} ${Silent}
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "${PRODUCT_NAME} is already installed. $\n$\nClick `OK` to remove the previous version or `Cancel` to cancel this upgrade." IDOK Uninstall
Abort
; Run the uninstaller
uninst:
ClearErrors
ExecWait '$R0' ; Do not copy the uninstaller to a temp file
${EndIf}
done:
Uninstall:
ClearErrors
${If} ${Silent}
ExecWait '$R0 /S'
${Else}
ExecWait '$R0'
${EndIf}
Done:
FunctionEnd
@@ -299,6 +300,7 @@ Section "Strawberry" Strawberry
File "libidn2-0.dll"
File "libintl-8.dll"
File "libjpeg-9.dll"
File "libkdsingleapplication-qt6.dll"
File "liblzma-5.dll"
File "libmp3lame-0.dll"
File "libmpcdec.dll"
@@ -343,6 +345,7 @@ Section "Strawberry" Strawberry
File "libabsl_examine_stack.dll"
File "libabsl_hash.dll"
File "libabsl_int128.dll"
File "libabsl_kernel_timeout_internal.dll"
File "libabsl_log_globals.dll"
File "libabsl_log_internal_check_op.dll"
File "libabsl_log_internal_conditions.dll"
@@ -406,7 +409,7 @@ Section "Strawberry" Strawberry
File "brotlidec.dll"
File "chromaprint.dll"
File "ebur128.dll"
File "faad.dll"
File "faad-2.dll"
File "fdk-aac.dll"
File "ffi-7.dll"
File "gio-2.0-0.dll"
@@ -435,6 +438,7 @@ Section "Strawberry" Strawberry
File "harfbuzz.dll"
File "intl-8.dll"
File "jpeg62.dll"
File "kdsingleapplication-qt6.dll"
File "libbs2b.dll"
File "libfaac_dll.dll"
File "liblzma.dll"
@@ -489,7 +493,7 @@ Section "Strawberry" Strawberry
; Common files
File "icudt73.dll"
File "icudt74.dll"
File "libfftw3-3.dll"
!ifdef debug
File "libprotobufd.dll"
@@ -497,8 +501,8 @@ Section "Strawberry" Strawberry
File "libprotobuf.dll"
!endif
!ifdef msvc && debug
File "icuin73d.dll"
File "icuuc73d.dll"
File "icuin74d.dll"
File "icuuc74d.dll"
File "Qt6Concurrentd.dll"
File "Qt6Cored.dll"
File "Qt6Guid.dll"
@@ -506,8 +510,8 @@ Section "Strawberry" Strawberry
File "Qt6Sqld.dll"
File "Qt6Widgetsd.dll"
!else
File "icuin73.dll"
File "icuuc73.dll"
File "icuin74.dll"
File "icuuc74.dll"
File "Qt6Concurrent.dll"
File "Qt6Core.dll"
File "Qt6Gui.dll"
@@ -853,6 +857,7 @@ Section "Uninstall"
Delete "$INSTDIR\libidn2-0.dll"
Delete "$INSTDIR\libintl-8.dll"
Delete "$INSTDIR\libjpeg-9.dll"
Delete "$INSTDIR\libkdsingleapplication-qt6.dll"
Delete "$INSTDIR\liblzma-5.dll"
Delete "$INSTDIR\libmp3lame-0.dll"
Delete "$INSTDIR\libmpcdec.dll"
@@ -897,6 +902,7 @@ Section "Uninstall"
Delete "$INSTDIR\libabsl_examine_stack.dll"
Delete "$INSTDIR\libabsl_hash.dll"
Delete "$INSTDIR\libabsl_int128.dll"
Delete "$INSTDIR\libabsl_kernel_timeout_internal.dll"
Delete "$INSTDIR\libabsl_log_globals.dll"
Delete "$INSTDIR\libabsl_log_internal_check_op.dll"
Delete "$INSTDIR\libabsl_log_internal_conditions.dll"
@@ -960,7 +966,7 @@ Section "Uninstall"
Delete "$INSTDIR\brotlidec.dll"
Delete "$INSTDIR\chromaprint.dll"
Delete "$INSTDIR\ebur128.dll"
Delete "$INSTDIR\faad.dll"
Delete "$INSTDIR\faad-2.dll"
Delete "$INSTDIR\fdk-aac.dll"
Delete "$INSTDIR\ffi-7.dll"
Delete "$INSTDIR\gio-2.0-0.dll"
@@ -989,6 +995,7 @@ Section "Uninstall"
Delete "$INSTDIR\harfbuzz.dll"
Delete "$INSTDIR\intl-8.dll"
Delete "$INSTDIR\jpeg62.dll"
Delete "$INSTDIR\kdsingleapplication-qt6.dll"
Delete "$INSTDIR\libbs2b.dll"
Delete "$INSTDIR\libfaac_dll.dll"
Delete "$INSTDIR\liblzma.dll"
@@ -1042,7 +1049,7 @@ Section "Uninstall"
; Common files
Delete "$INSTDIR\icudt73.dll"
Delete "$INSTDIR\icudt74.dll"
Delete "$INSTDIR\libfftw3-3.dll"
!ifdef debug
Delete "$INSTDIR\libprotobufd.dll"
@@ -1050,8 +1057,8 @@ Section "Uninstall"
Delete "$INSTDIR\libprotobuf.dll"
!endif
!ifdef msvc && debug
Delete "$INSTDIR\icuin73d.dll"
Delete "$INSTDIR\icuuc73d.dll"
Delete "$INSTDIR\icuin74d.dll"
Delete "$INSTDIR\icuuc74d.dll"
Delete "$INSTDIR\Qt6Concurrentd.dll"
Delete "$INSTDIR\Qt6Cored.dll"
Delete "$INSTDIR\Qt6Guid.dll"
@@ -1059,8 +1066,8 @@ Section "Uninstall"
Delete "$INSTDIR\Qt6Sqld.dll"
Delete "$INSTDIR\Qt6Widgetsd.dll"
!else
Delete "$INSTDIR\icuin73.dll"
Delete "$INSTDIR\icuuc73.dll"
Delete "$INSTDIR\icuin74.dll"
Delete "$INSTDIR\icuuc74.dll"
Delete "$INSTDIR\Qt6Concurrent.dll"
Delete "$INSTDIR\Qt6Core.dll"
Delete "$INSTDIR\Qt6Gui.dll"

View File

@@ -31,6 +31,7 @@
#include <QMutex>
#include <QLocalServer>
#include <QProcess>
#include <QDir>
#include <QFile>
#include <QList>
#include <QQueue>
@@ -245,8 +246,8 @@ void WorkerPool<HandlerType>::DoStart() {
search_path << "/usr/libexec";
search_path << "/usr/local/libexec";
#endif
#if defined(Q_OS_MACOS) && defined(USE_BUNDLE)
search_path << QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR;
#if defined(Q_OS_MACOS)
search_path << QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../PlugIns");
#endif
for (const QString &path_prefix : search_path) {

View File

@@ -34,7 +34,7 @@ namespace GME {
bool IsSupportedFormat(const QFileInfo &file_info);
bool ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
uint32_t UnpackBytes32(const char *const arr, size_t length);
uint32_t UnpackBytes32(const char *const bytes, size_t length);
namespace SPC {
// SPC SPEC: http://vspcplay.raphnet.net/spc_file_format.txt

View File

@@ -113,6 +113,7 @@ set(SOURCES
playlist/playlisttabbar.cpp
playlist/playlistundocommands.cpp
playlist/playlistview.cpp
playlist/playlistproxystyle.cpp
playlist/songloaderinserter.cpp
playlist/songplaylistitem.cpp
playlist/dynamicplaylistcontrols.cpp
@@ -359,6 +360,7 @@ set(HEADERS
playlist/playlistsequence.h
playlist/playlisttabbar.h
playlist/playlistview.h
playlist/playlistproxystyle.h
playlist/playlistitemmimedata.h
playlist/songloaderinserter.h
playlist/songmimedata.h

View File

@@ -45,7 +45,6 @@
#include "scrobbler/lastfmimport.h"
#include "settings/collectionsettingspage.h"
using std::make_unique;
using std::make_shared;
const char *SCollection::kSongsTable = "songs";
@@ -93,14 +92,14 @@ SCollection::~SCollection() {
void SCollection::Init() {
watcher_ = make_unique<CollectionWatcher>(Song::Source::Collection);
watcher_ = new CollectionWatcher(Song::Source::Collection);
watcher_thread_ = new Thread(this);
watcher_thread_->SetIoPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
watcher_->moveToThread(watcher_thread_);
qLog(Debug) << &*watcher_ << "moved to thread" << watcher_thread_;
qLog(Debug) << watcher_ << "moved to thread" << watcher_thread_;
watcher_thread_->start(QThread::IdlePriority);
@@ -108,20 +107,20 @@ void SCollection::Init() {
watcher_->set_task_manager(app_->task_manager());
QObject::connect(&*backend_, &CollectionBackend::Error, this, &SCollection::Error);
QObject::connect(&*backend_, &CollectionBackend::DirectoryDiscovered, &*watcher_, &CollectionWatcher::AddDirectory);
QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, &*watcher_, &CollectionWatcher::RemoveDirectory);
QObject::connect(&*backend_, &CollectionBackend::DirectoryDiscovered, watcher_, &CollectionWatcher::AddDirectory);
QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, watcher_, &CollectionWatcher::RemoveDirectory);
QObject::connect(&*backend_, &CollectionBackend::SongsRatingChanged, this, &SCollection::SongsRatingChanged);
QObject::connect(&*backend_, &CollectionBackend::SongsStatisticsChanged, this, &SCollection::SongsPlaycountChanged);
QObject::connect(&*watcher_, &CollectionWatcher::NewOrUpdatedSongs, &*backend_, &CollectionBackend::AddOrUpdateSongs);
QObject::connect(&*watcher_, &CollectionWatcher::SongsMTimeUpdated, &*backend_, &CollectionBackend::UpdateMTimesOnly);
QObject::connect(&*watcher_, &CollectionWatcher::SongsDeleted, &*backend_, &CollectionBackend::DeleteSongs);
QObject::connect(&*watcher_, &CollectionWatcher::SongsUnavailable, &*backend_, &CollectionBackend::MarkSongsUnavailable);
QObject::connect(&*watcher_, &CollectionWatcher::SongsReadded, &*backend_, &CollectionBackend::MarkSongsUnavailable);
QObject::connect(&*watcher_, &CollectionWatcher::SubdirsDiscovered, &*backend_, &CollectionBackend::AddOrUpdateSubdirs);
QObject::connect(&*watcher_, &CollectionWatcher::SubdirsMTimeUpdated, &*backend_, &CollectionBackend::AddOrUpdateSubdirs);
QObject::connect(&*watcher_, &CollectionWatcher::CompilationsNeedUpdating, &*backend_, &CollectionBackend::CompilationsNeedUpdating);
QObject::connect(&*watcher_, &CollectionWatcher::UpdateLastSeen, &*backend_, &CollectionBackend::UpdateLastSeen);
QObject::connect(watcher_, &CollectionWatcher::NewOrUpdatedSongs, &*backend_, &CollectionBackend::AddOrUpdateSongs);
QObject::connect(watcher_, &CollectionWatcher::SongsMTimeUpdated, &*backend_, &CollectionBackend::UpdateMTimesOnly);
QObject::connect(watcher_, &CollectionWatcher::SongsDeleted, &*backend_, &CollectionBackend::DeleteSongs);
QObject::connect(watcher_, &CollectionWatcher::SongsUnavailable, &*backend_, &CollectionBackend::MarkSongsUnavailable);
QObject::connect(watcher_, &CollectionWatcher::SongsReadded, &*backend_, &CollectionBackend::MarkSongsUnavailable);
QObject::connect(watcher_, &CollectionWatcher::SubdirsDiscovered, &*backend_, &CollectionBackend::AddOrUpdateSubdirs);
QObject::connect(watcher_, &CollectionWatcher::SubdirsMTimeUpdated, &*backend_, &CollectionBackend::AddOrUpdateSubdirs);
QObject::connect(watcher_, &CollectionWatcher::CompilationsNeedUpdating, &*backend_, &CollectionBackend::CompilationsNeedUpdating);
QObject::connect(watcher_, &CollectionWatcher::UpdateLastSeen, &*backend_, &CollectionBackend::UpdateLastSeen);
QObject::connect(&*app_->lastfm_import(), &LastFMImport::UpdateLastPlayed, &*backend_, &CollectionBackend::UpdateLastPlayed);
QObject::connect(&*app_->lastfm_import(), &LastFMImport::UpdatePlayCount, &*backend_, &CollectionBackend::UpdatePlayCount);
@@ -133,13 +132,13 @@ void SCollection::Init() {
void SCollection::Exit() {
wait_for_exit_ << &*backend_ << &*watcher_;
wait_for_exit_ << &*backend_ << watcher_;
QObject::disconnect(&*backend_, nullptr, &*watcher_, nullptr);
QObject::disconnect(&*watcher_, nullptr, &*backend_, nullptr);
QObject::disconnect(&*backend_, nullptr, watcher_, nullptr);
QObject::disconnect(watcher_, nullptr, &*backend_, nullptr);
QObject::connect(&*backend_, &CollectionBackend::ExitFinished, this, &SCollection::ExitReceived);
QObject::connect(&*watcher_, &CollectionWatcher::ExitFinished, this, &SCollection::ExitReceived);
QObject::connect(watcher_, &CollectionWatcher::ExitFinished, this, &SCollection::ExitReceived);
backend_->ExitAsync();
watcher_->Abort();
watcher_->ExitAsync();

View File

@@ -29,7 +29,6 @@
#include <QHash>
#include <QString>
#include "core/scoped_ptr.h"
#include "core/shared_ptr.h"
#include "core/song.h"
@@ -91,7 +90,7 @@ class SCollection : public QObject {
SharedPtr<CollectionBackend> backend_;
CollectionModel *model_;
ScopedPtr<CollectionWatcher> watcher_;
CollectionWatcher *watcher_;
Thread *watcher_thread_;
QThread *original_thread_;

View File

@@ -401,7 +401,7 @@ SongList CollectionBackend::FindSongsInDirectory(const int id) {
QSqlDatabase db(db_->Connect());
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE directory_id = :directory_id").arg(songs_table_));
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id").arg(Song::kColumnSpec, songs_table_));
q.BindValue(":directory_id", id);
if (!q.Exec()) {
db_->ReportErrors(q);
@@ -424,7 +424,7 @@ SongList CollectionBackend::SongsWithMissingFingerprint(const int id) {
QSqlDatabase db(db_->Connect());
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE directory_id = :directory_id AND unavailable = 0 AND (fingerprint IS NULL OR fingerprint = '')").arg(songs_table_));
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id AND unavailable = 0 AND (fingerprint IS NULL OR fingerprint = '')").arg(Song::kColumnSpec, songs_table_));
q.BindValue(":directory_id", id);
if (!q.Exec()) {
db_->ReportErrors(q);
@@ -447,7 +447,7 @@ SongList CollectionBackend::SongsWithMissingLoudnessCharacteristics(const int id
QSqlDatabase db(db_->Connect());
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE directory_id = :directory_id AND unavailable = 0 AND (ebur128_integrated_loudness_lufs IS NULL OR ebur128_loudness_range_lu IS NULL)").arg(songs_table_));
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id AND unavailable = 0 AND (ebur128_integrated_loudness_lufs IS NULL OR ebur128_loudness_range_lu IS NULL)").arg(Song::kColumnSpec, songs_table_));
q.BindValue(":directory_id", id);
if (!q.Exec()) {
db_->ReportErrors(q);
@@ -548,7 +548,7 @@ SongList CollectionBackend::GetAllSongs() {
QSqlDatabase db(db_->Connect());
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1").arg(songs_table_));
q.prepare(QString("SELECT ROWID, %1 FROM %2").arg(Song::kColumnSpec, songs_table_));
if (!q.Exec()) {
db_->ReportErrors(q);
return SongList();
@@ -604,7 +604,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
// Update
{
SqlQuery q(db);
q.prepare(QString("UPDATE %1 SET " + Song::kUpdateSpec + " WHERE ROWID = :id").arg(songs_table_));
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(songs_table_, Song::kUpdateSpec));
song.BindToQuery(&q);
q.BindValue(":id", song.id());
if (!q.Exec()) {
@@ -615,7 +615,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
{
SqlQuery q(db);
q.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_));
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
song.BindToFtsQuery(&q);
q.BindValue(":id", song.id());
if (!q.Exec()) {
@@ -643,7 +643,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
// Update
{
SqlQuery q(db);
q.prepare(QString("UPDATE %1 SET " + Song::kUpdateSpec + " WHERE ROWID = :id").arg(songs_table_));
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(songs_table_, Song::kUpdateSpec));
new_song.BindToQuery(&q);
q.BindValue(":id", new_song.id());
if (!q.Exec()) {
@@ -654,7 +654,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
{
SqlQuery q(db);
q.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_));
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
new_song.BindToFtsQuery(&q);
q.BindValue(":id", new_song.id());
if (!q.Exec()) {
@@ -675,7 +675,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
int id = -1;
{ // Insert the row and create a new ID
SqlQuery q(db);
q.prepare(QString("INSERT INTO %1 (" + Song::kColumnSpec + ") VALUES (" + Song::kBindSpec + ")").arg(songs_table_));
q.prepare(QString("INSERT INTO %1 (%2) VALUES (%3)").arg(songs_table_, Song::kColumnSpec, Song::kBindSpec));
song.BindToQuery(&q);
if (!q.Exec()) {
db_->ReportErrors(q);
@@ -689,7 +689,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
{ // Add to the FTS index
SqlQuery q(db);
q.prepare(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ") VALUES (:id, " + Song::kFtsBindSpec + ")").arg(fts_table_));
q.prepare(QString("INSERT INTO %1 (ROWID, %2) VALUES (:id, %3)").arg(fts_table_, Song::kFtsColumnSpec, Song::kFtsBindSpec));
q.BindValue(":id", id);
song.BindToFtsQuery(&q);
if (!q.Exec()) {
@@ -750,7 +750,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
{
SqlQuery q(db);
q.prepare(QString("UPDATE %1 SET " + Song::kUpdateSpec + " WHERE ROWID = :id").arg(songs_table_));
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(songs_table_, Song::kUpdateSpec));
new_song.BindToQuery(&q);
q.BindValue(":id", old_song.id());
if (!q.Exec()) {
@@ -760,7 +760,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
}
{
SqlQuery q(db);
q.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_));
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
new_song.BindToFtsQuery(&q);
q.BindValue(":id", old_song.id());
if (!q.Exec()) {
@@ -781,7 +781,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
int id = -1;
{
SqlQuery q(db);
q.prepare(QString("INSERT INTO %1 (" + Song::kColumnSpec + ") VALUES (" + Song::kBindSpec + ")").arg(songs_table_));
q.prepare(QString("INSERT INTO %1 (%2) VALUES (%3)").arg(songs_table_, Song::kColumnSpec, Song::kBindSpec));
new_song.BindToQuery(&q);
if (!q.Exec()) {
db_->ReportErrors(q);
@@ -795,7 +795,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
{ // Add to the FTS index
SqlQuery q(db);
q.prepare(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ") VALUES (:id, " + Song::kFtsBindSpec + ")").arg(fts_table_));
q.prepare(QString("INSERT INTO %1 (ROWID, %2) VALUES (:id, %3)").arg(fts_table_, Song::kFtsColumnSpec, Song::kFtsBindSpec));
q.BindValue(":id", id);
new_song.BindToFtsQuery(&q);
if (!q.Exec()) {
@@ -1133,7 +1133,7 @@ SongList CollectionBackend::GetSongsByForeignId(const QStringList &ids, const QS
QString in = ids.join(",");
SqlQuery q(db);
q.prepare(QString("SELECT %2.ROWID, " + Song::kColumnSpec + ", %2.%3 FROM %2, %1 WHERE %2.%3 IN (%4) AND %1.ROWID = %2.ROWID AND unavailable = 0").arg(songs_table_, table, column, in));
q.prepare(QString("SELECT %3.ROWID, %2, %3.%4 FROM %3, %1 WHERE %3.%4 IN (in) AND %1.ROWID = %3.ROWID AND unavailable = 0").arg(songs_table_, Song::kColumnSpec, table, column, in));
if (!q.Exec()) {
db_->ReportErrors(q);
return SongList();
@@ -1164,7 +1164,7 @@ SongList CollectionBackend::GetSongsById(const QStringList &ids, QSqlDatabase &d
QString in = ids.join(",");
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE ROWID IN (%2)").arg(songs_table_, in));
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE ROWID IN (%3)").arg(Song::kColumnSpec, songs_table_, in));
if (!q.Exec()) {
db_->ReportErrors(q);
return SongList();
@@ -1186,7 +1186,7 @@ Song CollectionBackend::GetSongByUrl(const QUrl &url, const qint64 beginning) {
QSqlDatabase db(db_->Connect());
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND beginning = :beginning AND unavailable = 0").arg(songs_table_));
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND beginning = :beginning AND unavailable = 0").arg(Song::kColumnSpec, songs_table_));
q.BindValue(":url1", url);
q.BindValue(":url2", url.toString());
@@ -1216,7 +1216,7 @@ SongList CollectionBackend::GetSongsByUrl(const QUrl &url, const bool unavailabl
QSqlDatabase db(db_->Connect());
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = :unavailable").arg(songs_table_));
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = :unavailable").arg(Song::kColumnSpec, songs_table_));
q.BindValue(":url1", url);
q.BindValue(":url2", url.toString());
@@ -1276,7 +1276,7 @@ SongList CollectionBackend::GetSongsBySongId(const QStringList &song_ids, QSqlDa
QString in = song_ids2.join(",");
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE SONG_ID IN (%2)").arg(songs_table_, in));
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE SONG_ID IN (%3)").arg(Song::kColumnSpec, songs_table_, in));
if (!q.Exec()) {
db_->ReportErrors(q);
return SongList();
@@ -1299,7 +1299,7 @@ SongList CollectionBackend::GetSongsByFingerprint(const QString &fingerprint) {
QSqlDatabase db(db_->Connect());
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE fingerprint = :fingerprint").arg(songs_table_));
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE fingerprint = :fingerprint").arg(Song::kColumnSpec, songs_table_));
q.BindValue(":fingerprint", fingerprint);
if (!q.Exec()) {
db_->ReportErrors(q);
@@ -1422,7 +1422,7 @@ bool CollectionBackend::UpdateCompilations(const QSqlDatabase &db, SongList &del
{ // Get song, so we can tell the model its updated
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0").arg(songs_table_));
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0").arg(Song::kColumnSpec, songs_table_));
q.BindValue(":url1", url);
q.BindValue(":url2", url.toString());
q.BindValue(":url3", url.toString(QUrl::FullyEncoded));
@@ -2043,10 +2043,10 @@ SongList CollectionBackend::GetSongsBy(const QString &artist, const QString &alb
SongList songs;
SqlQuery 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_));
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE artist = :artist COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(Song::kColumnSpec, 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.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE artist = :artist COLLATE NOCASE AND album = :album COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(Song::kColumnSpec, songs_table_));
}
q.BindValue(":artist", artist);
if (!album.isEmpty()) q.BindValue(":album", album);

View File

@@ -54,59 +54,48 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
// 3) Remove colons which don't correspond to column names.
// Split on whitespace
QString filter_text = filter_options.filter_text().replace(QRegularExpression(":\\s+"), ":");
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList tokens(filter_options.filter_text().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts));
QStringList tokens(filter_text.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts));
#else
QStringList tokens(filter_options.filter_text().split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
QStringList tokens(filter_text.split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
#endif
QString query;
for (QString token : tokens) {
token.remove('(');
token.remove(')');
token.remove('"');
token.replace('-', ' ');
token.remove('(')
.remove(')')
.remove('"')
.replace('-', ' ');
if (token.contains(':')) {
// Only prefix fts if the token is a valid column name.
if (Song::kFtsColumns.contains("fts" + token.section(':', 0, 0), Qt::CaseInsensitive)) {
// Account for multiple colons.
QString columntoken = token.section(':', 0, 0, QString::SectionIncludeTrailingSep);
QString subtoken = token.section(':', 1, -1);
subtoken.replace(":", " ");
subtoken = subtoken.trimmed();
if (!subtoken.isEmpty()) {
if (!query.isEmpty()) query.append(" ");
query += "fts" + columntoken + "\"" + subtoken + "\"*";
}
}
else if (Song::kNumericalColumns.contains(token.section(':', 0, 0), Qt::CaseInsensitive)) {
// Account for multiple colons.
QString columntoken = token.section(':', 0, 0);
QString subtoken = token.section(':', 1, -1);
subtoken = subtoken.trimmed();
if (!subtoken.isEmpty()) {
QString comparator = RemoveSqlOperator(subtoken);
if (columntoken.compare("rating", Qt::CaseInsensitive) == 0) {
subtoken.replace(":", " ");
AddWhereRating(subtoken, comparator);
}
else if (columntoken.compare("length", Qt::CaseInsensitive) == 0) {
// time is saved in nanoseconds, so add 9 0's
QString parsedTime = QString::number(Utilities::ParseSearchTime(subtoken)) + "000000000";
AddWhere(columntoken, parsedTime, comparator);
}
else {
subtoken.replace(":", " ");
AddWhere(columntoken, subtoken, comparator);
}
}
}
// not a valid filter, remove
else {
token.replace(":", " ");
token = token.trimmed();
const QString columntoken = token.section(':', 0, 0);
QString subtoken = token.section(':', 1, -1).replace(":", " ").trimmed();
if (subtoken.isEmpty()) continue;
if (Song::kFtsColumns.contains("fts" + columntoken, Qt::CaseInsensitive)) {
if (!query.isEmpty()) query.append(" ");
query += "\"" + token + "\"*";
query += "fts" + columntoken + ":\"" + subtoken + "\"*";
}
else if (Song::kNumericalColumns.contains(columntoken, Qt::CaseInsensitive)) {
QString comparator = RemoveSqlOperator(subtoken);
if (columntoken.compare("rating", Qt::CaseInsensitive) == 0) {
AddWhereRating(subtoken, comparator);
}
else if (columntoken.compare("length", Qt::CaseInsensitive) == 0) {
// Time is saved in nanoseconds, so add 9 0's
QString parsedTime = QString::number(Utilities::ParseSearchTime(subtoken)) + "000000000";
AddWhere(columntoken, parsedTime, comparator);
}
else {
AddWhere(columntoken, subtoken, comparator);
}
}
// Not a valid filter, remove
else {
token = token.replace(":", " ").trimmed();
if (!token.isEmpty()) {
if (!query.isEmpty()) query.append(" ");
query += "\"" + token + "\"*";
}
}
}
else {
@@ -155,6 +144,7 @@ QString CollectionQuery::RemoveSqlOperator(QString &token) {
if (op == "!=") {
op = "<>";
}
return op;
}
@@ -214,7 +204,7 @@ void CollectionQuery::AddWhereRating(const QVariant &value, const QString &op) {
// You can't query the database for a float, due to float precision errors,
// So we have to use a certain tolerance, so that the searched value is definetly included.
const float tolerance = 0.001;
const float tolerance = 0.001F;
if (op == "<") {
AddWhere("rating", parsed_rating-tolerance, "<");
}

View File

@@ -30,7 +30,7 @@ class CollectionQueryOptions {
explicit CollectionQueryOptions();
struct Where {
explicit Where(const QString _column = QString(), const QVariant _value = QString(), const QString _op = QString()) : column(_column), value(_value), op(_op) {}
explicit Where(const QString &_column = QString(), const QVariant &_value = QString(), const QString &_op = QString()) : column(_column), value(_value), op(_op) {}
QString column;
QVariant value;
QString op;

View File

@@ -71,6 +71,7 @@
using namespace std::chrono_literals;
QStringList CollectionWatcher::sValidImages = QStringList() << "jpg" << "png" << "gif" << "jpeg";
QStringList CollectionWatcher::kIgnoredExtensions = QStringList() << "tmp" << "tar" << "gz" << "bz2" << "xz" << "tbz" << "tgz" << "z" << "zip" << "rar";
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
: QObject(parent),
@@ -509,7 +510,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
else {
QString ext_part(ExtensionPart(child));
QString dir_part(DirectoryPart(child));
if (child_info.suffix() == "tmp" || child_info.baseName() == "qt_temp") {
if (kIgnoredExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == "qt_temp") {
t->AddToProgress(1);
}
else if (sValidImages.contains(ext_part)) {

View File

@@ -245,6 +245,7 @@ class CollectionWatcher : public QObject {
CueParser *cue_parser_;
static QStringList sValidImages;
static QStringList kIgnoredExtensions;
qint64 last_scan_time_;

View File

@@ -41,7 +41,6 @@
#cmakedefine HAVE_TAGLIB_DSDIFFFILE
#cmakedefine USE_BUNDLE
#define USE_BUNDLE_DIR "${USE_BUNDLE_DIR}"
#cmakedefine HAVE_TRANSLATIONS
#cmakedefine INSTALL_TRANSLATIONS
@@ -59,4 +58,6 @@
#cmakedefine HAVE_EBUR128
#cmakedefine HAVE_KDSINGLEAPPLICATION_OPTIONS
#endif // CONFIG_H_IN

View File

@@ -87,8 +87,8 @@ class ContextAlbum : public QWidget {
void AutomaticCoverSearchDone();
void FadeCurrentCover(const qreal value);
void FadeCurrentCoverFinished();
void FadePreviousCover(SharedPtr<PreviousCover> previouscover);
void FadePreviousCoverFinished(SharedPtr<PreviousCover> previouscover);
void FadePreviousCover(SharedPtr<PreviousCover> previous_cover);
void FadePreviousCoverFinished(SharedPtr<PreviousCover> previous_cover);
public slots:
void SearchCoverInProgress();

View File

@@ -88,6 +88,7 @@ void RegisterMetaTypes() {
qRegisterMetaType<QVector<int>>("QVector<int>");
qRegisterMetaType<QList<QUrl>>("QList<QUrl>");
qRegisterMetaType<QFileInfo>("QFileInfo");
qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState");
qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
qRegisterMetaType<QNetworkReply**>("QNetworkReply**");
qRegisterMetaType<QItemSelection>("QItemSelection");
@@ -110,6 +111,7 @@ void RegisterMetaTypes() {
#ifdef HAVE_GSTREAMER
qRegisterMetaType<GstBuffer*>("GstBuffer*");
qRegisterMetaType<GstElement*>("GstElement*");
qRegisterMetaType<GstState>("GstState");
qRegisterMetaType<GstEnginePipeline*>("GstEnginePipeline*");
#endif
qRegisterMetaType<CollectionDirectory>("CollectionDirectory");

View File

@@ -367,6 +367,7 @@ double Mpris2::Rating() const {
}
void Mpris2::SetRating(double rating) {
if (rating > 1.0) {
rating = 1.0;
}
@@ -374,7 +375,8 @@ void Mpris2::SetRating(double rating) {
rating = -1.0;
}
app_->playlist_manager()->RateCurrentSong(rating);
app_->playlist_manager()->RateCurrentSong(static_cast<float>(rating));
}
QString Mpris2::current_track_id() const {

View File

@@ -385,7 +385,7 @@ void Player::NextItem(const EngineBase::TrackChangeFlags change, const Playlist:
Playlist *active_playlist = app_->playlist_manager()->active();
// If we received too many errors in auto change, with repeat enabled, we stop
if (change == EngineBase::TrackChangeType::Auto) {
if (change & EngineBase::TrackChangeType::Auto) {
const PlaylistSequence::RepeatMode repeat_mode = active_playlist->sequence()->repeat_mode();
if (repeat_mode != PlaylistSequence::RepeatMode::Off) {
if ((repeat_mode == PlaylistSequence::RepeatMode::Track && nb_errors_received_ >= 3) || (nb_errors_received_ >= app_->playlist_manager()->active()->filter()->rowCount())) {
@@ -706,7 +706,7 @@ void Player::PlayAt(const int index, const quint64 offset_nanosec, EngineBase::T
pause_time_ = QDateTime();
play_offset_nanosec_ = offset_nanosec;
if (current_item_ && change == EngineBase::TrackChangeType::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
if (current_item_ && change & EngineBase::TrackChangeType::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
emit TrackSkipped(current_item_);
}
@@ -867,6 +867,9 @@ void Player::TrackAboutToEnd() {
if (engine_->is_autocrossfade_enabled()) {
// Crossfade is on, so just start playing the next track. The current one will fade out, and the new one will fade in
// If the decoding failed, current_item_ will be null
if (!current_item_) return;
// But, if there's no next track, and we don't want to fade out, then do nothing and just let the track finish to completion.
if (!engine_->is_fadeout_enabled() && !has_next_row) return;

View File

@@ -32,10 +32,9 @@ class PoTranslator : public QTranslator {
public:
PoTranslator(QObject *parent = nullptr) : QTranslator(parent) {}
QString translate(const char *context, const char *source_text, const char *disambiguation = nullptr, int n = -1) const override {
Q_UNUSED(n);
QString ret = QTranslator::translate(context, source_text, disambiguation);
QString ret = QTranslator::translate(context, source_text, disambiguation, n);
if (!ret.isEmpty()) return ret;
return QTranslator::translate(nullptr, source_text, disambiguation);
return QTranslator::translate(nullptr, source_text, disambiguation, n);
}
};

View File

@@ -952,7 +952,7 @@ QString Song::TextForFiletype(const FileType filetype) {
case FileType::OggVorbis: return "Ogg Vorbis";
case FileType::OggOpus: return "Ogg Opus";
case FileType::OggSpeex: return "Ogg Speex";
case FileType::MPEG: return "MP3";
case FileType::MPEG: return "MPEG";
case FileType::MP4: return "MP4 AAC";
case FileType::ASF: return "Windows Media audio";
case FileType::AIFF: return "AIFF";
@@ -1073,6 +1073,7 @@ Song::FileType Song::FiletypeByMimetype(const QString &mimetype) {
else if (mimetype.compare("audio/aac", Qt::CaseInsensitive) == 0) return FileType::MP4;
else if (mimetype.compare("audio/x-wma", Qt::CaseInsensitive) == 0) return FileType::ASF;
else if (mimetype.compare("audio/aiff", Qt::CaseInsensitive) == 0 || mimetype.compare("audio/x-aiff", Qt::CaseInsensitive) == 0) return FileType::AIFF;
else if (mimetype.compare("audio/x-musepack", Qt::CaseInsensitive) == 0) return FileType::MPC;
else if (mimetype.compare("application/x-project", Qt::CaseInsensitive) == 0) return FileType::MPC;
else if (mimetype.compare("audio/x-dsf", Qt::CaseInsensitive) == 0) return FileType::DSF;
else if (mimetype.compare("audio/x-dsd", Qt::CaseInsensitive) == 0) return FileType::DSDIFF;
@@ -1094,11 +1095,13 @@ Song::FileType Song::FiletypeByDescription(const QString &text) {
else if (text.compare("Vorbis", Qt::CaseInsensitive) == 0) return FileType::OggVorbis;
else if (text.compare("Opus", Qt::CaseInsensitive) == 0) return FileType::OggOpus;
else if (text.compare("Speex", Qt::CaseInsensitive) == 0) return FileType::OggSpeex;
else if (text.compare("MPEG-1 Layer 2 (MP2)", Qt::CaseInsensitive) == 0) return FileType::MPEG;
else if (text.compare("MPEG-1 Layer 3 (MP3)", Qt::CaseInsensitive) == 0) return FileType::MPEG;
else if (text.compare("MPEG-4 AAC", Qt::CaseInsensitive) == 0) return FileType::MP4;
else if (text.compare("WMA", Qt::CaseInsensitive) == 0) return FileType::ASF;
else if (text.compare("Audio Interchange File Format", Qt::CaseInsensitive) == 0) return FileType::AIFF;
else if (text.compare("MPC", Qt::CaseInsensitive) == 0) return FileType::MPC;
else if (text.compare("Musepack (MPC)", Qt::CaseInsensitive) == 0) return FileType::MPC;
else if (text.compare("audio/x-dsf", Qt::CaseInsensitive) == 0) return FileType::DSF;
else if (text.compare("audio/x-dsd", Qt::CaseInsensitive) == 0) return FileType::DSDIFF;
else if (text.compare("audio/x-ffmpeg-parsed-ape", Qt::CaseInsensitive) == 0) return FileType::APE;
@@ -1118,6 +1121,7 @@ Song::FileType Song::FiletypeByExtension(const QString &ext) {
else if (ext.compare("ogg", Qt::CaseInsensitive) == 0 || ext.compare("oga", Qt::CaseInsensitive) == 0) return FileType::OggVorbis;
else if (ext.compare("opus", Qt::CaseInsensitive) == 0) return FileType::OggOpus;
else if (ext.compare("speex", Qt::CaseInsensitive) == 0 || ext.compare("spx", Qt::CaseInsensitive) == 0) return FileType::OggSpeex;
else if (ext.compare("mp2", Qt::CaseInsensitive) == 0) return FileType::MPEG;
else if (ext.compare("mp3", Qt::CaseInsensitive) == 0) return FileType::MPEG;
else if (ext.compare("mp4", Qt::CaseInsensitive) == 0 || ext.compare("m4a", Qt::CaseInsensitive) == 0 || ext.compare("aac", Qt::CaseInsensitive) == 0) return FileType::MP4;
else if (ext.compare("asf", Qt::CaseInsensitive) == 0 || ext.compare("wma", Qt::CaseInsensitive) == 0) return FileType::ASF;

View File

@@ -251,7 +251,7 @@ void AlbumCoverLoader::InitArt(TaskPtr task) {
}
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type &type) {
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type type) {
switch (type) {
case AlbumCoverLoaderOptions::Type::Unset:{

View File

@@ -109,7 +109,7 @@ class AlbumCoverLoader : public QObject {
quint64 EnqueueTask(TaskPtr task);
void ProcessTask(TaskPtr task);
void InitArt(TaskPtr task);
LoadImageResult LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type &type);
LoadImageResult LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type type);
LoadImageResult LoadEmbeddedImage(TaskPtr task);
LoadImageResult LoadUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
LoadImageResult LoadLocalUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);

View File

@@ -41,10 +41,10 @@ class AlbumCoverLoaderResult {
explicit AlbumCoverLoaderResult(const bool _success = false,
const Type _type = Type::None,
AlbumCoverImageResult _album_cover = AlbumCoverImageResult(),
const AlbumCoverImageResult &_album_cover = AlbumCoverImageResult(),
const QImage &_image_scaled = QImage(),
const QUrl _art_manual_updated = QUrl(),
const QUrl _art_automatic_updated = QUrl()) :
const QUrl &_art_manual_updated = QUrl(),
const QUrl &_art_automatic_updated = QUrl()) :
success(_success),
type(_type),
album_cover(_album_cover),

View File

@@ -36,7 +36,7 @@
#include "core/scopedtransaction.h"
#include "devicedatabasebackend.h"
const int DeviceDatabaseBackend::kDeviceSchemaVersion = 4;
const int DeviceDatabaseBackend::kDeviceSchemaVersion = 5;
DeviceDatabaseBackend::DeviceDatabaseBackend(QObject *parent)
: QObject(parent),

View File

@@ -120,7 +120,7 @@ void DeviceInfo::LoadIcon(const QVariantList &icons, const QString &name_hint) {
if (!icon.isNull() && icon.userType() == QMetaType::QString) {
QString icon_name = icon.toString();
if (!icon_name.isEmpty()) {
QString hint = QString(icons.first().toString() + name_hint).toLower();
QString hint = icons.first().toString().toLower() + name_hint.toLower();
if (hint.contains("phone")) icon_name_ = "device-phone";
else if (hint.contains("ipod") || hint.contains("apple")) icon_name_ = "device-ipod";
else if ((hint.contains("usb")) && (hint.contains("reader"))) icon_name_ = "device-usb-flash";

View File

@@ -638,7 +638,7 @@ SharedPtr<ConnectedDevice> DeviceManager::Connect(DeviceInfo *info) {
Q_ARG(QUrl, device_url),
Q_ARG(DeviceLister*, info->BestBackend()->lister_),
Q_ARG(QString, info->BestBackend()->unique_id_),
Q_ARG(DeviceManager*, this),
Q_ARG(SharedPtr<DeviceManager>, app_->device_manager()),
Q_ARG(Application*, app_),
Q_ARG(int, info->database_id_),
Q_ARG(bool, first_time));

View File

@@ -31,7 +31,7 @@
#include "playlist/playlist.h"
SavePlaylistsDialog::SavePlaylistsDialog(const QStringList &types, const QString &default_extension) : ui_(new Ui_SavePlaylistsDialog) {
SavePlaylistsDialog::SavePlaylistsDialog(const QStringList &types, const QString &default_extension, QWidget *parent) : QDialog(parent), ui_(new Ui_SavePlaylistsDialog) {
ui_->setupUi(this);

View File

@@ -31,7 +31,7 @@ class SavePlaylistsDialog : public QDialog {
Q_OBJECT
public:
explicit SavePlaylistsDialog(const QStringList &types, const QString &default_extension);
explicit SavePlaylistsDialog(const QStringList &types, const QString &default_extension, QWidget *parent = nullptr);
~SavePlaylistsDialog();
QString path() const { return ui_->lineedit_path->text(); }

View File

@@ -75,7 +75,7 @@ class EngineBase : public QObject {
Error
};
enum TrackChangeType {
enum class TrackChangeType {
// One of:
First = 0x01,
Manual = 0x02,
@@ -254,5 +254,6 @@ Q_DECLARE_METATYPE(EngineBase::Type)
Q_DECLARE_METATYPE(EngineBase::State)
Q_DECLARE_METATYPE(EngineBase::TrackChangeType)
Q_DECLARE_METATYPE(EngineBase::OutputDetails)
Q_DECLARE_OPERATORS_FOR_FLAGS(EngineBase::TrackChangeFlags)
#endif // ENGINEBASE_H

View File

@@ -21,6 +21,8 @@
#include "config.h"
#include <QtGlobal>
#include <cstdint>
#include <cstring>
#include <cmath>
@@ -30,7 +32,13 @@
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <QtGlobal>
#ifdef Q_OS_UNIX
# include <pthread.h>
#endif
#ifdef Q_OS_WIN32
# include <windows.h>
#endif
#include <QObject>
#include <QCoreApplication>
#include <QtConcurrent>
@@ -94,11 +102,12 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
next_end_offset_nanosec_(-1),
ignore_next_seek_(false),
ignore_tags_(false),
pipeline_is_initialized_(false),
pipeline_is_active_(false),
pipeline_is_connected_(false),
pending_seek_nanosec_(-1),
last_known_position_ns_(0),
next_uri_set_(false),
next_uri_reset_(false),
ebur128_loudness_normalizing_gain_db_(0.0),
volume_set_(false),
volume_internal_(-1.0),
@@ -281,6 +290,25 @@ void GstEnginePipeline::set_fading_enabled(const bool enabled) {
fading_enabled_ = enabled;
}
QString GstEnginePipeline::GstStateText(const GstState state) {
switch (state) {
case GST_STATE_VOID_PENDING:
return "Pending";
case GST_STATE_NULL:
return "Null";
case GST_STATE_READY:
return "Ready";
case GST_STATE_PAUSED:
return "Paused";
case GST_STATE_PLAYING:
return "Playing";
default:
return "Unknown";
}
}
GstElement *GstEnginePipeline::CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const {
QString unique_name = QString("pipeline") + "-" + QString::number(id_) + "-" + (name.isEmpty() ? factory_name : name);
@@ -322,22 +350,6 @@ bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_ur
if (!InitAudioBin(error)) return false;
#ifdef Q_OS_WIN32
if (volume_enabled_ && !volume_ && volume_sw_) {
SetupVolume(volume_sw_);
}
#else
if (volume_enabled_ && !volume_) {
if (output_ == GstEngine::kAutoSink) {
element_added_cb_id_ = CHECKED_GCONNECT(G_OBJECT(audiobin_), "deep-element-added", &ElementAddedCallback, this);
}
else if (volume_sw_) {
qLog(Debug) << output_ << "does not have volume, using own volume.";
SetupVolume(volume_sw_);
}
}
#endif
// Set playbin's sink to be our custom audio-sink.
g_object_set(GST_OBJECT(pipeline_), "audio-sink", audiobin_, nullptr);
@@ -507,6 +519,8 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
if (!volume_sw_) {
return false;
}
qLog(Debug) << output_ << "does not have volume, using own volume.";
SetupVolume(volume_sw_);
}
if (fading_enabled_) {
@@ -644,11 +658,18 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
// We set this on this queue instead of the playbin because setting it on the playbin only affects network sources.
// Disable the default buffer and byte limits, so we only buffer based on time.
qLog(Debug) << "Setting buffer duration:" << buffer_duration_nanosec_ << "low watermark:" << buffer_low_watermark_ << "high watermark:" << buffer_high_watermark_;
g_object_set(G_OBJECT(audioqueue_), "use-buffering", true, nullptr);
g_object_set(G_OBJECT(audioqueue_), "max-size-buffers", 0, nullptr);
g_object_set(G_OBJECT(audioqueue_), "max-size-bytes", 0, nullptr);
g_object_set(G_OBJECT(audioqueue_), "max-size-time", buffer_duration_nanosec_, nullptr);
if (buffer_duration_nanosec_ > 0) {
qLog(Debug) << "Setting buffer duration:" << buffer_duration_nanosec_ << "low watermark:" << buffer_low_watermark_ << "high watermark:" << buffer_high_watermark_;
g_object_set(G_OBJECT(audioqueue_), "max-size-buffers", 0, nullptr);
g_object_set(G_OBJECT(audioqueue_), "max-size-bytes", 0, nullptr);
g_object_set(G_OBJECT(audioqueue_), "max-size-time", buffer_duration_nanosec_, nullptr);
}
else {
qLog(Debug) << "Setting low watermark:" << buffer_low_watermark_ << "high watermark:" << buffer_high_watermark_;
}
g_object_set(G_OBJECT(audioqueue_), "low-watermark", buffer_low_watermark_, nullptr);
g_object_set(G_OBJECT(audioqueue_), "high-watermark", buffer_high_watermark_, nullptr);
@@ -929,7 +950,7 @@ void GstEnginePipeline::PadAddedCallback(GstElement *element, GstPad *pad, gpoin
instance->playbin_probe_cb_id_ = gst_pad_add_probe(pad, static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), PlaybinProbeCallback, instance, nullptr);
instance->pipeline_is_connected_ = true;
if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_initialized_) {
if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_active_) {
QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_));
}
@@ -1252,6 +1273,7 @@ void GstEnginePipeline::StreamStartMessageReceived() {
if (next_uri_set_) {
qLog(Debug) << "Stream changed from URL" << gst_url_ << "to" << next_gst_url_;
next_uri_set_ = false;
next_uri_reset_ = false;
about_to_finish_ = false;
media_url_ = next_media_url_;
stream_url_ = next_stream_url_;
@@ -1274,16 +1296,17 @@ void GstEnginePipeline::TaskEnterCallback(GstTask *task, GThread *thread, gpoint
Q_UNUSED(thread)
Q_UNUSED(self)
// Bump the priority of the thread only on macOS
#ifdef Q_OS_MACOS
sched_param param;
#ifdef Q_OS_UNIX
sched_param param{};
memset(&param, 0, sizeof(param));
param.sched_priority = 99;
param.sched_priority = 40;
pthread_setschedparam(pthread_self(), SCHED_RR, &param);
#endif
#ifdef Q_OS_WIN32
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
#endif
}
void GstEnginePipeline::ElementMessageReceived(GstMessage *msg) {
@@ -1312,7 +1335,7 @@ void GstEnginePipeline::ErrorMessageReceived(GstMessage *msg) {
g_error_free(error);
g_free(debugs);
if (pipeline_is_initialized_ && next_uri_set_ && (domain == GST_CORE_ERROR || domain == GST_RESOURCE_ERROR || domain == GST_STREAM_ERROR)) {
if (pipeline_is_active_ && next_uri_set_ && (domain == GST_CORE_ERROR || domain == GST_RESOURCE_ERROR || domain == GST_STREAM_ERROR)) {
// A track is still playing and the next uri is not playable. We ignore the error here so it can play until the end.
// But there is no message send to the bus when the current track finishes, we have to add an EOS ourself.
qLog(Info) << "Ignoring error" << domain << code << message << debugstr << "when loading next track";
@@ -1430,26 +1453,44 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
GstState old_state = GST_STATE_NULL, new_state = GST_STATE_NULL, pending = GST_STATE_NULL;
gst_message_parse_state_changed(msg, &old_state, &new_state, &pending);
if (!pipeline_is_initialized_ && (new_state == GST_STATE_PAUSED || new_state == GST_STATE_PLAYING)) {
qLog(Debug) << "Pipeline initialized: State changed from" << old_state << "to" << new_state;
pipeline_is_initialized_ = true;
if (!volume_set_) {
SetVolume(volume_percent_);
}
if (pending_seek_nanosec_ != -1 && pipeline_is_connected_) {
QMetaObject::invokeMethod(this, "Seek", Qt::QueuedConnection, Q_ARG(qint64, pending_seek_nanosec_));
qLog(Debug) << "Pipeline state changed from" << GstStateText(old_state) << "to" << GstStateText(new_state);
if (!pipeline_is_active_ && (new_state == GST_STATE_PAUSED || new_state == GST_STATE_PLAYING)) {
qLog(Debug) << "Pipeline is active";
pipeline_is_active_ = true;
if (pipeline_is_connected_) {
if (!volume_set_) {
SetVolume(volume_percent_);
}
if (pending_seek_nanosec_ != -1) {
if (next_uri_reset_ && new_state == GST_STATE_PAUSED) {
qLog(Debug) << "Reverting next uri and going to playing state.";
next_uri_reset_ = false;
SeekDelayed(pending_seek_nanosec_);
SetStateDelayed(GST_STATE_PLAYING);
}
else {
SeekQueued(pending_seek_nanosec_);
}
}
}
}
if (pipeline_is_initialized_ && new_state != GST_STATE_PAUSED && new_state != GST_STATE_PLAYING) {
qLog(Debug) << "Pipeline uninitialized: State changed from" << old_state << "to" << new_state;
pipeline_is_initialized_ = false;
else if (pipeline_is_active_ && new_state != GST_STATE_PAUSED && new_state != GST_STATE_PLAYING) {
qLog(Debug) << "Pipeline is inactive";
pipeline_is_active_ = false;
if (next_uri_set_ && new_state == GST_STATE_READY) {
// Revert uri and go back to PLAY state again
next_uri_set_ = false;
g_object_set(G_OBJECT(pipeline_), "uri", gst_url_.constData(), nullptr);
SetState(GST_STATE_PLAYING);
if (pending_seek_nanosec_ == -1) {
qLog(Debug) << "Reverting next uri and going to playing state.";
SetState(GST_STATE_PLAYING);
}
else {
qLog(Debug) << "Reverting next uri and going to paused state.";
next_uri_reset_ = true;
SetState(GST_STATE_PAUSED);
}
}
}
@@ -1487,8 +1528,11 @@ void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) {
qint64 GstEnginePipeline::position() const {
if (pipeline_is_initialized_) {
gst_element_query_position(pipeline_, GST_FORMAT_TIME, &last_known_position_ns_);
if (pipeline_is_active_) {
gint64 current_position = 0;
if (gst_element_query_position(pipeline_, GST_FORMAT_TIME, &current_position)) {
last_known_position_ns_ = current_position;
}
}
return last_known_position_ns_;
@@ -1516,7 +1560,18 @@ GstState GstEnginePipeline::state() const {
}
QFuture<GstStateChangeReturn> GstEnginePipeline::SetState(const GstState state) {
qLog(Debug) << "Setting pipeline state to" << GstStateText(state);
return QtConcurrent::run(&set_state_threadpool_, &gst_element_set_state, pipeline_, state);
}
void GstEnginePipeline::SetStateDelayed(const GstState state) {
QMetaObject::invokeMethod(this, [this, state]() {
QTimer::singleShot(300, this, [this, state]() { SetState(state); });
}, Qt::QueuedConnection);
}
bool GstEnginePipeline::Seek(const qint64 nanosec) {
@@ -1526,7 +1581,7 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
return true;
}
if (!pipeline_is_connected_ || !pipeline_is_initialized_) {
if (!pipeline_is_connected_ || !pipeline_is_active_) {
pending_seek_nanosec_ = nanosec;
return true;
}
@@ -1539,10 +1594,27 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
pending_seek_nanosec_ = -1;
last_known_position_ns_ = nanosec;
qLog(Debug) << "Seeking to" << nanosec;
return gst_element_seek_simple(pipeline_, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, nanosec);
}
void GstEnginePipeline::SeekQueued(const qint64 nanosec) {
QMetaObject::invokeMethod(this, "Seek", Qt::QueuedConnection, Q_ARG(qint64, nanosec));
}
void GstEnginePipeline::SeekDelayed(const qint64 nanosec) {
QMetaObject::invokeMethod(this, [this, nanosec]() {
QTimer::singleShot(100, this, [this, nanosec]() { Seek(nanosec); });
}, Qt::QueuedConnection);
}
void GstEnginePipeline::SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db) {
ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db;
@@ -1567,7 +1639,7 @@ void GstEnginePipeline::SetVolume(const uint volume_percent) {
if (!volume_set_ || volume_internal != volume_internal_) {
volume_internal_ = volume_internal;
g_object_set(G_OBJECT(volume_), "volume", volume_internal, nullptr);
if (pipeline_is_initialized_) {
if (pipeline_is_active_) {
volume_set_ = true;
}
}

View File

@@ -85,8 +85,11 @@ class GstEnginePipeline : public QObject {
void RemoveAllBufferConsumers();
// Control the music playback
QFuture<GstStateChangeReturn> SetState(const GstState state);
Q_INVOKABLE QFuture<GstStateChangeReturn> SetState(const GstState state);
void SetStateDelayed(const GstState state);
Q_INVOKABLE bool Seek(const qint64 nanosec);
void SeekQueued(const qint64 nanosec);
void SeekDelayed(const qint64 nanosec);
void SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db);
void SetVolume(const uint volume_percent);
void SetStereoBalance(const float value);
@@ -148,6 +151,7 @@ class GstEnginePipeline : public QObject {
void timerEvent(QTimerEvent*) override;
private:
static QString GstStateText(const GstState state);
GstElement *CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const;
bool InitAudioBin(QString &error);
void SetupVolume(GstElement *element);
@@ -277,7 +281,7 @@ class GstEnginePipeline : public QObject {
// Seeking while the pipeline is in the READY state doesn't work, so we have to wait until it goes to PAUSED or PLAYING.
// Also, we have to wait for the playbin to be connected.
bool pipeline_is_initialized_;
bool pipeline_is_active_;
bool pipeline_is_connected_;
qint64 pending_seek_nanosec_;
@@ -288,6 +292,7 @@ class GstEnginePipeline : public QObject {
// Complete the transition to the next song when it starts playing
bool next_uri_set_;
bool next_uri_reset_;
double ebur128_loudness_normalizing_gain_db_;
bool volume_set_;

View File

@@ -110,26 +110,33 @@ void GstStartup::SetEnvironment() {
#ifdef USE_BUNDLE
QString app_path = QCoreApplication::applicationDirPath();
QString bundle_path = app_path + "/" + USE_BUNDLE_DIR;
const QString app_path = QCoreApplication::applicationDirPath();
QString gio_module_path;
QString gst_plugin_scanner;
QString gst_plugin_path;
QString libsoup_library_path;
# if defined(Q_OS_WIN32) || defined(Q_OS_MACOS)
gio_module_path = bundle_path + "/gio-modules";
# endif
# if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
gst_plugin_scanner = bundle_path + "/gst-plugin-scanner";
gst_plugin_path = bundle_path + "/gstreamer";
# endif
# if defined(Q_OS_WIN32)
gst_plugin_path = bundle_path + "/gstreamer-plugins";
# endif
// Set plugin root path
QString plugin_root_path;
# if defined(Q_OS_MACOS)
libsoup_library_path = app_path + "/../Frameworks/libsoup-3.0.0.dylib";
plugin_root_path = QDir::cleanPath(app_path + "/../PlugIns");
# elif defined(Q_OS_UNIX)
plugin_root_path = QDir::cleanPath(app_path + "/../plugins");
# elif defined(Q_OS_WIN32)
plugin_root_path = app_path;
# endif
// Set GIO module path
const QString gio_module_path = plugin_root_path + "/gio-modules";
// Set GStreamer plugin scanner path
QString gst_plugin_scanner;
# if defined(Q_OS_UNIX)
gst_plugin_scanner = plugin_root_path + "/gst-plugin-scanner";
# endif
// Set GStreamer plugin path
QString gst_plugin_path;
# if defined(Q_OS_WIN32)
gst_plugin_path = plugin_root_path + "/gstreamer-plugins";
# else
gst_plugin_path = plugin_root_path + "/gstreamer";
# endif
if (!gio_module_path.isEmpty()) {
@@ -138,7 +145,7 @@ void GstStartup::SetEnvironment() {
Utilities::SetEnv("GIO_EXTRA_MODULES", gio_module_path);
}
else {
qLog(Debug) << "GIO module path does not exist:" << gio_module_path;
qLog(Error) << "GIO module path" << gio_module_path << "does not exist.";
}
}
@@ -148,7 +155,7 @@ void GstStartup::SetEnvironment() {
Utilities::SetEnv("GST_PLUGIN_SCANNER", gst_plugin_scanner);
}
else {
qLog(Debug) << "GStreamer plugin scanner does not exist:" << gst_plugin_scanner;
qLog(Error) << "GStreamer plugin scanner" << gst_plugin_scanner << "does not exist.";
}
}
@@ -160,16 +167,7 @@ void GstStartup::SetEnvironment() {
Utilities::SetEnv("GST_PLUGIN_SYSTEM_PATH", gst_plugin_path);
}
else {
qLog(Debug) << "GStreamer plugin path does not exist:" << gst_plugin_path;
}
}
if (!libsoup_library_path.isEmpty()) {
if (QFile::exists(libsoup_library_path)) {
Utilities::SetEnv("LIBSOUP3_LIBRARY_PATH", libsoup_library_path);
}
else {
qLog(Debug) << "libsoup path does not exist:" << libsoup_library_path;
qLog(Error) << "GStreamer plugin path" << gst_plugin_path << "does not exist.";
}
}

View File

@@ -26,6 +26,7 @@
#include "core/shared_ptr.h"
#include "core/networkaccessmanager.h"
#include "utilities/transliterate.h"
#include "lyricssearchrequest.h"
#include "azlyricscomlyricsprovider.h"
@@ -43,8 +44,8 @@ QUrl AzLyricsComLyricsProvider::Url(const LyricsSearchRequest &request) {
}
QString AzLyricsComLyricsProvider::StringFixup(QString string) {
QString AzLyricsComLyricsProvider::StringFixup(const QString &text) {
return string.remove(QRegularExpression("[^\\w0-9\\-]", QRegularExpression::UseUnicodePropertiesOption)).simplified().toLower();
return Utilities::Transliterate(text).remove(QRegularExpression("[^\\w0-9\\-]")).toLower();
}

View File

@@ -42,7 +42,7 @@ class AzLyricsComLyricsProvider : public HtmlLyricsProvider {
QUrl Url(const LyricsSearchRequest &request) override;
private:
QString StringFixup(QString string);
QString StringFixup(const QString &text);
private:
static const char kUrl[];

View File

@@ -26,6 +26,7 @@
#include "core/shared_ptr.h"
#include "core/networkaccessmanager.h"
#include "utilities/transliterate.h"
#include "lyricssearchrequest.h"
#include "elyricsnetlyricsprovider.h"
@@ -43,12 +44,13 @@ QUrl ElyricsNetLyricsProvider::Url(const LyricsSearchRequest &request) {
}
QString ElyricsNetLyricsProvider::StringFixup(QString string) {
QString ElyricsNetLyricsProvider::StringFixup(const QString &text) {
return string
.replace(' ', '-')
.replace(QRegularExpression("[^\\w0-9_-]", QRegularExpression::UseUnicodePropertiesOption), "_")
return Utilities::Transliterate(text)
.replace(QRegularExpression("[^\\w0-9_,&\\-\\(\\) ]"), "_")
.replace(QRegularExpression(" {2,}"), " ")
.simplified()
.replace(' ', '-')
.toLower();
}

View File

@@ -42,7 +42,7 @@ class ElyricsNetLyricsProvider : public HtmlLyricsProvider {
QUrl Url(const LyricsSearchRequest &request) override;
private:
QString StringFixup(QString string);
QString StringFixup(const QString &text);
private:
static const char kUrl[];

View File

@@ -297,7 +297,7 @@ bool GeniusLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &
requests_search_.insert(id, search);
QUrlQuery url_query;
url_query.addQueryItem("q", QUrl::toPercentEncoding(QString(request.artist + " " + request.title)));
url_query.addQueryItem("q", QUrl::toPercentEncoding(QString("%1 %2").arg(request.artist, request.title)));
QUrl url(kUrlSearch);
url.setQuery(url_query);

View File

@@ -43,12 +43,14 @@ QUrl LyricsModeComLyricsProvider::Url(const LyricsSearchRequest &request) {
}
QString LyricsModeComLyricsProvider::StringFixup(QString string) {
QString LyricsModeComLyricsProvider::StringFixup(QString text) {
return string
.replace(' ', '_')
.remove(QRegularExpression("[^\\w0-9_-]", QRegularExpression::UseUnicodePropertiesOption))
return text
.remove(QRegularExpression("[^\\w0-9_\\- ]"))
.replace(QRegularExpression(" {2,}"), " ")
.simplified()
.replace(' ', '_')
.replace('-', '_')
.toLower();
}

View File

@@ -42,7 +42,7 @@ class LyricsModeComLyricsProvider : public HtmlLyricsProvider {
QUrl Url(const LyricsSearchRequest &request) override;
private:
QString StringFixup(QString string);
QString StringFixup(QString text);
private:
static const char kUrl[];

View File

@@ -43,14 +43,15 @@ QUrl SongLyricsComLyricsProvider::Url(const LyricsSearchRequest &request) {
}
QString SongLyricsComLyricsProvider::StringFixup(QString string) {
QString SongLyricsComLyricsProvider::StringFixup(QString text) {
return string.replace('/', '-')
.replace('\'', '-')
.remove(QRegularExpression("[^\\w0-9\\- ]", QRegularExpression::UseUnicodePropertiesOption))
.simplified()
.replace(' ', '-')
.replace(QRegularExpression("(-)\\1+"), "-")
.toLower();
return text.replace('/', '-')
.replace('\'', '-')
.remove(QRegularExpression("[^\\w0-9\\- ]"))
.replace(QRegularExpression(" {2,}"), " ")
.simplified()
.replace(' ', '-')
.replace(QRegularExpression("(-)\\1+"), "-")
.toLower();
}

View File

@@ -42,7 +42,7 @@ class SongLyricsComLyricsProvider : public HtmlLyricsProvider {
QUrl Url(const LyricsSearchRequest &request) override;
private:
QString StringFixup(QString string);
QString StringFixup(QString text);
private:
static const char kUrl[];

View File

@@ -70,6 +70,8 @@
#include "core/scoped_ptr.h"
#include "core/shared_ptr.h"
#include "utilities/envutils.h"
#include <kdsingleapplication.h>
#ifdef HAVE_QTSPARKLE
@@ -154,7 +156,11 @@ int main(int argc, char *argv[]) {
// Only start a core application now, so we can check if there's another instance without requiring an X server.
// This MUST be done before parsing the commandline options so QTextCodec gets the right system locale for filenames.
QCoreApplication core_app(argc, argv);
#ifdef HAVE_KDSINGLEAPPLICATION_OPTIONS
KDSingleApplication single_app(QCoreApplication::applicationName(), KDSingleApplication::Option::IncludeUsernameInSocketName);
#else
KDSingleApplication single_app(QCoreApplication::applicationName());
#endif
// Parse commandline options - need to do this before starting the full QApplication, so it works without an X server
if (!options.Parse()) return 1;
logging::SetLevels(options.log_levels());
@@ -171,7 +177,7 @@ int main(int argc, char *argv[]) {
#ifdef Q_OS_MACOS
// Must happen after QCoreApplication::setOrganizationName().
setenv("XDG_CONFIG_HOME", QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation).toLocal8Bit().constData(), 1);
Utilities::SetEnv("XDG_CONFIG_HOME", QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
#endif
// Output the version, so when people attach log output to bug reports they don't have to tell us which version they're using.
@@ -191,7 +197,11 @@ int main(int argc, char *argv[]) {
QGuiApplication::setQuitOnLastWindowClosed(false);
QApplication a(argc, argv);
#ifdef HAVE_KDSINGLEAPPLICATION_OPTIONS
KDSingleApplication single_app(QCoreApplication::applicationName(), KDSingleApplication::Option::IncludeUsernameInSocketName);
#else
KDSingleApplication single_app(QCoreApplication::applicationName());
#endif
if (!single_app.isPrimaryInstance()) {
if (options.is_empty()) {
qLog(Info) << "Strawberry is already running - activating existing window (2)";
@@ -205,19 +215,7 @@ int main(int argc, char *argv[]) {
QGuiApplication::setWindowIcon(IconLoader::Load("strawberry"));
#if defined(USE_BUNDLE)
{
QStringList library_paths;
#ifdef Q_OS_MACOS
library_paths.append(QCoreApplication::applicationDirPath() + "/../Frameworks");
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
library_paths.append(QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR);
#endif
if (!library_paths.isEmpty()) {
qLog(Debug) << "Looking for resources in" << library_paths;
QCoreApplication::setLibraryPaths(library_paths);
}
}
qLog(Debug) << "Looking for resources in" << QCoreApplication::libraryPaths();
#endif
// Gnome on Ubuntu has menu icons disabled by default. I think that's a bad idea, and makes some menus in Strawberry look confusing.

View File

@@ -1286,10 +1286,10 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const {
}
bool Playlist::CompareItems(const int column, const Qt::SortOrder order, SharedPtr<PlaylistItem> _a, SharedPtr<PlaylistItem> _b) {
bool Playlist::CompareItems(const int column, const Qt::SortOrder order, PlaylistItemPtr _a, PlaylistItemPtr _b) {
SharedPtr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
SharedPtr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
PlaylistItemPtr a = order == Qt::AscendingOrder ? _a : _b;
PlaylistItemPtr b = order == Qt::AscendingOrder ? _b : _a;
#define cmp(field) return a->Metadata().field() < b->Metadata().field()
#define strcmp(field) return QString::localeAwareCompare(a->Metadata().field().toLower(), b->Metadata().field().toLower()) < 0;
@@ -1345,10 +1345,10 @@ bool Playlist::CompareItems(const int column, const Qt::SortOrder order, SharedP
}
bool Playlist::ComparePathDepths(const Qt::SortOrder order, SharedPtr<PlaylistItem> _a, SharedPtr<PlaylistItem> _b) {
bool Playlist::ComparePathDepths(const Qt::SortOrder order, PlaylistItemPtr _a, PlaylistItemPtr _b) {
SharedPtr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
SharedPtr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
PlaylistItemPtr a = order == Qt::AscendingOrder ? _a : _b;
PlaylistItemPtr b = order == Qt::AscendingOrder ? _b : _a;
qint64 a_dir_level = a->Url().path().count('/');
qint64 b_dir_level = b->Url().path().count('/');

View File

@@ -48,6 +48,7 @@
#include "playlistitem.h"
#include "playlistsequence.h"
#include "smartplaylists/playlistgenerator_fwd.h"
#include <internet/internetservice.h>
class QMimeData;
class QUndoStack;
@@ -58,7 +59,6 @@ class PlaylistBackend;
class PlaylistFilter;
class Queue;
class TaskManager;
class InternetService;
class RadioService;
namespace PlaylistUndoCommands {
@@ -237,7 +237,7 @@ class Playlist : public QAbstractListModel {
void InsertSongs(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
void InsertSongsOrCollectionItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
void InsertSmartPlaylist(PlaylistGeneratorPtr gen, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
void InsertInternetItems(SharedPtr<InternetService> service, const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
void InsertInternetItems(InternetServicePtr service, const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
void InsertRadioItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
void ReshuffleIndices();

View File

@@ -67,9 +67,6 @@ class PlaylistContainer : public QWidget {
bool eventFilter(QObject *objectWatched, QEvent *event) override;
signals:
void TabChanged(const int id);
void Rename(const int id, const QString &new_name);
void UndoRedoActionsChanged(QAction *undo, QAction *redo);
void ViewSelectionModelChanged();

View File

@@ -131,7 +131,7 @@ class FloatEqComparator : public SearchTermComparator {
class FloatNeComparator : public SearchTermComparator {
public:
explicit FloatNeComparator(const float &value) : search_term_(value) {}
explicit FloatNeComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return search_term_ != element.toFloat();
}
@@ -141,7 +141,7 @@ class FloatNeComparator : public SearchTermComparator {
class FloatGtComparator : public SearchTermComparator {
public:
explicit FloatGtComparator(const float &value) : search_term_(value) {}
explicit FloatGtComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return element.toFloat() > search_term_;
}
@@ -151,7 +151,7 @@ class FloatGtComparator : public SearchTermComparator {
class FloatGeComparator : public SearchTermComparator {
public:
explicit FloatGeComparator(const float &value) : search_term_(value) {}
explicit FloatGeComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return element.toFloat() >= search_term_;
}
@@ -161,7 +161,7 @@ class FloatGeComparator : public SearchTermComparator {
class FloatLtComparator : public SearchTermComparator {
public:
explicit FloatLtComparator(const float &value) : search_term_(value) {}
explicit FloatLtComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return element.toFloat() < search_term_;
}
@@ -171,7 +171,7 @@ class FloatLtComparator : public SearchTermComparator {
class FloatLeComparator : public SearchTermComparator {
public:
explicit FloatLeComparator(const float &value) : search_term_(value) {}
explicit FloatLeComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return element.toFloat() <= search_term_;
}

View File

@@ -44,6 +44,7 @@
#include <QToolButton>
#include <QShowEvent>
#include <QContextMenuEvent>
#include <QMimeData>
#include "core/application.h"
#include "core/iconloader.h"
@@ -110,6 +111,7 @@ PlaylistListContainer::PlaylistListContainer(QWidget *parent)
QObject::connect(ui_->tree, &PlaylistListView::ItemsSelectedChanged, this, &PlaylistListContainer::ItemsSelectedChanged);
QObject::connect(ui_->tree, &PlaylistListView::doubleClicked, this, &PlaylistListContainer::ItemDoubleClicked);
QObject::connect(ui_->tree, &PlaylistListView::ItemMimeDataDroppedSignal, this, &PlaylistListContainer::ItemMimeDataDropped);
model_->invisibleRootItem()->setData(PlaylistListModel::Type_Folder, PlaylistListModel::Role_Type);
@@ -348,6 +350,19 @@ void PlaylistListContainer::ItemDoubleClicked(const QModelIndex &proxy_idx) {
}
void PlaylistListContainer::ItemMimeDataDropped(const QModelIndex &proxy_idx, const QMimeData *q_mimedata) {
const QModelIndex idx = proxy_->mapToSource(proxy_idx);
if (!idx.isValid()) return;
// Drop playlist rows if type is playlist and it's not active, to prevent selfcopy
int playlis_id = idx.data(PlaylistListModel::Role_PlaylistId).toInt();
if (idx.data(PlaylistListModel::Role_Type).toInt() == PlaylistListModel::Type_Playlist && playlis_id != app_->playlist_manager()->active_id()) {
app_->playlist_manager()->playlist(playlis_id)->dropMimeData(q_mimedata, Qt::CopyAction, -1, 0, QModelIndex());
}
}
void PlaylistListContainer::CopyToDevice() {
#ifndef Q_OS_WIN

View File

@@ -63,6 +63,7 @@ class PlaylistListContainer : public QWidget {
// From the UI
void NewFolderClicked();
void ItemDoubleClicked(const QModelIndex &proxy_idx);
void ItemMimeDataDropped(const QModelIndex &proxy_idx, const QMimeData *q_mimedata);
// From the model
void PlaylistPathChanged(const int id, const QString &new_path);

View File

@@ -23,6 +23,7 @@
#include <QWidget>
#include <QFont>
#include <QMimeData>
#include <QPainter>
#include <QPalette>
#include <QRect>
@@ -30,6 +31,7 @@
#include "widgets/autoexpandingtreeview.h"
#include "playlistlistview.h"
#include "playlist.h"
PlaylistListView::PlaylistListView(QWidget *parent) : AutoExpandingTreeView(parent) {}
@@ -64,3 +66,72 @@ bool PlaylistListView::ItemsSelected() const {
void PlaylistListView::selectionChanged(const QItemSelection&, const QItemSelection&) {
emit ItemsSelectedChanged(selectionModel()->selectedRows().count() > 0);
}
void PlaylistListView::dragEnterEvent(QDragEnterEvent *e) {
if (e->mimeData()->hasFormat(Playlist::kRowsMimetype)) {
e->acceptProposedAction();
}
else {
AutoExpandingTreeView::dragEnterEvent(e);
}
}
void PlaylistListView::dragMoveEvent(QDragMoveEvent *e) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QModelIndex drag_hover_tab_ = indexAt(e->position().toPoint());
#else
QModelIndex drag_hover_tab_ = indexAt(e->pos());
#endif
if (e->mimeData()->hasFormat(Playlist::kRowsMimetype)) {
if (drag_hover_tab_ != currentIndex()) {
e->setDropAction(Qt::CopyAction);
e->accept(visualRect(drag_hover_tab_));
setCurrentIndex(drag_hover_tab_);
if (drag_hover_timer_.isActive()) {
drag_hover_timer_.stop();
}
drag_hover_timer_.start(kDragHoverTimeout, this);
}
}
else {
AutoExpandingTreeView::dragMoveEvent(e);
}
}
void PlaylistListView::dragLeaveEvent(QDragLeaveEvent *e) {
if (drag_hover_timer_.isActive()) {
drag_hover_timer_.stop();
}
AutoExpandingTreeView::dragLeaveEvent(e);
}
void PlaylistListView::timerEvent(QTimerEvent *e) {
QTreeView::timerEvent(e);
if (e->timerId() == drag_hover_timer_.timerId()) {
drag_hover_timer_.stop();
emit doubleClicked(currentIndex());
}
}
void PlaylistListView::dropEvent(QDropEvent *e) {
if (e->mimeData()->hasFormat(Playlist::kRowsMimetype)) {
if (drag_hover_timer_.isActive()) {
drag_hover_timer_.stop();
}
emit ItemMimeDataDroppedSignal(currentIndex(), e->mimeData());
}
else {
AutoExpandingTreeView::dropEvent(e);
}
}

View File

@@ -24,6 +24,7 @@
#include "config.h"
#include <QBasicTimer>
#include <QObject>
#include <QWidget>
#include <QString>
@@ -31,6 +32,11 @@
#include "widgets/autoexpandingtreeview.h"
class QPaintEvent;
class QDragEnterEvent;
class QDragLeaveEvent;
class QDragMoveEvent;
class QDropEvent;
class QTimerEvent;
class PlaylistListView : public AutoExpandingTreeView {
Q_OBJECT
@@ -42,11 +48,23 @@ class PlaylistListView : public AutoExpandingTreeView {
signals:
void ItemsSelectedChanged(const bool);
void ItemMimeDataDroppedSignal(const QModelIndex &proxy_idx, const QMimeData *q_mimedata);
protected:
// QWidget
void paintEvent(QPaintEvent *event) override;
void selectionChanged(const QItemSelection&, const QItemSelection&) override;
void dragEnterEvent(QDragEnterEvent *e) override;
void dragMoveEvent(QDragMoveEvent *e) override;
void dragLeaveEvent(QDragLeaveEvent *e) override;
void dropEvent(QDropEvent *e) override;
void timerEvent(QTimerEvent *e) override;
private:
static const int kDragHoverTimeout = 500;
QBasicTimer drag_hover_timer_;
};
#endif // PLAYLISTVIEW_H

View File

@@ -449,8 +449,7 @@ void PlaylistManager::UpdateSummaryText() {
nanoseconds = current()->GetTotalLength();
}
// TODO: Make the plurals translatable
summary += tracks == 1 ? tr("1 track") : tr("%1 tracks").arg(tracks);
summary += tr("%n track(s)", "", tracks);
if (nanoseconds > 0) {
summary += " - [ " + Utilities::WordyTimeNanosec(nanoseconds) + " ]";

View File

@@ -0,0 +1,71 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
*
* 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 <QProxyStyle>
#include <QString>
#include <QPainter>
#include <QStyleOptionHeader>
#include <QFontMetrics>
#include "playlistproxystyle.h"
#include "playlist.h"
PlaylistProxyStyle::PlaylistProxyStyle(const QString &style) : QProxyStyle(style), common_style_(new QCommonStyle) {}
void PlaylistProxyStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const {
if (element == CE_HeaderLabel) {
const QStyleOptionHeader *header_option = qstyleoption_cast<const QStyleOptionHeader*>(option);
const QRect &rect = header_option->rect;
const QString &text = header_option->text;
const QFontMetrics &font_metrics = header_option->fontMetrics;
// Spaces added to make transition less abrupt
if (rect.width() < font_metrics.horizontalAdvance(text + " ")) {
const Playlist::Column column = static_cast<Playlist::Column>(header_option->section);
QStyleOptionHeader new_option(*header_option);
new_option.text = Playlist::abbreviated_column_name(column);
QProxyStyle::drawControl(element, &new_option, painter, widget);
return;
}
}
if (element == CE_ItemViewItem) {
common_style_->drawControl(element, option, painter, widget);
}
else {
QProxyStyle::drawControl(element, option, painter, widget);
}
}
void PlaylistProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const {
if (element == QStyle::PE_PanelItemViewRow || element == QStyle::PE_PanelItemViewItem) {
common_style_->drawPrimitive(element, option, painter, widget);
}
else {
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
*
* 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 PLAYLISTPROXYSTYLE_H
#define PLAYLISTPROXYSTYLE_H
#include "config.h"
#include <QProxyStyle>
#include <QCommonStyle>
#include <QString>
#include "core/scoped_ptr.h"
class QPainter;
// This proxy style works around a bug/feature introduced in Qt 4.7's QGtkStyle
// that uses Gtk to paint row backgrounds, ignoring any custom brush or palette the caller set in the QStyleOption.
// That breaks our currently playing track animation, which relies on the background painted by Qt to be transparent.
// This proxy style uses QCommonStyle to paint the affected elements.
class PlaylistProxyStyle : public QProxyStyle {
Q_OBJECT
public:
explicit PlaylistProxyStyle(const QString &style);
void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override;
void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override;
private:
ScopedPtr<QCommonStyle> common_style_;
};
#endif // PLAYLISTPROXYSTYLE_H

View File

@@ -32,8 +32,6 @@
#include <QTreeView>
#include <QHeaderView>
#include <QClipboard>
#include <QCommonStyle>
#include <QFontMetrics>
#include <QKeySequence>
#include <QMimeData>
#include <QMetaType>
@@ -55,9 +53,7 @@
#include <QPoint>
#include <QRect>
#include <QRegion>
#include <QStyleOptionHeader>
#include <QStyleOptionViewItem>
#include <QProxyStyle>
#include <QLinearGradient>
#include <QScrollBar>
#include <QtEvents>
@@ -73,6 +69,7 @@
#include "playlistheader.h"
#include "playlistview.h"
#include "playlistfilter.h"
#include "playlistproxystyle.h"
#include "covermanager/currentalbumcoverloader.h"
#include "covermanager/albumcoverloaderresult.h"
#include "settings/appearancesettingspage.h"
@@ -88,50 +85,14 @@ const int PlaylistView::kAutoscrollGraceTimeout = 30; // seconds
const int PlaylistView::kDropIndicatorWidth = 2;
const int PlaylistView::kDropIndicatorGradientWidth = 5;
PlaylistProxyStyle::PlaylistProxyStyle(QObject*) : QProxyStyle(nullptr), common_style_(new QCommonStyle) {}
void PlaylistProxyStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const {
if (element == CE_Header) {
const QStyleOptionHeader *header_option = qstyleoption_cast<const QStyleOptionHeader*>(option);
const QRect &rect = header_option->rect;
const QString &text = header_option->text;
const QFontMetrics &font_metrics = header_option->fontMetrics;
// Spaces added to make transition less abrupt
if (rect.width() < font_metrics.horizontalAdvance(text + " ")) {
const Playlist::Column column = static_cast<Playlist::Column>(header_option->section);
QStyleOptionHeader new_option(*header_option);
new_option.text = Playlist::abbreviated_column_name(column);
QProxyStyle::drawControl(element, &new_option, painter, widget);
return;
}
}
if (element == CE_ItemViewItem) {
common_style_->drawControl(element, option, painter, widget);
}
else {
QProxyStyle::drawControl(element, option, painter, widget);
}
}
void PlaylistProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const {
if (element == QStyle::PE_PanelItemViewRow || element == QStyle::PE_PanelItemViewItem) {
common_style_->drawPrimitive(element, option, painter, widget);
}
else {
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}
PlaylistView::PlaylistView(QWidget *parent)
: QTreeView(parent),
app_(nullptr),
style_(new PlaylistProxyStyle()),
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
style_(new PlaylistProxyStyle(QApplication::style()->name())),
#else
style_(new PlaylistProxyStyle(QApplication::style()->objectName())),
#endif
playlist_(nullptr),
header_(new PlaylistHeader(Qt::Horizontal, this, this)),
background_image_type_(AppearanceSettingsPage::BackgroundImageType::Default),

View File

@@ -40,12 +40,9 @@
#include <QRect>
#include <QRegion>
#include <QStyleOption>
#include <QProxyStyle>
#include <QPoint>
#include <QBasicTimer>
#include <QCommonStyle>
#include "core/scoped_ptr.h"
#include "core/song.h"
#include "covermanager/albumcoverloaderresult.h"
#include "settings/appearancesettingspage.h"
@@ -72,27 +69,10 @@ class QTimerEvent;
class Application;
class CollectionBackend;
class PlaylistHeader;
class PlaylistProxyStyle;
class DynamicPlaylistControls;
class RatingItemDelegate;
// This proxy style works around a bug/feature introduced in Qt 4.7's QGtkStyle
// that uses Gtk to paint row backgrounds, ignoring any custom brush or palette the caller set in the QStyleOption.
// That breaks our currently playing track animation, which relies on the background painted by Qt to be transparent.
// This proxy style uses QCommonStyle to paint the affected elements.
// This class is used by internet search view as well.
class PlaylistProxyStyle : public QProxyStyle {
Q_OBJECT
public:
explicit PlaylistProxyStyle(QObject *parent = nullptr);
void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override;
void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override;
private:
ScopedPtr<QCommonStyle> common_style_;
};
class PlaylistView : public QTreeView {
Q_OBJECT

View File

@@ -55,7 +55,7 @@ SongList ASXParser::Load(QIODevice *device, const QString &playlist_path, const
QString url = re_match.captured(2);
url.replace(QRegularExpression("&(?!amp;|quot;|apos;|lt;|gt;)"), "&amp;");
QByteArray replacement = QString(re_match.captured(1) + url + "\"").toLocal8Bit();
QByteArray replacement = QString("%1%2\"").arg(re_match.captured(1), url).toLocal8Bit();
data.replace(re_match.captured(0).toLocal8Bit(), replacement);
index += replacement.length();
}

View File

@@ -25,14 +25,15 @@
const char *MusixmatchProvider::kApiUrl = "https://api.musixmatch.com/ws/1.1";
const char *MusixmatchProvider::kApiKey = "Y2FhMDRlN2Y4OWE5OTIxYmZlOGMzOWQzOGI3ZGU4MjE=";
QString MusixmatchProvider::StringFixup(QString string) {
QString MusixmatchProvider::StringFixup(QString text) {
return string.replace('/', '-')
.replace('\'', '-')
.remove(QRegularExpression("[^\\w0-9\\- ]", QRegularExpression::UseUnicodePropertiesOption))
.simplified()
.replace(' ', '-')
.replace(QRegularExpression("(-)\\1+"), "-")
.toLower();
return text.replace('/', '-')
.replace('\'', '-')
.remove(QRegularExpression("[^\\w0-9\\- ]", QRegularExpression::UseUnicodePropertiesOption))
.replace(QRegularExpression(" {2,}"), " ")
.simplified()
.replace(' ', '-')
.replace(QRegularExpression("(-)\\1+"), "-")
.toLower();
}

View File

@@ -25,7 +25,7 @@
class MusixmatchProvider {
protected:
QString StringFixup(QString string);
QString StringFixup(QString text);
protected:
static const char *kApiUrl;

View File

@@ -143,7 +143,7 @@ class QobuzService : public InternetService {
using Param = QPair<QString, QString>;
using ParamList = QList<Param>;
QString DecodeAppSecret(const QString &app_secret_encoded) const;
QString DecodeAppSecret(const QString &app_secret_base64) const;
void SendSearch();
void LoginError(const QString &error = QString(), const QVariant &debug = QVariant());

View File

@@ -158,7 +158,7 @@ QVariant Queue::data(const QModelIndex &proxy_index, int role) const {
const QString title = source_index.sibling(source_index.row(), Playlist::Column_Title).data().toString();
if (artist.isEmpty()) return title;
return QString(artist + " - " + title);
return QString("%1 - %2").arg(artist, title);
}
default:
@@ -249,7 +249,7 @@ void Queue::UpdateSummaryText() {
int tracks = ItemCount();
quint64 nanoseconds = GetTotalLength();
summary += tracks == 1 ? tr("1 track") : tr("%1 tracks").arg(tracks);
summary += tr("%n track(s)", "", tracks);
if (nanoseconds > 0) {
summary += " - [ " + Utilities::WordyTimeNanosec(nanoseconds) + " ]";

View File

@@ -111,7 +111,7 @@ class TidalRequest : public TidalBaseRequest {
void ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested);
void AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested);
void AlbumsReceived(QNetworkReply *reply, const Artist &artist_artist, const int limit_requested, const int offset_requested, const bool auto_login);
void AlbumsReceived(QNetworkReply *reply, const Artist &artist_requested, const int limit_requested, const int offset_requested, const bool auto_login);
void SongsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested);
void SongsReceived(QNetworkReply *reply, const Artist &artist, const Album &album, const int limit_requested, const int offset_requested, const bool auto_login = false);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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