Compare commits

..

63 Commits
1.2.3 ... 1.2.4

Author SHA1 Message Date
Jonas Kvinge
33ae53a90f Release 1.2.4 2025-01-10 02:26:41 +01:00
Strawberry Bot
01c28867b7 New translations 2025-01-10 02:10:44 +01:00
Jonas Kvinge
08fb2ae331 Update Changelog 2025-01-10 01:58:19 +01:00
Jonas Kvinge
99970f9e52 Update strawberry_en_US.ts 2025-01-10 01:58:19 +01:00
Jonas Kvinge
bc206f43b4 TranscodeDialog: Minor adjustments 2025-01-10 01:58:19 +01:00
dependabot[bot]
018448159c Bump vmactions/freebsd-vm from 1.1.7 to 1.1.8
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.1.7 to 1.1.8.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.1.7...v1.1.8)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-vm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-10 01:27:57 +01:00
Jonas Kvinge
81fe90bdef Update Changelog 2025-01-10 01:07:32 +01:00
Jonas Kvinge
319558c535 tests: Fixed ignored return value 2025-01-10 00:58:41 +01:00
Jonas Kvinge
9095b0d6b2 Always run MSVC runtime installer 2025-01-08 19:34:27 +01:00
Jonas Kvinge
558eae1ca1 Remove unneeded slots 2025-01-08 04:57:17 +01:00
Jonas Kvinge
2c64f05cea MoodbarProxyStyle: Add const 2025-01-08 04:54:45 +01:00
Jonas Kvinge
c80e7071a1 StandardPaths: Fix namespace 2025-01-07 22:13:24 +01:00
Jonas Kvinge
038e679000 SmartPlaylistWizard: Apply dark mode workaround for all styles except vista
Fixes #1639
2025-01-07 22:04:28 +01:00
Jonas Kvinge
72447fecfb StandardPaths: Remove inheritance 2025-01-07 21:40:06 +01:00
Jonas Kvinge
c7830f6f05 MoodbarPipeline: Cleanup on finish 2025-01-07 01:09:49 +01:00
Jonas Kvinge
af525e42b6 MoodbarPipeline: Remove Q_ASSERT 2025-01-06 21:28:44 +01:00
Jonas Kvinge
eb83f23125 MoodbarPipeline: Use bytearray directly 2025-01-06 21:28:33 +01:00
Jonas Kvinge
7527d2ea9a MoodbarLoader: Set object name 2025-01-06 18:11:38 +01:00
Jonas Kvinge
69d38879d2 Use QCoreApplication::applicationName() directly 2025-01-06 00:38:51 +01:00
Jonas Kvinge
cbce9f7191 Override config, data and cache location 2025-01-05 23:45:29 +01:00
Jonas Kvinge
f938129d81 CI: Fix deb copy command 2025-01-05 21:22:08 +01:00
Jonas Kvinge
415a40ea04 FilterParser: Ignore space after operator 2025-01-05 20:25:48 +01:00
Jonas Kvinge
6e7aaed4ee Use QSharedPointer for GstEnginePipeline 2025-01-05 19:03:16 +01:00
Jonas Kvinge
7afae70bb0 GstEnginePipeline: Make sure all set states are finished before finishing pipeline 2025-01-05 18:58:03 +01:00
Jonas Kvinge
be8097919b mutex_protected: Add operator ++ and -- 2025-01-05 18:56:22 +01:00
Jonas Kvinge
1990a42e1d CI: Don't run build on l10n_master branch 2025-01-05 18:53:19 +01:00
Kyle Hopkins
8fcee4511d TranscodeDialog: update to optionally preserve directory structure 2025-01-05 18:34:14 +01:00
Leandro Matheus
24af1be666 Subsonic: Add support for using album id to retrieve album covers 2025-01-05 18:30:24 +01:00
Jonas Kvinge
8302a95bc1 Use shared pointers for moodbar pipelines
Possible fix for #1633
2025-01-05 18:28:41 +01:00
Jonas Kvinge
36a8ab49a0 MoodbarItemDelegate: Format comment 2025-01-05 03:51:00 +01:00
Jonas Kvinge
5c64dc9c4d MoodbarItemDelegate: Delete data if it fails to insert to cache 2025-01-05 03:50:51 +01:00
Strawberry Bot
c271743208 New translations 2025-01-05 00:22:50 +01:00
Jonas Kvinge
ee49b1ddc8 Update strawberry_en_US.ts 2025-01-04 04:56:08 +01:00
Jonas Kvinge
bf98633f16 Load XSPF title as playlist name 2025-01-04 04:52:17 +01:00
Jonas Kvinge
e2a928f2dc Save XSPF playlist with title
Fixes #1624
2025-01-04 03:48:53 +01:00
Jonas Kvinge
47d3312a6b PlaylistManager: Remove slash from playlist filename 2025-01-04 03:46:03 +01:00
Jonas Kvinge
4f97325953 CollectionSettingsPage: Fix string conversion 2025-01-04 03:32:50 +01:00
Jonas Kvinge
91e8fe0943 ScrobblerSettingsPage: Add tooltip 2025-01-04 03:15:38 +01:00
Jonas Kvinge
dc5894b38a CollectionWatcher: Ignore special filesystem paths
Fixes #1615
2025-01-04 03:06:46 +01:00
Jonas Kvinge
52ee50a2a4 CollectionSettingsPage: Add check for filesystem type 2025-01-04 02:58:59 +01:00
Jonas Kvinge
f545b028ee Add filesystem constants 2025-01-04 02:58:17 +01:00
Jonas Kvinge
a96627c5a9 Playlist: Invalidate deleted songs in main thread
Fixes #1625
2025-01-04 00:16:36 +01:00
Jonas Kvinge
a13fc31f83 PlaylistManager: Remove unused InvalidateDeletedSongs function 2025-01-04 00:09:26 +01:00
Jonas Kvinge
9ee5c8dc17 Playlist: Update copyright 2025-01-03 23:41:43 +01:00
Jonas Kvinge
82cd425ece Playlist: Add const 2025-01-03 23:41:30 +01:00
Jonas Kvinge
3b02d364ba Playlist: Rename some variables 2025-01-03 23:41:15 +01:00
dependabot[bot]
73e7947487 Bump vmactions/openbsd-vm from 1.1.4 to 1.1.5
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.1.4 to 1.1.5.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.1.4...v1.1.5)

---
updated-dependencies:
- dependency-name: vmactions/openbsd-vm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 17:38:02 +01:00
dependabot[bot]
cfc9a43b88 Bump vmactions/freebsd-vm from 1.1.6 to 1.1.7
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.1.6 to 1.1.7.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.1.6...v1.1.7)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-vm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 17:37:32 +01:00
dependabot[bot]
969500023a Bump vmactions/freebsd-vm from 1.1.5 to 1.1.6
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.1.5 to 1.1.6.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.1.5...v1.1.6)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-vm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-26 00:44:32 +01:00
Jonas Kvinge
53c72d4f8e CollectionModel: Don't use artist sort text for album 2024-12-17 22:38:04 +01:00
Jonas Kvinge
f971c92f32 Move debian back to source dir
PPA failed to build
2024-12-17 21:57:37 +01:00
Strawberry Bot
82156e8a13 New translations 2024-12-17 00:01:04 +01:00
dependabot[bot]
6a6285861e Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 23:59:25 +01:00
Jonas Kvinge
dae8c8730b debian: Remove whitespaces 2024-12-14 18:33:40 +01:00
Jonas Kvinge
20b47eae8f CI: Use -j 4 for Ubuntu 2024-12-14 18:33:29 +01:00
Jonas Kvinge
2ce8220d88 Move generated files to binary directory 2024-12-14 17:59:26 +01:00
Jonas Kvinge
35b0b5df57 CMake: Fix backtrace linking 2024-12-14 15:47:45 +01:00
Jonas Kvinge
63631d6b0c CMake: Fix backtrace linking 2024-12-14 05:30:31 +01:00
Jonas Kvinge
21ab2ef1a7 CI: Add FreeBSD and OpenBSD 2024-12-14 05:01:40 +01:00
Jonas Kvinge
a3f96d2b85 CMake: Link Backtrace 2024-12-14 05:01:14 +01:00
Jonas Kvinge
e2c1cb0116 Formatting 2024-12-14 00:55:53 +01:00
Jonas Kvinge
07e295776b Add Spotify to scrobbler 2024-12-10 01:29:57 +01:00
Jonas Kvinge
8604a39d94 Turn on git revision 2024-12-08 17:29:14 +01:00
136 changed files with 3400 additions and 3705 deletions

View File

@@ -10,7 +10,7 @@ jobs:
build-opensuse:
name: Build openSUSE
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -102,14 +102,14 @@ jobs:
- name: Build RPM (Tumbleweed)
if: matrix.opensuse_version == 'tumbleweed'
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
run: rpmbuild -ba strawberry.spec
- name: Build RPM (Leap)
if: matrix.opensuse_version != 'tumbleweed'
working-directory: build
env:
CC: gcc-13
CXX: g++-13
run: rpmbuild -ba ../dist/unix/strawberry.spec
run: rpmbuild -ba strawberry.spec
- name: Set subdir
id: set-subdir
run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT
@@ -132,7 +132,7 @@ jobs:
build-fedora:
name: Build Fedora
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -209,7 +209,7 @@ jobs:
env:
RPM_BUILD_NCPUS: "2"
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
run: rpmbuild -ba strawberry.spec
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
@@ -221,7 +221,7 @@ jobs:
build-openmandriva:
name: Build OpenMandriva
if: github.repository != 'strawberrymusicplayer/strawberry-private' && false
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master' && false
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -302,7 +302,7 @@ jobs:
env:
RPM_BUILD_NCPUS: "2"
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
run: rpmbuild -ba strawberry.spec
- name: Upload artifacts
if: matrix.openmandriva_version != 'cooker'
uses: actions/upload-artifact@v4
@@ -315,7 +315,7 @@ jobs:
build-mageia:
name: Build Mageia
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -398,7 +398,7 @@ jobs:
env:
RPM_BUILD_NCPUS: "2"
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
run: rpmbuild -ba strawberry.spec
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
@@ -410,7 +410,7 @@ jobs:
build-debian:
name: Build Debian
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -471,21 +471,24 @@ jobs:
- name: Create Build Environment
run: cmake -E make_directory build
- name: Configure CMake
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_WERROR=ON
- name: Delete build directory
run: rm -rf build
- name: make deb
run: dpkg-buildpackage -b -d -uc -us -nc -j2
run: dpkg-buildpackage -b -d -uc -us -nc -j4
- name: Copy deb
run: cp ../*.deb .
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: debian-${{matrix.debian_version}}
path: "*.deb"
path: |
*.deb
build-ubuntu:
name: Build Ubuntu
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -549,9 +552,11 @@ jobs:
- name: Create Build Environment
run: cmake -E make_directory build
- name: Configure CMake
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_WERROR=ON
- name: Delete build directory
run: rm -rf build
- name: make deb
run: dpkg-buildpackage -b -d -uc -us -nc -j2
run: dpkg-buildpackage -b -d -uc -us -nc -j4
- name: Copy deb
run: cp ../*.deb ../*.ddeb .
- name: Upload artifacts
@@ -565,7 +570,7 @@ jobs:
upload-ubuntu-ppa:
name: Upload Ubuntu PPA
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/1.1'))) && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/1.1')))
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -634,7 +639,7 @@ jobs:
- name: Create Build Environment
run: cmake -E make_directory build
- name: Configure CMake
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_WERROR=ON
- name: Delete build directory
run: rm -rf build
- name: Import Ubuntu PPA GPG private key
@@ -651,9 +656,60 @@ jobs:
run: dput ppa:jonaski/strawberry ../*_source.changes
build-freebsd:
name: Build FreeBSD
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Build FreeBSD
id: build-freebsd
uses: vmactions/freebsd-vm@v1.1.8
with:
usesh: true
mem: 4096
prepare: pkg install -y git cmake pkgconf boost-libs alsa-lib glib qt6-base qt6-tools sqlite gstreamer1 gstreamer1-plugins chromaprint libebur128 taglib libcdio libmtp gdk-pixbuf2 libgpod fftw3 icu kdsingleapplication googletest pulseaudio
run: |
set -e
git config --global --add safe.directory ${GITHUB_WORKSPACE}
cmake -E make_directory build
cmake -S . -B build -DCMAKE_BUILD_TYPE="Debug"
cmake --build build --config Debug --parallel 4
build-openbsd:
name: Build OpenBSD
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Build OpenBSD
id: build-openbsd
uses: vmactions/openbsd-vm@v1.1.5
with:
usesh: true
mem: 4096
prepare: pkg_add git cmake pkgconf boost glib2 qt6-qtbase qt6-qttools sqlite gstreamer1 gstreamer1-plugins-base chromaprint libebur128 taglib libcdio libmtp gdk-pixbuf libgpod fftw3 icu4c kdsingleapplication pulseaudio
run: |
set -e
export LDFLAGS="-L/usr/local/lib"
git config --global --add safe.directory ${GITHUB_WORKSPACE}
cmake -E make_directory build
cmake -S . -B build -DCMAKE_BUILD_TYPE="Debug"
cmake --build build --config Debug --parallel 4
build-macos-public:
name: Build macOS Public
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
strategy:
fail-fast: false
@@ -931,7 +987,7 @@ jobs:
build-windows-mingw:
name: Build Windows MinGW
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -1120,7 +1176,7 @@ jobs:
build-windows-msvc:
name: Build Windows MSVC
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
runs-on: windows-2022
strategy:
fail-fast: false
@@ -1396,6 +1452,11 @@ jobs:
exit 1
fi
- name: Download MSVC runtime
shell: bash
working-directory: build
run: curl -f -O -L https://aka.ms/vs/17/release/vc_redist.$(test "${{matrix.arch}}" = "x86_64" && echo "x64" || echo "${{matrix.arch}}").exe
- name: Create nsis installer
shell: cmd
working-directory: build

2
.gitignore vendored
View File

@@ -11,6 +11,4 @@
/out
/CMakeSettings.json
/dist/scripts/maketarball.sh
/dist/unix/strawberry.spec
/debian/changelog
/dist/macos/Info.plist

View File

@@ -25,17 +25,12 @@ include(cmake/ParseArguments.cmake)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LINUX ON)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
set(FREEBSD ON)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
set(OPENBSD ON)
endif()
if(LINUX)
include(cmake/Rpm.cmake)
include(cmake/Deb.cmake)
endif()
if(APPLE)
include(cmake/Dmg.cmake)
endif()
@@ -392,7 +387,7 @@ add_executable(strawberry)
if(APPLE)
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/dist/macos/Info.plist")
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/dist/macos/Info.plist")
endif()
if(WIN32 AND NOT ENABLE_WIN32_CONSOLE)
@@ -443,6 +438,7 @@ set(SOURCES
src/core/enginemetadata.cpp
src/core/songmimedata.cpp
src/core/platforminterface.cpp
src/core/standardpaths.cpp
src/utilities/strutils.cpp
src/utilities/envutils.cpp
@@ -1491,6 +1487,7 @@ endif()
target_link_libraries(strawberry_lib PUBLIC
${CMAKE_THREAD_LIBS_INIT}
$<$<BOOL:${HAVE_BACKTRACE}>:${Backtrace_LIBRARIES}>
PkgConfig::GLIB
PkgConfig::GOBJECT
PkgConfig::SQLITE
@@ -1522,7 +1519,6 @@ target_link_libraries(strawberry_lib PUBLIC
$<$<BOOL:${HAVE_MTP}>:PkgConfig::LIBMTP>
$<$<BOOL:${HAVE_GPOD}>:PkgConfig::LIBGPOD PkgConfig::GDK_PIXBUF>
$<$<BOOL:${HAVE_QTSPARKLE}>:PkgConfig::QTSPARKLE>
$<$<BOOL:${FREEBSD}>:execinfo>
$<$<BOOL:${WIN32}>:dsound dwmapi getopt-win::getopt>
$<$<BOOL:${MSVC}>:WindowsApp>
${SINGLEAPPLICATION_LIBRARIES}

View File

@@ -2,6 +2,24 @@ Strawberry Music Player
=======================
ChangeLog
Version 1.2.4 (2025.01.10):
Bugfixes:
* Fixed Spotify songs not being available for scrobbling.
* Fixed leading "A" and "The" articles being skipped for album sort text.
* Fixed thread safety issue when validating playlist songs on startup.
* Fixed filter search not ignoring space after colon when using column based search.
* Fixed KGlobalAccel to use capitalized application name.
* Fixed slash not properly handled when saving a playlist (#1624).
* (Unix) Fixed collection scanner so it ignores special filesystem paths (/sys, /proc, /run, etc) (#1615).
* (Windows) Fixed smart playlist wizard not respecting dark mode with Windows 11 style (#1639).
Enhancements:
* Use XSPF "title" as playlist name when loading and saving playlists (#1624).
* Added support for using album ID when receving album covers for Subsonic songs (#1636).
* Added option for preserving directory structure when trascoding songs (#1637).
* (Windows) Always run MSVC runtime installer to possible fix issues when there is an older runtime installed.
Version 1.2.3 (2024.12.08):
Bugfixes:

View File

@@ -2,5 +2,5 @@ find_program(LSB_RELEASE_EXEC lsb_release)
find_program(DPKG_BUILDPACKAGE dpkg-buildpackage)
if (LSB_RELEASE_EXEC AND DPKG_BUILDPACKAGE)
add_custom_target(deb WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${DPKG_BUILDPACKAGE} -b -d -uc -us)
add_custom_target(deb WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${DPKG_BUILDPACKAGE} -b -d -uc -us -nc -j4)
endif()

View File

@@ -31,7 +31,7 @@ if(MACDEPLOYQT_EXECUTABLE)
add_custom_target(deploy
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/strawberry.app/Contents/{Frameworks,Resources}
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/Info.plist ${CMAKE_BINARY_DIR}/strawberry.app/Contents/
COMMAND cp -v ${CMAKE_BINARY_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/gst-plugin-scanner ${MACDEPLOYQT_CODESIGN}

View File

@@ -64,7 +64,7 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
add_custom_target(rpm
COMMAND ${CMAKE_SOURCE_DIR}/dist/scripts/maketarball.sh
COMMAND ${CMAKE_COMMAND} -E copy strawberry-${STRAWBERRY_VERSION_PACKAGE}.tar.xz ${RPMBUILD_DIR}/SOURCES/
COMMAND ${RPMBUILD_EXEC} -ba ${CMAKE_SOURCE_DIR}/dist/unix/strawberry.spec
COMMAND ${RPMBUILD_EXEC} -ba ${CMAKE_BINARY_DIR}/strawberry.spec
)
endif()

View File

@@ -1,6 +1,6 @@
set(STRAWBERRY_VERSION_MAJOR 1)
set(STRAWBERRY_VERSION_MINOR 2)
set(STRAWBERRY_VERSION_PATCH 3)
set(STRAWBERRY_VERSION_PATCH 4)
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION OFF)

3
debian/clean vendored Normal file
View File

@@ -0,0 +1,3 @@
dist/scripts/maketarball.sh
CMakeCache.txt
CMakeFiles/

1
debian/compat vendored
View File

@@ -1 +0,0 @@
11

4
debian/control vendored
View File

@@ -2,7 +2,7 @@ Source: strawberry
Section: sound
Priority: optional
Maintainer: Jonas Kvinge <jonas@jkvinge.net>
Build-Depends: debhelper (>= 11),
Build-Depends: debhelper-compat (= 12),
git,
make,
cmake,
@@ -28,7 +28,7 @@ Build-Depends: debhelper (>= 11),
libchromaprint-dev,
libfftw3-dev,
libebur128-dev
Standards-Version: 4.6.1
Standards-Version: 4.7.0
Package: strawberry
Architecture: any

15
debian/rules vendored
View File

@@ -1,17 +1,10 @@
#!/usr/bin/make -f
%:
dh $@ --buildsystem=cmake -builddirectory=build
override_dh_auto_clean:
rm -f dist/macos/Info.plist
rm -f dist/unix/strawberry.spec
rm -f dist/scripts/maketarball.sh
rm -f dist/windows/strawberry.nsi
rm -f src/translations/translations.pot
dh_auto_clean
export DH_VERBOSE=1
export DEB_BUILD_MAINT_OPTIONS=hardening=+all
override_dh_installchangelogs:
dh_installchangelogs Changelog
override_dh_auto_test:
%:
dh $@

14
dist/CMakeLists.txt vendored
View File

@@ -1,7 +1,7 @@
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh.in ${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh @ONLY)
if(RPM_DISTRO AND RPM_DATE)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec.in ${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec @ONLY)
endif(RPM_DISTRO AND RPM_DATE)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec.in ${CMAKE_BINARY_DIR}/strawberry.spec @ONLY)
endif()
if(APPLE)
if(DEFINED ENV{MACOSX_DEPLOYMENT_TARGET})
@@ -9,13 +9,13 @@ if(APPLE)
else()
set(LSMinimumSystemVersion 12.0)
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist)
endif(APPLE)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist)
endif()
if(WIN32)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/windres.rc.in ${CMAKE_BINARY_DIR}/windres.rc)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi.in ${CMAKE_BINARY_DIR}/strawberry.nsi @ONLY)
endif(WIN32)
endif()
if(UNIX AND NOT APPLE)
install(FILES ../data/icons/48x48/strawberry.png DESTINATION share/icons/hicolor/48x48/apps/)
@@ -24,9 +24,9 @@ if(UNIX AND NOT APPLE)
install(FILES unix/org.strawberrymusicplayer.strawberry.desktop DESTINATION share/applications)
install(FILES unix/org.strawberrymusicplayer.strawberry.appdata.xml DESTINATION share/metainfo)
install(FILES unix/strawberry.1 DESTINATION share/man/man1)
endif(UNIX AND NOT APPLE)
endif()
if(APPLE)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents")
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/macos/strawberry.icns" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources")
endif()

View File

@@ -51,6 +51,7 @@
</screenshots>
<update_contact>eclipseo@fedoraproject.org</update_contact>
<releases>
<release version="1.2.4" date="2025-01-10"/>
<release version="1.2.3" date="2024-12-08"/>
<release version="1.2.2" date="2024-11-23"/>
<release version="1.2.1" date="2024-11-21"/>

View File

@@ -208,14 +208,16 @@ FunctionEnd
!ifdef msvc
!define vc_redist_file "vc_redist.${arch}.exe"
Function InstallMSVCRuntime
${registry::Read} "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\${arch}" "Version" $R0 $R1
${If} $R0 == ""
SetOutPath "$TEMP"
File "${vc_redist_file}"
; ${registry::Read} "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\${arch}" "Version" $R0 $R1
; ${If} $R0 == ""
SetDetailsView hide
inetc::get /caption "Downloading..." "https://aka.ms/vs/17/release/${vc_redist_file}" "$TEMP\${vc_redist_file}" /end
; inetc::get /caption "Downloading..." "https://aka.ms/vs/17/release/${vc_redist_file}" "$TEMP\${vc_redist_file}" /end
ExecWait '"$TEMP\${vc_redist_file}" /install /passive'
Delete "$TEMP\${vc_redist_file}"
SetDetailsView show
${EndIf}
; ${EndIf}
FunctionEnd
!endif

View File

@@ -50,14 +50,14 @@
#include <QPixmapCache>
#include <QNetworkDiskCache>
#include <QSettings>
#include <QStandardPaths>
#include <QTimer>
#include "includes/scoped_ptr.h"
#include "includes/shared_ptr.h"
#include "core/logging.h"
#include "core/standardpaths.h"
#include "core/database.h"
#include "core/iconloader.h"
#include "core/logging.h"
#include "core/settings.h"
#include "core/songmimedata.h"
#include "collectionfilteroptions.h"
@@ -114,7 +114,7 @@ CollectionModel::CollectionModel(const SharedPtr<CollectionBackend> backend, con
pixmap_no_cover_ = nocover.pixmap(nocover_sizes.last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
icon_disk_cache_->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/' + QLatin1String(kPixmapDiskCacheDir) + u'-' + Song::TextForSource(backend_->source()));
icon_disk_cache_->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u'/' + QLatin1String(kPixmapDiskCacheDir) + u'-' + Song::TextForSource(backend_->source()));
QObject::connect(&*backend_, &CollectionBackend::SongsAdded, this, &CollectionModel::AddReAddOrUpdate);
QObject::connect(&*backend_, &CollectionBackend::SongsChanged, this, &CollectionModel::AddReAddOrUpdate);
@@ -1095,7 +1095,7 @@ QString CollectionModel::SortText(const GroupBy group_by, const Song &song, cons
case GroupBy::Artist:
return SortTextForArtist(song.artist(), sort_skips_articles);
case GroupBy::Album:
return SortTextForArtist(song.album(), sort_skips_articles);
return SortText(song.album());
case GroupBy::AlbumDisc:
return song.album() + SortTextForNumber(std::max(0, song.disc()));
case GroupBy::YearAlbum:

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, 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
@@ -27,6 +27,7 @@
#include <QObject>
#include <QThread>
#include <QIODevice>
#include <QStorageInfo>
#include <QDir>
#include <QDirIterator>
#include <QFile>
@@ -51,6 +52,7 @@
#include "core/settings.h"
#include "utilities/imageutils.h"
#include "constants/timeconstants.h"
#include "constants/filesystemconstants.h"
#include "tagreader/tagreaderclient.h"
#include "collectiondirectory.h"
#include "collectionbackend.h"
@@ -464,6 +466,24 @@ CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs) {
{
const QFileInfo path_info(dir.path);
if (path_info.isSymbolicLink()) {
const QStorageInfo storage_info(path_info.symLinkTarget());
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
qLog(Warning) << "Ignoring collection directory path" << dir.path << "which is a symbolic link to path" << path_info.symLinkTarget() << "with rejected filesystem type" << storage_info.fileSystemType();
return;
}
}
else {
const QStorageInfo storage_info(dir.path);
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
qLog(Warning) << "Ignoring collection directory path" << dir.path << "with rejected filesystem type" << storage_info.fileSystemType();
return;
}
}
}
CancelStop();
watched_dirs_[dir.id] = dir;
@@ -506,17 +526,29 @@ void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const Colle
void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSubdirectory &subdir, const quint64 files_count, ScanTransaction *t, const bool force_noincremental) {
QFileInfo path_info(path);
const QFileInfo path_info(path);
// Do not scan symlinked dirs that are already in collection
if (path_info.isSymLink()) {
QString real_path = path_info.symLinkTarget();
const QString real_path = path_info.symLinkTarget();
const QStorageInfo storage_info(real_path);
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
qLog(Warning) << "Ignoring symbolic link" << path << "which links to" << real_path << "with rejected filesystem type" << storage_info.fileSystemType();
return;
}
// Do not scan symlinked dirs that are already in collection
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
if (real_path.startsWith(dir.path)) {
return;
}
}
}
else {
const QStorageInfo storage_info(path);
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
qLog(Warning) << "Ignoring path" << path << "with rejected filesystem type" << storage_info.fileSystemType();
return;
}
}
bool songs_missing_fingerprint = false;
bool songs_missing_loudness_characteristics = false;
@@ -556,32 +588,40 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
if (stop_or_abort_requested()) return;
QString child(it.next());
QFileInfo child_info(child);
const QString child_filepath = it.next();
const QFileInfo child_fileinfo(child_filepath);
if (child_info.isDir()) {
if (!t->HasSeenSubdir(child)) {
if (child_fileinfo.isSymLink()) {
QStorageInfo storage_info(child_fileinfo.symLinkTarget());
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
qLog(Warning) << "Ignoring symbolic link" << child_filepath << "which links to" << child_fileinfo.symLinkTarget() << "with rejected filesystem type" << storage_info.fileSystemType();
continue;
}
}
if (child_fileinfo.isDir()) {
if (!t->HasSeenSubdir(child_filepath)) {
// We haven't seen this subdirectory before - add it to a list, and later we'll tell the backend about it and scan it.
CollectionSubdirectory new_subdir;
new_subdir.directory_id = -1;
new_subdir.path = child;
new_subdir.mtime = child_info.lastModified().toSecsSinceEpoch();
new_subdir.path = child_filepath;
new_subdir.mtime = child_fileinfo.lastModified().toSecsSinceEpoch();
my_new_subdirs << new_subdir;
}
t->AddToProgress(1);
}
else {
QString ext_part(ExtensionPart(child));
QString dir_part(DirectoryPart(child));
if (Song::kRejectedExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == "qt_temp"_L1) {
QString ext_part(ExtensionPart(child_filepath));
QString dir_part(DirectoryPart(child_filepath));
if (Song::kRejectedExtensions.contains(child_fileinfo.suffix(), Qt::CaseInsensitive) || child_fileinfo.baseName() == "qt_temp"_L1) {
t->AddToProgress(1);
}
else if (sValidImages.contains(ext_part)) {
album_art[dir_part] << child;
album_art[dir_part] << child_filepath;
t->AddToProgress(1);
}
else if (tagreader_client_->IsMediaFileBlocking(child)) {
files_on_disk << child;
else if (tagreader_client_->IsMediaFileBlocking(child_filepath)) {
files_on_disk << child_filepath;
}
else {
t->AddToProgress(1);
@@ -828,7 +868,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
qLog(Error) << "Could not open CUE file" << matching_cue << "for reading:" << cue_file.errorString();
return;
}
const SongList songs = cue_parser_->Load(&cue_file, matching_cue, path, false);
const SongList songs = cue_parser_->Load(&cue_file, matching_cue, path, false).songs;
cue_file.close();
// Update every song that's in the CUE and collection
@@ -915,7 +955,7 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
// Also, watch out for incorrect media files.
// Playlist parser for CUEs considers every entry in sheet valid, and we don't want invalid media getting into collection!
QString file_nfd = file.normalized(QString::NormalizationForm_D);
SongList cue_songs = cue_parser_->Load(&cue_file, matching_cue, path, false);
SongList cue_songs = cue_parser_->Load(&cue_file, matching_cue, path, false).songs;
cue_file.close();
songs.reserve(cue_songs.count());
for (Song &cue_song : cue_songs) {
@@ -1153,7 +1193,7 @@ void CollectionWatcher::RescanPathsNow() {
QMap<QString, quint64> subdir_files_count;
for (const QString &path : paths) {
quint64 files_count = FilesCountForPath(&transaction, path);
const quint64 files_count = FilesCountForPath(&transaction, path);
subdir_files_count[path] = files_count;
transaction.AddToProgressMax(files_count);
}
@@ -1316,18 +1356,45 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &path) {
const QFileInfo path_info(path);
if (path_info.isSymLink()) {
const QString real_path = path_info.symLinkTarget();
const QStorageInfo storage_info(real_path);
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
return 0;
}
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
if (real_path.startsWith(dir.path)) {
return 0;
}
}
}
else {
const QStorageInfo storage_info(path);
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
return 0;
}
}
quint64 i = 0;
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
while (it.hasNext()) {
if (stop_or_abort_requested()) break;
QString child = it.next();
QFileInfo path_info(child);
const QString child_filepath = it.next();
const QFileInfo child_fileinfo(child_filepath);
if (child_fileinfo.isDir()) {
if (child_fileinfo.isSymLink()) {
const QString real_path = child_fileinfo.symLinkTarget();
QStorageInfo storage_info(real_path);
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
continue;
}
if (path_info.isDir()) {
if (path_info.isSymLink()) {
QString real_path = path_info.symLinkTarget();
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
if (real_path.startsWith(dir.path)) {
continue;
@@ -1335,9 +1402,9 @@ quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &
}
}
if (!t->HasSeenSubdir(child) && !path_info.isHidden()) {
if (!t->HasSeenSubdir(child_filepath) && !child_fileinfo.isHidden()) {
// We haven't seen this subdirectory before, so we need to include the file count for this directory too.
i += FilesCountForPath(t, child);
i += FilesCountForPath(t, child_filepath);
}
}
@@ -1385,7 +1452,7 @@ void CollectionWatcher::RescanSongs(const SongList &songs) {
if (stop_or_abort_requested()) break;
if (subdir.path != song_path) continue;
qLog(Debug) << "Rescan for directory ID" << song.directory_id() << "directory" << subdir.path;
quint64 files_count = FilesCountForPath(&transaction, subdir.path);
const quint64 files_count = FilesCountForPath(&transaction, subdir.path);
ScanSubdirectory(song_path, subdir, files_count, &transaction);
scanned_paths << subdir.path;
}

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, 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

View File

@@ -0,0 +1,31 @@
/*
* Strawberry Music Player
* Copyright 2025, 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 FILESYSTEMCONSTANTS_H
#define FILESYSTEMCONSTANTS_H
#include <QByteArrayList>
const QByteArrayList kRejectedFileSystems = QByteArrayList() << "proc"
<< "sysfs"
<< "tmpfs"
<< "devtmpfs";
#endif // FILESYSTEMCONSTANTS_H

View File

@@ -37,6 +37,7 @@ constexpr char kPassword[] = "password";
constexpr char kHTTP2[] = "http2";
constexpr char kVerifyCertificate[] = "verifycertificate";
constexpr char kDownloadAlbumCovers[] = "downloadalbumcovers";
constexpr char kUseAlbumIdForAlbumCovers[] = "usealbumidforalbumcovers";
constexpr char kServerSideScrobbling[] = "serversidescrobbling";
constexpr char kAuthMethod[] = "authmethod";

View File

@@ -39,10 +39,10 @@
#include <QSqlDriver>
#include <QSqlDatabase>
#include <QSqlError>
#include <QStandardPaths>
#include <QScopeGuard>
#include "core/logging.h"
#include "logging.h"
#include "standardpaths.h"
#include "taskmanager.h"
#include "database.h"
#include "sqlquery.h"
@@ -78,7 +78,7 @@ Database::Database(SharedPtr<TaskManager> task_manager, QObject *parent, const Q
connection_id_ = sNextConnectionId++;
}
directory_ = QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
directory_ = QDir::toNativeSeparators(StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation)).replace(u"Strawberry"_s, u"strawberry"_s);
QMutexLocker l(&mutex_);
Connect();

View File

@@ -27,10 +27,10 @@
#include <QString>
#include <QIcon>
#include <QSize>
#include <QStandardPaths>
#include <QSettings>
#include "logging.h"
#include "standardpaths.h"
#include "settings.h"
#include "includes/iconmapper.h"
#include "iconloader.h"
@@ -51,7 +51,7 @@ void IconLoader::Init() {
#endif
QDir dir;
if (dir.exists(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/icons"_s)) {
if (dir.exists(StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/icons"_s)) {
custom_icons_ = true;
}
@@ -125,7 +125,7 @@ QIcon IconLoader::Load(const QString &name, const bool system_icon, const int fi
}
if (custom_icons_) {
QString custom_icon_path = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/icons/%1x%2/%3.png"_s;
QString custom_icon_path = StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/icons/%1x%2/%3.png"_s;
for (int s : std::as_const(sizes)) {
QString filename(custom_icon_path.arg(s).arg(s).arg(name));
if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s));

View File

@@ -1,6 +1,6 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2024-2025, 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
@@ -18,13 +18,26 @@
*/
#include <QSettings>
#include <QVariant>
#include <QString>
#include <QCoreApplication>
#include "settings.h"
using namespace Qt::Literals::StringLiterals;
Settings::Settings(QObject *parent)
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
: QSettings(QCoreApplication::organizationName().toLower(), QCoreApplication::applicationName().toLower(), parent) {}
#else
: QSettings(parent) {}
#endif
Settings::Settings(const QSettings::Scope scope, QObject *parent)
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
: QSettings(scope, QCoreApplication::organizationName().toLower(), QCoreApplication::applicationName().toLower(), parent) {}
#else
: QSettings(scope, parent) {}
#endif
Settings::Settings(const QString &filename, const Format format, QObject *parent)
: QSettings(filename, format, parent) {}

View File

@@ -1,6 +1,6 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2024-2025, 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
@@ -21,14 +21,14 @@
#define SETTINGS_H
#include <QSettings>
#include <QObject>
#include <QVariant>
#include <QString>
class Settings : public QSettings {
Q_OBJECT
public:
explicit Settings(QObject *parent = nullptr);
explicit Settings(const QSettings::Scope scope, QObject *parent = nullptr);
explicit Settings(const QString &filename, const Format format, QObject *parent = nullptr);
};

View File

@@ -45,11 +45,11 @@
#include <QRegularExpression>
#include <QUrl>
#include <QIcon>
#include <QStandardPaths>
#include <QSqlRecord>
#include <taglib/tstring.h>
#include "core/standardpaths.h"
#include "core/iconloader.h"
#include "core/enginemetadata.h"
#include "utilities/strutils.h"
@@ -1335,24 +1335,24 @@ QString Song::ImageCacheDir(const Source source) {
switch (source) {
case Source::Collection:
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/collectionalbumcovers"_s;
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/collectionalbumcovers"_s;
case Source::Subsonic:
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/subsonicalbumcovers"_s;
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/subsonicalbumcovers"_s;
case Source::Tidal:
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/tidalalbumcovers"_s;
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/tidalalbumcovers"_s;
case Source::Spotify:
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/spotifyalbumcovers"_s;
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/spotifyalbumcovers"_s;
case Source::Qobuz:
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/qobuzalbumcovers"_s;
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/qobuzalbumcovers"_s;
case Source::Device:
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/devicealbumcovers"_s;
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/devicealbumcovers"_s;
case Source::LocalFile:
case Source::CDDA:
case Source::Stream:
case Source::SomaFM:
case Source::RadioParadise:
case Source::Unknown:
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/albumcovers"_s;
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/albumcovers"_s;
}
return QString();

View File

@@ -281,7 +281,7 @@ SongLoader::Result SongLoader::LoadLocalAsync(const QString &filename) {
// It's a CUE - create virtual tracks
QFile cue(matching_cue);
if (cue.open(QIODevice::ReadOnly)) {
const SongList songs = cue_parser_->Load(&cue, matching_cue, QDir(filename.section(u'/', 0, -2)));
const SongList songs = cue_parser_->Load(&cue, matching_cue, QDir(filename.section(u'/', 0, -2))).songs;
cue.close();
for (const Song &song : songs) {
if (song.is_valid()) songs_ << song;
@@ -348,7 +348,9 @@ void SongLoader::LoadPlaylist(ParserBase *parser, const QString &filename) {
QFile file(filename);
if (file.open(QIODevice::ReadOnly)) {
songs_ = parser->Load(&file, filename, QFileInfo(filename).path());
const ParserBase::LoadResult result = parser->Load(&file, filename, QFileInfo(filename).path());
songs_ = result.songs;
playlist_name_ = result.playlist_name;
file.close();
}
else {
@@ -424,7 +426,9 @@ void SongLoader::StopTypefind() {
// Parse the playlist
QBuffer buf(&buffer_);
if (buf.open(QIODevice::ReadOnly)) {
songs_ = parser_->Load(&buf);
const ParserBase::LoadResult result = parser_->Load(&buf);
songs_ = result.songs;
playlist_name_ = result.playlist_name;
buf.close();
}

View File

@@ -72,6 +72,7 @@ class SongLoader : public QObject {
const QUrl &url() const { return url_; }
const SongList &songs() const { return songs_; }
const QString &playlist_name() const { return playlist_name_; }
int timeout() const { return timeout_; }
void set_timeout(int msec) { timeout_ = msec; }
@@ -141,6 +142,7 @@ class SongLoader : public QObject {
QUrl url_;
SongList songs_;
QString playlist_name_;
const SharedPtr<UrlHandlers> url_handlers_;
const SharedPtr<CollectionBackendInterface> collection_backend_;

View File

@@ -0,0 +1,82 @@
/*
* Strawberry Music Player
* Copyright 2025, 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 <QtGlobal>
#include <QString>
#include <QDir>
#include <QCoreApplication>
#include "standardpaths.h"
using namespace Qt::StringLiterals;
void StandardPaths::AppendOrganizationAndApplication(QString &path) {
const QString organization_name = QCoreApplication::organizationName().toLower();
if (!organization_name.isEmpty()) {
path += u'/' + organization_name;
}
const QString application_name = QCoreApplication::applicationName().toLower();
if (!application_name.isEmpty()) {
path += u'/' + application_name;
}
}
QString StandardPaths::WritableLocation(const StandardLocation type) {
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
switch (type) {
case StandardLocation::CacheLocation:
case StandardLocation::GenericCacheLocation:{
QString cache_location = qEnvironmentVariable("XDG_CACHE_HOME");
if (!cache_location.startsWith(u'/')) {
cache_location.clear();
}
if (cache_location.isEmpty()) {
cache_location = QDir::homePath() + "/.cache"_L1;
}
if (type == QStandardPaths::CacheLocation) {
AppendOrganizationAndApplication(cache_location);
}
return cache_location;
}
case StandardLocation::AppDataLocation:
case StandardLocation::AppLocalDataLocation:
case StandardLocation::GenericDataLocation:{
QString data_location = qEnvironmentVariable("XDG_DATA_HOME");
if (!data_location.startsWith(u'/')) {
data_location.clear();
}
if (data_location.isEmpty()) {
data_location = QDir::homePath() + "/.local/share"_L1;
}
if (type == StandardLocation::AppDataLocation || type == StandardLocation::AppLocalDataLocation) {
AppendOrganizationAndApplication(data_location);
}
return data_location;
}
default:
break;
}
#endif
return QStandardPaths::writableLocation(type);
}

36
src/core/standardpaths.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* Strawberry Music Player
* Copyright 2025, 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 STANDARDPATHS_H
#define STANDARDPATHS_H
#include <QStandardPaths>
class StandardPaths {
Q_GADGET
public:
using StandardLocation = QStandardPaths::StandardLocation;
static QString WritableLocation(const StandardLocation type);
private:
static void AppendOrganizationAndApplication(QString &path);
};
#endif // STANDARDPATHS_H

View File

@@ -24,7 +24,6 @@
#include <QtGlobal>
#include <QObject>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QIODevice>
#include <QMutex>
#include <QNetworkDiskCache>
@@ -32,6 +31,7 @@
#include <QAbstractNetworkCache>
#include <QUrl>
#include "standardpaths.h"
#include "threadsafenetworkdiskcache.h"
using namespace Qt::Literals::StringLiterals;
@@ -48,9 +48,9 @@ ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject *parent) : QAbstr
if (!sCache) {
sCache = new QNetworkDiskCache;
#ifdef Q_OS_WIN32
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + u"/strawberry/networkcache"_s);
sCache->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::TempLocation) + u"/strawberry/networkcache"_s);
#else
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u"/networkcache"_s);
sCache->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u"/networkcache"_s);
#endif
}

View File

@@ -27,9 +27,9 @@
#include <QObject>
#include <QDir>
#include <QImage>
#include <QStandardPaths>
#include "core/logging.h"
#include "core/standardpaths.h"
#include "core/song.h"
#include "core/temporaryfile.h"
#include "albumcoverloader.h"
@@ -42,7 +42,7 @@ using namespace Qt::Literals::StringLiterals;
CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(const SharedPtr<AlbumCoverLoader> albumcover_loader, QObject *parent)
: QObject(parent),
albumcover_loader_(albumcover_loader),
temp_file_pattern_(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + u"/strawberry-cover-XXXXXX.jpg"_s),
temp_file_pattern_(StandardPaths::WritableLocation(StandardPaths::StandardLocation::TempLocation) + u"/strawberry-cover-XXXXXX.jpg"_s),
id_(0) {
setObjectName(QLatin1String(metaObject()->className()));

View File

@@ -36,10 +36,10 @@
#include <QString>
#include <QUrl>
#include <QImage>
#include <QStandardPaths>
#include "includes/shared_ptr.h"
#include "core/logging.h"
#include "core/standardpaths.h"
#include "core/temporaryfile.h"
#include "core/taskmanager.h"
#include "core/database.h"
@@ -211,9 +211,9 @@ bool GPodDevice::CopyToStorage(const CopyJob &job, QString &error_text) {
bool result = false;
if (!job.cover_image_.isNull()) {
#ifdef Q_OS_LINUX
QString temp_path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u"/organize"_s;
QString temp_path = StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u"/organize"_s;
#else
QString temp_path = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
QString temp_path = StandardPaths::WritableLocation(StandardPaths::StandardLocation::TempLocation);
#endif
if (!QDir(temp_path).exists()) QDir().mkpath(temp_path);
SharedPtr<TemporaryFile> cover_file = make_shared<TemporaryFile>(temp_path + u"/track-albumcover-XXXXXX.jpg"_s);

View File

@@ -62,7 +62,6 @@
#include "gstbufferconsumer.h"
using namespace Qt::Literals::StringLiterals;
using std::make_shared;
#ifdef __clang__
# pragma clang diagnostic push
@@ -897,7 +896,7 @@ void GstEngine::StopTimers() {
GstEnginePipelinePtr GstEngine::CreatePipeline() {
GstEnginePipelinePtr pipeline = make_shared<GstEnginePipeline>();
GstEnginePipelinePtr pipeline = GstEnginePipelinePtr(new GstEnginePipeline);
pipeline->set_output_device(output_, device_);
pipeline->set_exclusive_mode(exclusive_mode_);
pipeline->set_volume_enabled(volume_control_);

View File

@@ -174,7 +174,9 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
logged_unsupported_analyzer_format_(false),
about_to_finish_(false),
finish_requested_(false),
finished_(false) {
finished_(false),
set_state_in_progress_(0),
set_state_async_in_progress_(0) {
eq_band_gains_.reserve(kEqBandCount);
for (int i = 0; i < kEqBandCount; ++i) eq_band_gains_ << 0;
@@ -419,7 +421,7 @@ bool GstEnginePipeline::Finish() {
Disconnect();
if (IsStateNull()) {
if (IsStateNull() && set_state_async_in_progress_ == 0 && set_state_in_progress_ == 0) {
finished_ = true;
}
else {
@@ -1792,7 +1794,17 @@ bool GstEnginePipeline::IsStateNull() const {
void GstEnginePipeline::SetStateAsync(const GstState state) {
QMetaObject::invokeMethod(this, "SetState", Qt::QueuedConnection, Q_ARG(GstState, state));
++set_state_async_in_progress_;
QMetaObject::invokeMethod(this, "SetStateAsyncSlot", Qt::QueuedConnection, Q_ARG(GstState, state));
}
void GstEnginePipeline::SetStateAsyncSlot(const GstState state) {
--set_state_async_in_progress_;
SetState(state);
}
@@ -1800,6 +1812,8 @@ QFuture<GstStateChangeReturn> GstEnginePipeline::SetState(const GstState state)
qLog(Debug) << "Setting pipeline" << id() << "state to" << GstStateText(state);
++set_state_in_progress_;
QFutureWatcher<GstStateChangeReturn> *watcher = new QFutureWatcher<GstStateChangeReturn>();
QObject::connect(watcher, &QFutureWatcher<GstStateChangeReturn>::finished, this, [this, watcher, state]() {
const GstStateChangeReturn state_change_return = watcher->result();
@@ -1815,13 +1829,15 @@ QFuture<GstStateChangeReturn> GstEnginePipeline::SetState(const GstState state)
void GstEnginePipeline::SetStateFinishedSlot(const GstState state, const GstStateChangeReturn state_change_return) {
--set_state_in_progress_;
switch (state_change_return) {
case GST_STATE_CHANGE_SUCCESS:
case GST_STATE_CHANGE_ASYNC:
case GST_STATE_CHANGE_NO_PREROLL:
qLog(Debug) << "Pipeline" << id() << "state successfully set to" << GstStateText(state);
Q_EMIT SetStateFinished(state_change_return);
if (!finished_.value() && finish_requested_.value()) {
if (!finished_.value() && finish_requested_.value() && set_state_async_in_progress_ == 0 && set_state_in_progress_ == 0) {
finished_ = true;
Q_EMIT Finished();
}

View File

@@ -41,6 +41,7 @@
#include <QVariant>
#include <QString>
#include <QUrl>
#include <QSharedPointer>
#include "includes/shared_ptr.h"
#include "includes/mutex_protected.h"
@@ -197,6 +198,7 @@ class GstEnginePipeline : public QObject {
void ProcessPendingSeek(const GstState state);
private Q_SLOTS:
void SetStateAsyncSlot(const GstState state);
void SetStateFinishedSlot(const GstState state, const GstStateChangeReturn state_change_return);
void SetFaderVolume(const qreal volume);
void FaderTimelineStateChanged(const QTimeLine::State state);
@@ -368,8 +370,11 @@ class GstEnginePipeline : public QObject {
mutex_protected<bool> about_to_finish_;
mutex_protected<bool> finish_requested_;
mutex_protected<bool> finished_;
mutex_protected<int> set_state_in_progress_;
mutex_protected<int> set_state_async_in_progress_;
};
using GstEnginePipelinePtr = SharedPtr<GstEnginePipeline>;
using GstEnginePipelinePtr = QSharedPointer<GstEnginePipeline>;
#endif // GSTENGINEPIPELINE_H

View File

@@ -26,12 +26,12 @@
#include <gst/pbutils/pbutils.h>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QString>
#include <QDir>
#include <QFile>
#include "core/logging.h"
#include "core/standardpaths.h"
#include "utilities/envutils.h"
#ifdef HAVE_MOODBAR
@@ -145,7 +145,7 @@ void SetEnvironment() {
#endif // USE_BUNDLE
#if defined(Q_OS_WIN32) || defined(Q_OS_MACOS)
QString gst_registry_filename = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QStringLiteral("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion());
QString gst_registry_filename = StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + QStringLiteral("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion());
qLog(Debug) << "Setting GStreamer registry file to" << gst_registry_filename;
Utilities::SetEnv("GST_REGISTRY", gst_registry_filename);
#endif

View File

@@ -188,8 +188,15 @@ FilterTree *FilterParser::parseSearchTerm() {
QString value;
bool in_quotes = false;
bool previous_char_operator = false;
for (; iter_ != end_; ++iter_) {
if (previous_char_operator) {
if (iter_->isSpace()) {
continue;
}
previous_char_operator = false;
}
if (in_quotes) {
if (*iter_ == u'"') {
in_quotes = false;
@@ -206,6 +213,7 @@ FilterTree *FilterParser::parseSearchTerm() {
column = buf_.toLower();
buf_.clear();
prefix.clear(); // Prefix isn't allowed here - let's ignore it
previous_char_operator = true;
}
else if (iter_->isSpace() || *iter_ == u'(' || *iter_ == u')' || *iter_ == u'-') {
break;
@@ -214,9 +222,11 @@ FilterTree *FilterParser::parseSearchTerm() {
// We don't know whether there is a column part in this search term thus we assume the latter and just try and read a prefix
if (prefix.isEmpty() && (*iter_ == u'>' || *iter_ == u'<' || *iter_ == u'=' || *iter_ == u'!')) {
prefix += *iter_;
previous_char_operator = true;
}
else if (prefix != u'=' && *iter_ == u'=') {
prefix += *iter_;
previous_char_operator = true;
}
else {
buf_ += *iter_;

View File

@@ -57,6 +57,16 @@ class mutex_protected : public boost::noncopyable {
value_ = value;
}
void operator++() {
QMutexLocker l(&mutex_);
++value_;
}
void operator--() {
QMutexLocker l(&mutex_);
--value_;
}
private:
T value_;
mutable QMutex mutex_;

View File

@@ -48,7 +48,6 @@
#include <QApplication>
#include <QCoreApplication>
#include <QSysInfo>
#include <QStandardPaths>
#include <QLibraryInfo>
#include <QFileDevice>
#include <QIODevice>
@@ -70,6 +69,7 @@
#include "includes/shared_ptr.h"
#include "core/logging.h"
#include "core/standardpaths.h"
#include "core/settings.h"
#include "utilities/envutils.h"
@@ -131,13 +131,8 @@ int main(int argc, char *argv[]) {
mac::MacMain();
#endif
#if defined(Q_OS_WIN32) || defined(Q_OS_MACOS)
QCoreApplication::setApplicationName(u"Strawberry"_s);
QCoreApplication::setOrganizationName(u"Strawberry"_s);
#else
QCoreApplication::setApplicationName(u"strawberry"_s);
QCoreApplication::setOrganizationName(u"strawberry"_s);
#endif
QCoreApplication::setApplicationVersion(QStringLiteral(STRAWBERRY_VERSION_DISPLAY));
QCoreApplication::setOrganizationDomain(u"strawberrymusicplayer.org"_s);
@@ -157,7 +152,7 @@ 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);
KDSingleApplication single_app(QCoreApplication::applicationName(), KDSingleApplication::Option::IncludeUsernameInSocketName);
KDSingleApplication single_app(QCoreApplication::applicationName().toLower(), KDSingleApplication::Option::IncludeUsernameInSocketName);
// 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());
@@ -174,7 +169,7 @@ int main(int argc, char *argv[]) {
#ifdef Q_OS_MACOS
// Must happen after QCoreApplication::setOrganizationName().
Utilities::SetEnv("XDG_CONFIG_HOME", QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
Utilities::SetEnv("XDG_CONFIG_HOME", StandardPaths::WritableLocation(StandardPaths::StandardLocation::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.
@@ -194,7 +189,7 @@ int main(int argc, char *argv[]) {
QGuiApplication::setQuitOnLastWindowClosed(false);
QApplication a(argc, argv);
KDSingleApplication single_app(QCoreApplication::applicationName(), KDSingleApplication::Option::IncludeUsernameInSocketName);
KDSingleApplication single_app(QCoreApplication::applicationName().toLower(), KDSingleApplication::Option::IncludeUsernameInSocketName);
if (!single_app.isPrimaryInstance()) {
if (options.is_empty()) {
qLog(Info) << "Strawberry is already running - activating existing window (2)";

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-2025, 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
@@ -23,6 +23,7 @@
#include <QByteArray>
#include <QUrl>
#include "includes/shared_ptr.h"
#include "core/song.h"
#include "core/settings.h"
#include "core/player.h"
@@ -33,6 +34,8 @@
#include "moodbarloader.h"
#include "moodbarpipeline.h"
using std::make_shared;
MoodbarController::MoodbarController(const SharedPtr<Player> player, const SharedPtr<MoodbarLoader> moodbar_loader, QObject *parent)
: QObject(parent),
player_(player),
@@ -56,25 +59,27 @@ void MoodbarController::CurrentSongChanged(const Song &song) {
if (!enabled_) return;
QByteArray data;
MoodbarPipeline *pipeline = nullptr;
const MoodbarLoader::Result result = moodbar_loader_->Load(song.url(), song.has_cue(), &data, &pipeline);
switch (result) {
case MoodbarLoader::Result::CannotLoad:
Q_EMIT CurrentMoodbarDataChanged(QByteArray());
const MoodbarLoader::LoadResult load_result = moodbar_loader_->Load(song.url(), song.has_cue());
switch (load_result.status) {
case MoodbarLoader::LoadStatus::CannotLoad:
Q_EMIT CurrentMoodbarDataChanged();
break;
case MoodbarLoader::Result::Loaded:
Q_EMIT CurrentMoodbarDataChanged(data);
case MoodbarLoader::LoadStatus::Loaded:
Q_EMIT CurrentMoodbarDataChanged(load_result.data);
break;
case MoodbarLoader::Result::WillLoadAsync:
// Emit an empty array for now so the GUI reverts to a normal progress
// bar. Our slot will be called when the data is actually loaded.
Q_EMIT CurrentMoodbarDataChanged(QByteArray());
case MoodbarLoader::LoadStatus::WillLoadAsync:
// Emit an empty array for now so the GUI reverts to a normal progressbar. Our slot will be called when the data is actually loaded.
Q_EMIT CurrentMoodbarDataChanged();
QObject::connect(pipeline, &MoodbarPipeline::Finished, this, [this, pipeline, song]() { AsyncLoadComplete(pipeline, song.url()); });
MoodbarPipelinePtr pipeline = load_result.pipeline;
Q_ASSERT(pipeline);
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
*connection = QObject::connect(&*pipeline, &MoodbarPipeline::Finished, this, [this, connection, pipeline, song]() {
AsyncLoadComplete(pipeline, song.url());
QObject::disconnect(*connection);
});
break;
}
@@ -83,12 +88,12 @@ void MoodbarController::CurrentSongChanged(const Song &song) {
void MoodbarController::PlaybackStopped() {
if (enabled_) {
Q_EMIT CurrentMoodbarDataChanged(QByteArray());
Q_EMIT CurrentMoodbarDataChanged();
}
}
void MoodbarController::AsyncLoadComplete(MoodbarPipeline *pipeline, const QUrl &url) {
void MoodbarController::AsyncLoadComplete(MoodbarPipelinePtr pipeline, const QUrl &url) {
// Is this song still playing?
PlaylistItemPtr current_item = player_->GetCurrentItem();

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-2025, 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
@@ -28,9 +28,9 @@
#include <QUrl>
#include "includes/shared_ptr.h"
#include "moodbarpipeline.h"
class MoodbarLoader;
class MoodbarPipeline;
class Song;
class Player;
@@ -43,15 +43,15 @@ class MoodbarController : public QObject {
void ReloadSettings();
Q_SIGNALS:
void CurrentMoodbarDataChanged(const QByteArray &data);
void CurrentMoodbarDataChanged(const QByteArray &data = QByteArray());
void StyleChanged();
public Q_SLOTS:
void CurrentSongChanged(const Song &song);
void PlaybackStopped();
private Q_SLOTS:
void AsyncLoadComplete(MoodbarPipeline *pipeline, const QUrl &url);
private:
void AsyncLoadComplete(MoodbarPipelinePtr pipeline, const QUrl &url);
private:
const SharedPtr<Player> player_;

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-2025, 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
@@ -36,6 +36,8 @@
#include <QPainter>
#include <QRect>
#include "includes/shared_ptr.h"
#include "core/logging.h"
#include "core/settings.h"
#include "playlist/playlist.h"
#include "playlist/playlistview.h"
@@ -48,6 +50,8 @@
#include "constants/moodbarsettings.h"
using std::make_shared;
MoodbarItemDelegate::Data::Data() : state_(State::None) {}
MoodbarItemDelegate::MoodbarItemDelegate(const SharedPtr<MoodbarLoader> moodbar_loader, PlaylistView *playlist_view, QObject *parent)
@@ -113,7 +117,11 @@ QPixmap MoodbarItemDelegate::PixmapForIndex(const QModelIndex &idx, const QSize
}
else {
data = new Data;
if (!data_.insert(url, data)) return QPixmap();
if (!data_.insert(url, data)) {
qLog(Error) << "Could not insert moodbar data for URL" << url << "into cache";
delete data;
return QPixmap();
}
}
data->indexes_.insert(idx);
@@ -150,21 +158,24 @@ void MoodbarItemDelegate::StartLoadingData(const QUrl &url, const bool has_cue,
data->state_ = Data::State::LoadingData;
// Load a mood file for this song and generate some colors from it
QByteArray bytes;
MoodbarPipeline *pipeline = nullptr;
switch (moodbar_loader_->Load(url, has_cue, &bytes, &pipeline)) {
case MoodbarLoader::Result::CannotLoad:
const MoodbarLoader::LoadResult load_result = moodbar_loader_->Load(url, has_cue);
switch (load_result.status) {
case MoodbarLoader::LoadStatus::CannotLoad:
data->state_ = Data::State::CannotLoad;
break;
case MoodbarLoader::Result::Loaded:
// We got the data immediately.
StartLoadingColors(url, bytes, data);
case MoodbarLoader::LoadStatus::Loaded:
StartLoadingColors(url, load_result.data, data);
break;
case MoodbarLoader::Result::WillLoadAsync:
// Maybe in a little while.
QObject::connect(pipeline, &MoodbarPipeline::Finished, this, [this, url, pipeline]() { DataLoaded(url, pipeline); });
case MoodbarLoader::LoadStatus::WillLoadAsync:
MoodbarPipelinePtr pipeline = load_result.pipeline;
Q_ASSERT(pipeline);
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
*connection = QObject::connect(&*pipeline, &MoodbarPipeline::Finished, this, [this, connection, url, pipeline]() {
DataLoaded(url, pipeline);
QObject::disconnect(*connection);
});
break;
}
@@ -194,7 +205,7 @@ void MoodbarItemDelegate::ReloadAllColors() {
}
void MoodbarItemDelegate::DataLoaded(const QUrl &url, MoodbarPipeline *pipeline) {
void MoodbarItemDelegate::DataLoaded(const QUrl &url, MoodbarPipelinePtr pipeline) {
if (!data_.contains(url)) return;
@@ -291,8 +302,7 @@ void MoodbarItemDelegate::ImageLoaded(const QUrl &url, const QImage &image) {
}
if (source_index.model() != playlist) {
// The pixmap was for an index in a different playlist, maybe the user
// switched to a different one.
// The pixmap was for an index in a different playlist, maybe the user switched to a different one.
continue;
}

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-2025, 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
@@ -38,10 +38,10 @@
#include "includes/shared_ptr.h"
#include "constants/moodbarsettings.h"
#include "moodbarpipeline.h"
class QPainter;
class MoodbarLoader;
class MoodbarPipeline;
class PlaylistView;
class MoodbarItemDelegate : public QItemDelegate {
@@ -55,13 +55,14 @@ class MoodbarItemDelegate : public QItemDelegate {
private Q_SLOTS:
void ReloadSettings();
void DataLoaded(const QUrl &url, MoodbarPipeline *pipeline);
void ColorsLoaded(const QUrl &url, const ColorVector &colors);
void ImageLoaded(const QUrl &url, const QImage &image);
Q_SIGNALS:
void StyleChanged();
private:
void DataLoaded(const QUrl &url, MoodbarPipelinePtr pipeline);
void ColorsLoaded(const QUrl &url, const ColorVector &colors);
void ImageLoaded(const QUrl &url, const QImage &image);
private:
struct Data {
Data();

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-2025, 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
@@ -28,21 +28,21 @@
#include <QObject>
#include <QThread>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QIODevice>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QAbstractNetworkCache>
#include <QNetworkDiskCache>
#include <QTimer>
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <QSettings>
#include "includes/scoped_ptr.h"
#include "includes/shared_ptr.h"
#include "core/logging.h"
#include "core/standardpaths.h"
#include "core/settings.h"
#include "moodbarpipeline.h"
@@ -51,6 +51,7 @@
using namespace std::chrono_literals;
using namespace Qt::Literals::StringLiterals;
using std::make_shared;
#ifdef Q_OS_WIN32
# include <windows.h>
@@ -63,7 +64,10 @@ MoodbarLoader::MoodbarLoader(QObject *parent)
kMaxActiveRequests(qMax(1, QThread::idealThreadCount() / 2)),
save_(false) {
cache_->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u"/moodbar"_s);
setObjectName(QLatin1String(metaObject()->className()));
thread_->setObjectName(objectName());
cache_->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u"/moodbar"_s);
cache_->setMaximumCacheSize(60LL * 1024LL * 1024LL); // 60MB - enough for 20,000 moodbars
ReloadSettings();
@@ -104,16 +108,15 @@ QUrl MoodbarLoader::CacheUrlEntry(const QString &filename) {
}
MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, QByteArray *data, MoodbarPipeline **async_pipeline) {
MoodbarLoader::LoadResult MoodbarLoader::Load(const QUrl &url, const bool has_cue) {
if (!url.isLocalFile() || has_cue) {
return Result::CannotLoad;
return LoadStatus::CannotLoad;
}
// Are we in the middle of loading this moodbar already?
if (requests_.contains(url)) {
*async_pipeline = requests_.value(url);
return Result::WillLoadAsync;
return LoadResult(LoadStatus::WillLoadAsync, requests_.value(url));
}
// Check if a mood file exists for this file already
@@ -121,16 +124,18 @@ MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, Q
const QStringList possible_mood_files = MoodFilenames(filename);
for (const QString &possible_mood_file : possible_mood_files) {
QFile f(possible_mood_file);
if (f.exists()) {
if (f.open(QIODevice::ReadOnly)) {
QFile file(possible_mood_file);
if (file.exists()) {
if (file.open(QIODevice::ReadOnly)) {
qLog(Info) << "Loading moodbar data from" << possible_mood_file;
*data = f.readAll();
f.close();
return Result::Loaded;
const QByteArray data = file.readAll();
file.close();
if (!data.isEmpty()) {
return LoadResult(LoadStatus::Loaded, data);
}
}
else {
qLog(Error) << "Failed to load moodbar data from" << possible_mood_file << f.errorString();
qLog(Error) << "Failed to load moodbar data from" << possible_mood_file << file.errorString();
}
}
}
@@ -142,9 +147,9 @@ MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, Q
ScopedPtr<QIODevice> device_cache_file(cache_->data(disk_cache_metadata.url()));
if (device_cache_file) {
qLog(Info) << "Loading cached moodbar data for" << filename;
*data = device_cache_file->readAll();
if (!data->isEmpty()) {
return Result::Loaded;
const QByteArray data = device_cache_file->readAll();
if (!data.isEmpty()) {
return LoadResult(LoadStatus::Loaded, data);
}
}
}
@@ -152,17 +157,20 @@ MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, Q
if (!thread_->isRunning()) thread_->start(QThread::IdlePriority);
// There was no existing file, analyze the audio file and create one.
MoodbarPipeline *pipeline = new MoodbarPipeline(url);
MoodbarPipelinePtr pipeline = MoodbarPipelinePtr(new MoodbarPipeline(url));
pipeline->moveToThread(thread_);
QObject::connect(pipeline, &MoodbarPipeline::Finished, this, [this, pipeline, url]() { RequestFinished(pipeline, url); });
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
*connection = QObject::connect(&*pipeline, &MoodbarPipeline::Finished, this, [this, connection, pipeline, url]() {
RequestFinished(pipeline, url);
QObject::disconnect(*connection);
});
requests_[url] = pipeline;
queued_requests_ << url;
MaybeTakeNextRequest();
*async_pipeline = pipeline;
return Result::WillLoadAsync;
return LoadResult(LoadStatus::WillLoadAsync, pipeline);
}
@@ -178,15 +186,17 @@ void MoodbarLoader::MaybeTakeNextRequest() {
active_requests_ << url;
qLog(Info) << "Creating moodbar data for" << url.toLocalFile();
QMetaObject::invokeMethod(requests_.value(url), &MoodbarPipeline::Start, Qt::QueuedConnection);
MoodbarPipelinePtr pipeline = requests_.value(url);
QMetaObject::invokeMethod(&*pipeline, &MoodbarPipeline::Start, Qt::QueuedConnection);
}
void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
void MoodbarLoader::RequestFinished(MoodbarPipelinePtr pipeline, const QUrl &url) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
if (request->success()) {
if (pipeline->success()) {
const QString filename = url.toLocalFile();
@@ -201,7 +211,7 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
QIODevice *device_cache_file = cache_->prepare(disk_cache_metadata);
if (device_cache_file) {
const qint64 data_written = device_cache_file->write(request->data());
const qint64 data_written = device_cache_file->write(pipeline->data());
if (data_written > 0) {
cache_->insert(device_cache_file);
}
@@ -213,7 +223,7 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
const QString mood_filename(mood_filenames[0]);
QFile mood_file(mood_filename);
if (mood_file.open(QIODevice::WriteOnly)) {
if (mood_file.write(request->data()) <= 0) {
if (mood_file.write(pipeline->data()) <= 0) {
qLog(Error) << "Error writing to mood file" << mood_filename << mood_file.errorString();
}
mood_file.close();
@@ -233,8 +243,6 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
requests_.remove(url);
active_requests_.remove(url);
QTimer::singleShot(1s, request, &MoodbarLoader::deleteLater);
MaybeTakeNextRequest();
}

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-2025, 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
@@ -30,10 +30,11 @@
#include <QStringList>
#include <QUrl>
#include "moodbarpipeline.h"
class QThread;
class QByteArray;
class QNetworkDiskCache;
class MoodbarPipeline;
class MoodbarLoader : public QObject {
Q_OBJECT
@@ -42,7 +43,7 @@ class MoodbarLoader : public QObject {
explicit MoodbarLoader(QObject *parent = nullptr);
~MoodbarLoader() override;
enum class Result {
enum class LoadStatus {
// The URL isn't a local file or the moodbar plugin was not available -
// moodbar data can never be loaded.
CannotLoad,
@@ -55,17 +56,25 @@ class MoodbarLoader : public QObject {
WillLoadAsync
};
class LoadResult {
public:
LoadResult(const LoadStatus _status) : status(_status) {}
LoadResult(const LoadStatus _status, MoodbarPipelinePtr _pipeline) : status(_status), pipeline(_pipeline) {}
LoadResult(const LoadStatus _status, const QByteArray &_data) : status(_status), data(_data) {}
LoadStatus status;
MoodbarPipelinePtr pipeline;
QByteArray data;
};
void ReloadSettings();
Result Load(const QUrl &url, const bool has_cue, QByteArray *data, MoodbarPipeline **async_pipeline);
private Q_SLOTS:
void RequestFinished(MoodbarPipeline *request, const QUrl &url);
void MaybeTakeNextRequest();
LoadResult Load(const QUrl &url, const bool has_cue);
private:
static QStringList MoodFilenames(const QString &song_filename);
static QUrl CacheUrlEntry(const QString &filename);
void RequestFinished(MoodbarPipelinePtr pipeline, const QUrl &url);
void MaybeTakeNextRequest();
Q_SIGNALS:
void MoodbarEnabled(const bool enabled);
@@ -78,7 +87,7 @@ class MoodbarLoader : public QObject {
const int kMaxActiveRequests;
QMap<QUrl, MoodbarPipeline*> requests_;
QMap<QUrl, MoodbarPipelinePtr> requests_;
QList<QUrl> queued_requests_;
QSet<QUrl> active_requests_;

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-2025, 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
@@ -56,20 +56,24 @@ MoodbarPipeline::MoodbarPipeline(const QUrl &url, QObject *parent)
success_(false),
running_(false) {}
MoodbarPipeline::~MoodbarPipeline() { Cleanup(); }
MoodbarPipeline::~MoodbarPipeline() {
GstElement *MoodbarPipeline::CreateElement(const QString &factory_name) {
Cleanup();
GstElement *ret = gst_element_factory_make(factory_name.toLatin1().constData(), nullptr);
}
if (ret) {
gst_bin_add(GST_BIN(pipeline_), ret);
GstElement *MoodbarPipeline::CreateElement(const QByteArray &factory_name) {
GstElement *element = gst_element_factory_make(factory_name.constData(), nullptr);
if (element) {
gst_bin_add(GST_BIN(pipeline_), element);
}
else {
qLog(Warning) << "Unable to create gstreamer element" << factory_name;
}
return ret;
return element;
}
@@ -86,6 +90,7 @@ QByteArray MoodbarPipeline::ToGstUrl(const QUrl &url) {
void MoodbarPipeline::Start() {
Q_ASSERT(QThread::currentThread() == thread());
Q_ASSERT(QThread::currentThread() != qApp->thread());
Utilities::SetThreadIOPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
@@ -96,10 +101,10 @@ void MoodbarPipeline::Start() {
pipeline_ = gst_pipeline_new("moodbar-pipeline");
GstElement *decodebin = CreateElement(u"uridecodebin"_s);
convert_element_ = CreateElement(u"audioconvert"_s);
GstElement *spectrum = CreateElement(u"strawberry-fastspectrum"_s);
GstElement *fakesink = CreateElement(u"fakesink"_s);
GstElement *decodebin = CreateElement("uridecodebin");
convert_element_ = CreateElement("audioconvert");
GstElement *spectrum = CreateElement("strawberry-fastspectrum");
GstElement *fakesink = CreateElement("fakesink");
if (!decodebin || !convert_element_ || !spectrum || !fakesink) {
gst_object_unref(GST_OBJECT(pipeline_));
@@ -204,6 +209,8 @@ GstBusSyncReply MoodbarPipeline::BusCallbackSync(GstBus *bus, GstMessage *messag
MoodbarPipeline *instance = reinterpret_cast<MoodbarPipeline*>(self);
if (!instance->running_) return GST_BUS_PASS;
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_EOS:
instance->Stop(true);
@@ -224,23 +231,34 @@ GstBusSyncReply MoodbarPipeline::BusCallbackSync(GstBus *bus, GstMessage *messag
void MoodbarPipeline::Stop(const bool success) {
success_ = success;
running_ = false;
QMetaObject::invokeMethod(this, "Finish", Qt::QueuedConnection, Q_ARG(bool, success));
}
void MoodbarPipeline::Finish(const bool success) {
Q_ASSERT(QThread::currentThread() == thread());
Q_ASSERT(QThread::currentThread() != qApp->thread());
success_ = success;
if (builder_) {
data_ = builder_->Finish(1000);
builder_.reset();
}
Cleanup();
Q_EMIT Finished(success);
}
void MoodbarPipeline::Cleanup() {
Q_ASSERT(QThread::currentThread() == thread());
Q_ASSERT(QThread::currentThread() != qApp->thread());
running_ = false;
if (pipeline_) {
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
if (bus) {

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-2025, 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
@@ -26,6 +26,7 @@
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <QSharedPointer>
#include <glib.h>
#include <glib-object.h>
@@ -46,18 +47,18 @@ class MoodbarPipeline : public QObject {
bool success() const { return success_; }
const QByteArray &data() const { return data_; }
public Q_SLOTS:
void Start();
Q_INVOKABLE void Start();
Q_SIGNALS:
void Finished(const bool success);
private:
GstElement *CreateElement(const QString &factory_name);
GstElement *CreateElement(const QByteArray &factory_name);
QByteArray ToGstUrl(const QUrl &url);
void ReportError(GstMessage *msg);
void Stop(const bool success);
Q_INVOKABLE void Finish(const bool success);
void Cleanup();
static void NewPadCallback(GstElement *element, GstPad *pad, gpointer self);
@@ -75,4 +76,6 @@ class MoodbarPipeline : public QObject {
QByteArray data_;
};
using MoodbarPipelinePtr = QSharedPointer<MoodbarPipeline>;
#endif // MOODBARPIPELINE_H

View File

@@ -201,7 +201,7 @@ void MoodbarProxyStyle::drawComplexControl(ComplexControl control, const QStyleO
}
void MoodbarProxyStyle::Render(ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget) {
void MoodbarProxyStyle::Render(const ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget) {
const qreal fade_value = fade_timeline_->currentValue();

View File

@@ -84,7 +84,7 @@ class MoodbarProxyStyle : public QProxyStyle {
private:
void NextState();
void Render(ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget);
void Render(const ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget);
void EnsureMoodbarRendered(const QStyleOptionSlider *opt);
void DrawArrow(const QStyleOptionSlider *option, QPainter *painter) const;
void ShowContextMenu(const QPoint pos);
@@ -92,7 +92,7 @@ class MoodbarProxyStyle : public QProxyStyle {
static QPixmap MoodbarPixmap(const ColorVector &colors, const QSize size, const QPalette &palette, const QStyleOptionSlider *opt);
private Q_SLOTS:
void FaderValueChanged(qreal value);
void FaderValueChanged(const qreal value);
void SetStyle(QAction *action);
Q_SIGNALS:

View File

@@ -106,8 +106,7 @@ Mpris2::Mpris2(const SharedPtr<Player> player,
: QObject(parent),
player_(player),
playlist_manager_(playlist_manager),
current_albumcover_loader_(current_albumcover_loader),
app_name_(QCoreApplication::applicationName()) {
current_albumcover_loader_(current_albumcover_loader) {
new Mpris2Root(this);
new Mpris2TrackList(this);
@@ -135,8 +134,6 @@ Mpris2::Mpris2(const SharedPtr<Player> player,
QObject::connect(&*playlist_manager_, &PlaylistManager::PlaylistChanged, this, &Mpris2::PlaylistChangedSlot);
QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentChanged, this, &Mpris2::PlaylistCollectionChanged);
app_name_[0] = app_name_[0].toUpper();
QStringList data_dirs = QString::fromUtf8(qgetenv("XDG_DATA_DIRS")).split(u':');
if (!data_dirs.contains("/usr/local/share"_L1)) {
@@ -238,7 +235,7 @@ bool Mpris2::CanRaise() const { return true; }
bool Mpris2::HasTrackList() const { return true; }
QString Mpris2::Identity() const { return app_name_; }
QString Mpris2::Identity() const { return QCoreApplication::applicationName(); }
QString Mpris2::DesktopEntryAbsolutePath() const {

View File

@@ -242,7 +242,6 @@ class Mpris2 : public QObject {
const SharedPtr<PlaylistManager> playlist_manager_;
const SharedPtr<CurrentAlbumCoverLoader> current_albumcover_loader_;
QString app_name_;
QString desktopfilepath_;
QVariantMap last_metadata_;

View File

@@ -292,7 +292,7 @@ void Organize::ProcessSomeFiles() {
}
Song::FileType Organize::CheckTranscode(Song::FileType original_type) const {
Song::FileType Organize::CheckTranscode(const Song::FileType original_type) const {
if (original_type == Song::FileType::Stream) return Song::FileType::Unknown;
@@ -319,7 +319,7 @@ Song::FileType Organize::CheckTranscode(Song::FileType original_type) const {
}
void Organize::SetSongProgress(float progress, bool transcoded) {
void Organize::SetSongProgress(const float progress, const bool transcoded) {
const int max = transcoded ? 50 : 100;
current_copy_progress_ = (transcoded ? 50 : 0) + qBound(0, static_cast<int>(progress * static_cast<float>(max)), max - 1);
@@ -359,7 +359,7 @@ void Organize::UpdateProgress() {
}
void Organize::FileTranscoded(const QString &input, const QString &output, bool success) {
void Organize::FileTranscoded(const QString &input, const QString &output, const bool success) {
Q_UNUSED(output);

View File

@@ -87,13 +87,13 @@ class Organize : public QObject {
private Q_SLOTS:
void ProcessSomeFiles();
void FileTranscoded(const QString &input, const QString &output, bool success);
void FileTranscoded(const QString &input, const QString &output, const bool success);
void LogLine(const QString &message);
private:
void SetSongProgress(float progress, bool transcoded = false);
void SetSongProgress(const float progress, const bool transcoded = false);
void UpdateProgress();
Song::FileType CheckTranscode(Song::FileType original_type) const;
Song::FileType CheckTranscode(const Song::FileType original_type) const;
private:
struct Task {

View File

@@ -411,7 +411,6 @@ void OrganizeDialog::SetLoadingSongs(const bool loading) {
SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) const {
SongList songs;
Song song;
QStringList filenames_copy = filenames;
while (!filenames_copy.isEmpty()) {
@@ -427,6 +426,7 @@ SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) const {
continue;
}
Song song;
const TagReaderResult result = tagreader_client_->ReadFileBlocking(filename, &song);
if (result.success() && song.is_valid()) {
songs << song;
@@ -476,6 +476,7 @@ Organize::NewSongInfoList OrganizeDialog::ComputeNewSongsFilenames(const SongLis
}
new_songs_info << Organize::NewSongInfo(song, result.filename, result.unique_filename);
}
return new_songs_info;
}

View File

@@ -48,7 +48,6 @@ OSDBase::OSDBase(const SharedPtr<SystemTrayIcon> tray_icon, QObject *parent)
: QObject(parent),
tray_icon_(tray_icon),
pretty_popup_(new OSDPretty(OSDPretty::Mode::Popup)),
app_name_(QCoreApplication::applicationName()),
timeout_msec_(5000),
type_(OSDSettings::Type::Native),
show_on_volume_change_(false),
@@ -59,11 +58,7 @@ OSDBase::OSDBase(const SharedPtr<SystemTrayIcon> tray_icon, QObject *parent)
use_custom_text_(false),
force_show_next_(false),
ignore_next_stopped_(false),
playing_(false) {
app_name_[0] = app_name_[0].toUpper();
}
playing_(false) {}
OSDBase::~OSDBase() {
delete pretty_popup_;
@@ -264,7 +259,7 @@ void OSDBase::Stopped() {
}
void OSDBase::StopAfterToggle(const bool stop) {
ShowMessage(app_name_, tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off")));
ShowMessage(QCoreApplication::applicationName(), tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off")));
}
void OSDBase::PlaylistFinished() {
@@ -272,7 +267,7 @@ void OSDBase::PlaylistFinished() {
// We get a PlaylistFinished followed by a Stopped from the player
ignore_next_stopped_ = true;
ShowMessage(app_name_, tr("Playlist finished"));
ShowMessage(QCoreApplication::applicationName(), tr("Playlist finished"));
}
@@ -290,7 +285,7 @@ void OSDBase::VolumeChanged(const uint value) {
}
#endif
ShowMessage(app_name_, message);
ShowMessage(QCoreApplication::applicationName(), message);
}
@@ -346,7 +341,7 @@ void OSDBase::ShuffleModeChanged(const PlaylistSequence::ShuffleMode mode) {
case PlaylistSequence::ShuffleMode::InsideAlbum: current_mode = tr("Shuffle tracks in this album"); break;
case PlaylistSequence::ShuffleMode::Albums: current_mode = tr("Shuffle albums"); break;
}
ShowMessage(app_name_, current_mode);
ShowMessage(QCoreApplication::applicationName(), current_mode);
}
}
@@ -363,7 +358,7 @@ void OSDBase::RepeatModeChanged(const PlaylistSequence::RepeatMode mode) {
case PlaylistSequence::RepeatMode::OneByOne: current_mode = tr("Stop after every track"); break;
case PlaylistSequence::RepeatMode::Intro: current_mode = tr("Intro tracks"); break;
}
ShowMessage(app_name_, current_mode);
ShowMessage(QCoreApplication::applicationName(), current_mode);
}
}

View File

@@ -53,8 +53,6 @@ class OSDBase : public QObject {
virtual bool SupportsNativeNotifications();
virtual bool SupportsTrayPopups();
QString app_name() { return app_name_; }
public Q_SLOTS:
void ReloadSettings();
@@ -88,7 +86,6 @@ class OSDBase : public QObject {
const SharedPtr<SystemTrayIcon> tray_icon_;
OSDPretty *pretty_popup_;
QString app_name_;
int timeout_msec_;
OSDSettings::Type type_;
bool show_on_volume_change_;

View File

@@ -170,7 +170,7 @@ void OSDDBus::ShowMessageNative(const QString &summary, const QString &message,
id = notification_id_;
}
QDBusPendingReply<uint> reply = interface_->Notify(app_name(), id, icon, summary_stripped, message, QStringList(), hints, timeout_msec());
QDBusPendingReply<uint> reply = interface_->Notify(QCoreApplication::applicationName(), id, icon, summary_stripped, message, QStringList(), hints, timeout_msec());
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &OSDDBus::CallFinished);

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, 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
@@ -313,8 +313,8 @@ QVariant Playlist::data(const QModelIndex &idx, const int role) const {
case Qt::EditRole:
case Qt::ToolTipRole:
case Qt::DisplayRole:{
PlaylistItemPtr item = items_[idx.row()];
Song song = item->Metadata();
const PlaylistItemPtr item = items_[idx.row()];
const Song song = item->Metadata();
// Don't forget to change Playlist::CompareItems when adding new columns
switch (static_cast<Column>(idx.column())) {
@@ -423,8 +423,8 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int
Q_UNUSED(role);
int row = idx.row();
PlaylistItemPtr item = item_at(row);
const int row = idx.row();
const PlaylistItemPtr item = item_at(row);
Song song = item->OriginalMetadata();
if (idx.data() == value) return false;
@@ -487,7 +487,7 @@ void Playlist::ItemReload(const QPersistentModelIndex &idx, const Song &old_meta
void Playlist::ItemReloadComplete(const QPersistentModelIndex &idx, const Song &old_metadata, const bool metadata_edit) {
if (idx.isValid()) {
PlaylistItemPtr item = item_at(idx.row());
const PlaylistItemPtr item = item_at(idx.row());
if (item) {
ItemChanged(idx.row(), ChangedColumns(old_metadata, item->Metadata()));
if (idx.row() == current_row()) {
@@ -555,12 +555,12 @@ int Playlist::NextVirtualIndex(int i, const bool ignore_repeat_track) const {
}
// We need to advance i until we get something else on the same album
Song last_song = current_item_metadata();
const Song last_song = current_item_metadata();
for (int j = i + 1; j < virtual_items_.count(); ++j) {
if (item_at(virtual_items_[j])->GetShouldSkip()) {
continue;
}
Song this_song = item_at(virtual_items_[j])->Metadata();
const Song this_song = item_at(virtual_items_[j])->Metadata();
if (((last_song.is_compilation() && this_song.is_compilation()) ||
last_song.effective_albumartist() == this_song.effective_albumartist()) &&
last_song.album() == this_song.album() &&
@@ -677,13 +677,13 @@ int Playlist::previous_row(const bool ignore_repeat_track) {
void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const bool is_stopping, const bool force_inform) {
QPersistentModelIndex old_current_item_index = current_item_index_;
const QPersistentModelIndex old_current_item_index = current_item_index_;
QPersistentModelIndex new_current_item_index;
if (i != -1) new_current_item_index = QPersistentModelIndex(index(i, 0, QModelIndex()));
if (new_current_item_index != current_item_index_) ClearStreamMetadata();
int nextrow = next_row();
const int nextrow = next_row();
if (nextrow != -1 && nextrow != i) {
PlaylistItemPtr next_item = item_at(nextrow);
if (next_item) {
@@ -1127,7 +1127,7 @@ void Playlist::InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const in
beginInsertRows(QModelIndex(), start, end);
for (int i = start; i <= end; ++i) {
PlaylistItemPtr item = items[i - start];
const PlaylistItemPtr item = items[i - start];
items_.insert(i, item);
virtual_items_ << static_cast<int>(virtual_items_.count());
@@ -1180,7 +1180,11 @@ void Playlist::InsertSongs(const SongList &songs, const int pos, const bool play
InsertSongItems<SongPlaylistItem>(songs, pos, play_now, enqueue, enqueue_next);
}
void Playlist::InsertSongsOrCollectionItems(const SongList &songs, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next) {
void Playlist::InsertSongsOrCollectionItems(const SongList &songs, const QString &playlist_name, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next) {
if (!playlist_name.isEmpty()) {
Q_EMIT Rename(id_, playlist_name);
}
PlaylistItemPtrList items;
for (const Song &song : songs) {
@@ -1201,6 +1205,7 @@ void Playlist::InsertSongsOrCollectionItems(const SongList &songs, const int pos
}
}
}
InsertItems(items, pos, play_now, enqueue, enqueue_next);
}
@@ -1304,11 +1309,11 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const {
rows << idx.row();
}
QBuffer buf;
if (!buf.open(QIODevice::WriteOnly)) {
QBuffer buffer;
if (!buffer.open(QIODevice::WriteOnly)) {
return nullptr;
}
QDataStream stream(&buf);
QDataStream stream(&buffer);
const Playlist *self = this;
const qint64 pid = QCoreApplication::applicationPid();
@@ -1316,11 +1321,11 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const {
stream.writeRawData(reinterpret_cast<char*>(&self), sizeof(self)); // NOLINT(bugprone-sizeof-expression)
stream << rows;
stream.writeRawData(reinterpret_cast<const char*>(&pid), sizeof(pid));
buf.close();
buffer.close();
QMimeData *mimedata = new QMimeData;
mimedata->setUrls(urls);
mimedata->setData(QLatin1String(kRowsMimetype), buf.data());
mimedata->setData(QLatin1String(kRowsMimetype), buffer.data());
return mimedata;
@@ -1607,21 +1612,21 @@ void Playlist::ItemsLoaded() {
InsertItems(items, 0);
is_loading_ = false;
PlaylistBackend::Playlist p = playlist_backend_->GetPlaylist(id_);
const PlaylistBackend::Playlist playlist = playlist_backend_->GetPlaylist(id_);
// The newly loaded list of items might be shorter than it was before so look out for a bad last_played index
last_played_item_index_ = p.last_played == -1 || p.last_played >= rowCount() ? QModelIndex() : index(p.last_played);
last_played_item_index_ = playlist.last_played == -1 || playlist.last_played >= rowCount() ? QModelIndex() : index(playlist.last_played);
if (p.dynamic_type == PlaylistGenerator::Type::Query) {
PlaylistGeneratorPtr gen = PlaylistGenerator::Create(p.dynamic_type);
if (playlist.dynamic_type == PlaylistGenerator::Type::Query) {
PlaylistGeneratorPtr gen = PlaylistGenerator::Create(playlist.dynamic_type);
if (gen) {
SharedPtr<CollectionBackend> backend = nullptr;
if (p.dynamic_backend == collection_backend_->songs_table()) backend = collection_backend_;
if (playlist.dynamic_backend == collection_backend_->songs_table()) backend = collection_backend_;
if (backend) {
gen->set_collection_backend(collection_backend_);
gen->Load(p.dynamic_data);
gen->Load(playlist.dynamic_data);
TurnOnDynamicPlaylist(gen);
}
@@ -1637,7 +1642,7 @@ void Playlist::ItemsLoaded() {
// Should we gray out deleted songs asynchronously on startup?
if (greyout) {
(void)QtConcurrent::run(&Playlist::InvalidateDeletedSongs, this);
InvalidateDeletedSongs();
}
Q_EMIT PlaylistLoaded();
@@ -1725,11 +1730,11 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co
// Remove items
beginRemoveRows(QModelIndex(), row, row + count - 1);
PlaylistItemPtrList ret;
ret.reserve(count);
PlaylistItemPtrList items;
items.reserve(count);
for (int i = 0; i < count; ++i) {
PlaylistItemPtr item(items_.takeAt(row));
ret << item;
items << item;
if (item->source() == Song::Source::Collection) {
int id = item->Metadata().id();
@@ -1769,13 +1774,13 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co
ScheduleSave();
return ret;
return items;
}
void Playlist::StopAfter(const int row) {
QModelIndex old_stop_after = stop_after_;
const QModelIndex old_stop_after = stop_after_;
if ((stop_after_.isValid() && stop_after_.row() == row) || row == -1) {
stop_after_ = QModelIndex();
@@ -1911,9 +1916,9 @@ void Playlist::RemoveItemsNotInQueue() {
void Playlist::ReloadItems(const QList<int> &rows) {
for (int row : rows) {
PlaylistItemPtr item = item_at(row);
QPersistentModelIndex idx = index(row, 0);
for (const int row : rows) {
const PlaylistItemPtr item = item_at(row);
const QPersistentModelIndex idx = index(row, 0);
if (idx.isValid()) {
ItemReload(idx, item->Metadata(), false);
}
@@ -1921,18 +1926,6 @@ void Playlist::ReloadItems(const QList<int> &rows) {
}
void Playlist::ReloadItemsBlocking(const QList<int> &rows) {
for (int row : rows) {
PlaylistItemPtr item = item_at(row);
Song old_metadata = item->Metadata();
item->Reload();
QPersistentModelIndex idx = index(row, 0);
ItemReloadComplete(idx, old_metadata, false);
}
}
void Playlist::Shuffle() {
PlaylistItemPtrList new_items(items_);
@@ -1951,7 +1944,7 @@ void Playlist::Shuffle() {
const int count = static_cast<int>(items_.count());
for (int i = begin; i < count; ++i) {
int new_pos = i + (rand() % (count - i));
const int new_pos = i + (rand() % (count - i));
std::swap(new_items[i], new_items[new_pos]);
}
@@ -2053,12 +2046,12 @@ PlaylistFilter *Playlist::filter() const { return filter_; }
SongList Playlist::GetAllSongs() const {
SongList ret;
ret.reserve(items_.count());
SongList songs;
songs.reserve(items_.count());
for (PlaylistItemPtr item : items_) { // clazy:exclude=range-loop-reference
ret << item->Metadata();
songs << item->Metadata();
}
return ret;
return songs;
}
@@ -2066,12 +2059,13 @@ PlaylistItemPtrList Playlist::GetAllItems() const { return items_; }
quint64 Playlist::GetTotalLength() const {
quint64 ret = 0;
quint64 total_length = 0;
for (PlaylistItemPtr item : items_) { // clazy:exclude=range-loop-reference
qint64 length = item->Metadata().length_nanosec();
if (length > 0) ret += length;
if (length > 0) total_length += length;
}
return ret;
return total_length;
}
@@ -2112,7 +2106,7 @@ void Playlist::TracksEnqueued(const QModelIndex &parent_idx, const int begin, co
void Playlist::QueueLayoutChanged() {
for (int i = 0; i < queue_->rowCount(); ++i) {
const QModelIndex &idx = queue_->mapToSource(queue_->index(i, static_cast<int>(Column::Title)));
const QModelIndex idx = queue_->mapToSource(queue_->index(i, static_cast<int>(Column::Title)));
Q_EMIT dataChanged(idx, idx);
}
@@ -2309,10 +2303,10 @@ void Playlist::InvalidateDeletedSongs() {
for (int row = 0; row < items_.count(); ++row) {
PlaylistItemPtr item = items_.value(row);
Song song = item->Metadata();
const Song song = item->Metadata();
if (song.url().isLocalFile()) {
bool exists = QFile::exists(song.url().toLocalFile());
if (song.url().isValid() && song.url().isLocalFile()) {
const bool exists = QFile::exists(song.url().toLocalFile());
if (!exists && !item->HasForegroundColor(kInvalidSongPriority)) {
// Gray out the song if it's not there
@@ -2327,12 +2321,7 @@ void Playlist::InvalidateDeletedSongs() {
}
if (!invalidated_rows.isEmpty()) {
if (QThread::currentThread() == thread()) {
ReloadItems(invalidated_rows);
}
else {
ReloadItemsBlocking(invalidated_rows);
}
ReloadItems(invalidated_rows);
}
}
@@ -2342,8 +2331,8 @@ void Playlist::RemoveDeletedSongs() {
QList<int> rows_to_remove;
for (int row = 0; row < items_.count(); ++row) {
PlaylistItemPtr item = items_.value(row);
Song song = item->Metadata();
const PlaylistItemPtr item = items_.value(row);
const Song song = item->Metadata();
if (song.url().isLocalFile() && !QFile::exists(song.url().toLocalFile())) {
rows_to_remove.append(row); // clazy:exclude=reserve-candidates
@@ -2376,7 +2365,7 @@ void Playlist::RemoveDuplicateSongs() {
std::unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> unique_songs;
for (int row = 0; row < items_.count(); ++row) {
PlaylistItemPtr item = items_.value(row);
const PlaylistItemPtr item = items_.value(row);
const Song &song = item->Metadata();
bool found_duplicate = false;
@@ -2409,7 +2398,7 @@ void Playlist::RemoveUnavailableSongs() {
QList<int> rows_to_remove;
for (int row = 0; row < items_.count(); ++row) {
PlaylistItemPtr item = items_.value(row);
const PlaylistItemPtr item = items_.value(row);
const Song &song = item->Metadata();
// Check only local files
@@ -2424,10 +2413,10 @@ void Playlist::RemoveUnavailableSongs() {
bool Playlist::ApplyValidityOnCurrentSong(const QUrl &url, const bool valid) {
PlaylistItemPtr current = current_item();
const PlaylistItemPtr current = current_item();
if (current) {
Song current_song = current->Metadata();
const Song current_song = current->Metadata();
// If validity has changed, reload the item
if (current_song.source() == Song::Source::LocalFile || current_song.source() == Song::Source::Collection) {
@@ -2523,7 +2512,7 @@ void Playlist::TurnOffDynamicPlaylist() {
void Playlist::RateSong(const QModelIndex &idx, const float rating) {
if (has_item_at(idx.row())) {
PlaylistItemPtr item = item_at(idx.row());
const PlaylistItemPtr item = item_at(idx.row());
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
collection_backend_->UpdateSongRatingAsync(item->Metadata().id(), rating);
}
@@ -2537,7 +2526,7 @@ void Playlist::RateSongs(const QModelIndexList &index_list, const float rating)
for (const QModelIndex &idx : index_list) {
const int row = idx.row();
if (has_item_at(row)) {
PlaylistItemPtr item = item_at(row);
const PlaylistItemPtr item = item_at(row);
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
id_list << item->Metadata().id(); // clazy:exclude=reserve-candidates
}

View File

@@ -226,7 +226,7 @@ class Playlist : public QAbstractListModel {
void InsertItems(const PlaylistItemPtrList &itemsIn, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
void InsertCollectionItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
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 InsertSongsOrCollectionItems(const SongList &songs, const QString &playlist_name = QString(), 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 InsertStreamingItems(StreamingServicePtr 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);
@@ -239,14 +239,12 @@ class Playlist : public QAbstractListModel {
// song will be reloaded to even out the situation because obviously something has changed.
// This returns true if this playlist had current item when the method was invoked.
bool ApplyValidityOnCurrentSong(const QUrl &url, bool valid);
// Grays out and reloads all deleted songs in all playlists. Also, "ungreys" those songs which were once deleted but now got restored somehow.
void InvalidateDeletedSongs();
// Removes from the playlist all local files that don't exist anymore.
void RemoveDeletedSongs();
void StopAfter(const int row);
void ReloadItems(const QList<int> &rows);
void ReloadItemsBlocking(const QList<int> &rows);
void InformOfCurrentSongChange(const bool minor);
// Just emits the dataChanged() signal so the mood column is repainted.
@@ -329,6 +327,8 @@ class Playlist : public QAbstractListModel {
// Signals that the queue has changed, meaning that the remaining queued items should update their position.
void QueueChanged();
void Rename(const int id, const QString &name);
private:
void SetCurrentIsPaused(const bool paused);
int NextVirtualIndex(int i, const bool ignore_repeat_track) const;
@@ -354,6 +354,9 @@ class Playlist : public QAbstractListModel {
void TurnOnDynamicPlaylist(PlaylistGeneratorPtr gen);
void InsertDynamicItems(const int count);
// Grays out and reloads all deleted songs in all playlists. Also, "ungreys" those songs which were once deleted but now got restored somehow.
void InvalidateDeletedSongs();
private Q_SLOTS:
void TracksAboutToBeDequeued(const QModelIndex&, const int begin, const int end);
void TracksDequeued();

View File

@@ -298,7 +298,7 @@ PlaylistItemPtr PlaylistBackend::RestoreCueData(PlaylistItemPtr item, SharedPtr<
return item;
}
SongList song_list;
SongList songs;
{
QMutexLocker locker(&state->mutex_);
@@ -306,16 +306,16 @@ PlaylistItemPtr PlaylistBackend::RestoreCueData(PlaylistItemPtr item, SharedPtr<
QFile cue_file(cue_path);
if (!cue_file.open(QIODevice::ReadOnly)) return item;
song_list = cue_parser.Load(&cue_file, cue_path, QDir(cue_path.section(u'/', 0, -2)));
songs = cue_parser.Load(&cue_file, cue_path, QDir(cue_path.section(u'/', 0, -2))).songs;
cue_file.close();
state->cached_cues_[cue_path] = song_list;
state->cached_cues_[cue_path] = songs;
}
else {
song_list = state->cached_cues_[cue_path];
songs = state->cached_cues_[cue_path];
}
}
for (const Song &from_list : std::as_const(song_list)) {
for (const Song &from_list : std::as_const(songs)) {
if (from_list.url().toEncoded() == song.url().toEncoded() && from_list.beginning_nanosec() == song.beginning_nanosec()) {
// We found a matching section; replace the input item with a new one containing CUE metadata
return make_shared<SongPlaylistItem>(from_list);

View File

@@ -166,6 +166,7 @@ Playlist *PlaylistManager::AddPlaylist(const int id, const QString &name, const
QObject::connect(ret, &Playlist::EditingFinished, this, &PlaylistManager::EditingFinished);
QObject::connect(ret, &Playlist::Error, this, &PlaylistManager::Error);
QObject::connect(ret, &Playlist::PlayRequested, this, &PlaylistManager::PlayRequested);
QObject::connect(ret, &Playlist::Rename, this, &PlaylistManager::Rename);
QObject::connect(playlist_container_->view(), &PlaylistView::ColumnAlignmentChanged, ret, &Playlist::SetColumnAlignment);
QObject::connect(&*current_albumcover_loader_, &CurrentAlbumCoverLoader::AlbumCoverLoaded, ret, &Playlist::AlbumCoverLoaded);
@@ -208,7 +209,7 @@ void PlaylistManager::Load(const QString &filename) {
QFileInfo fileinfo(filename);
int id = playlist_backend_->CreatePlaylist(fileinfo.completeBaseName(), QString());
const int id = playlist_backend_->CreatePlaylist(fileinfo.completeBaseName(), QString());
if (id == -1) {
Q_EMIT Error(tr("Couldn't create playlist"));
@@ -221,17 +222,17 @@ void PlaylistManager::Load(const QString &filename) {
}
void PlaylistManager::Save(const int id, const QString &filename, const PlaylistSettings::PathType path_type) {
void PlaylistManager::Save(const int id, const QString &playlist_name, const QString &filename, const PlaylistSettings::PathType path_type) {
if (playlists_.contains(id)) {
parser_->Save(playlist(id)->GetAllSongs(), filename, path_type);
parser_->Save(playlist_name, playlist(id)->GetAllSongs(), filename, path_type);
}
else {
// Playlist is not in the playlist manager: probably save action was triggered from the left sidebar and the playlist isn't loaded.
QFuture<SongList> future = QtConcurrent::run(&PlaylistBackend::GetPlaylistSongs, playlist_backend_, id);
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [this, watcher, filename, path_type]() {
ItemsLoadedForSavePlaylist(watcher->result(), filename, path_type);
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [this, watcher, playlist_name, filename, path_type]() {
ItemsLoadedForSavePlaylist(playlist_name, watcher->result(), filename, path_type);
watcher->deleteLater();
});
watcher->setFuture(future);
@@ -239,9 +240,9 @@ void PlaylistManager::Save(const int id, const QString &filename, const Playlist
}
void PlaylistManager::ItemsLoadedForSavePlaylist(const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type) {
void PlaylistManager::ItemsLoadedForSavePlaylist(const QString &playlist_name, const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type) {
parser_->Save(songs, filename, path_type);
parser_->Save(playlist_name, songs, filename, path_type);
}
@@ -255,7 +256,7 @@ void PlaylistManager::SaveWithUI(const int id, const QString &playlist_name) {
s.endGroup();
QString suggested_filename = playlist_name;
QString filename = last_save_path + QLatin1Char('/') + suggested_filename.remove(QRegularExpression(QLatin1String(kProblematicCharactersRegex), QRegularExpression::CaseInsensitiveOption)) + QLatin1Char('.') + last_save_extension;
QString filename = last_save_path + QLatin1Char('/') + suggested_filename.remove(u'/').remove(QRegularExpression(QLatin1String(kProblematicCharactersRegex), QRegularExpression::CaseInsensitiveOption)) + QLatin1Char('.') + last_save_extension;
QFileInfo fileinfo;
Q_FOREVER {
@@ -283,7 +284,7 @@ void PlaylistManager::SaveWithUI(const int id, const QString &playlist_name) {
s.setValue(PlaylistSettings::kLastSaveExtension, fileinfo.suffix());
s.endGroup();
Save(id == -1 ? current_id() : id, filename, path_type);
Save(id == -1 ? current_id() : id, playlist_name, filename, path_type);
}
@@ -525,15 +526,6 @@ void PlaylistManager::RemoveCurrentSong() const {
active()->removeRows(active()->current_index().row(), 1);
}
void PlaylistManager::InvalidateDeletedSongs() {
const QList<Playlist*> playlists = GetAllPlaylists();
for (Playlist *playlist : playlists) {
playlist->InvalidateDeletedSongs();
}
}
void PlaylistManager::RemoveDeletedSongs() {
const QList<Playlist*> playlists = GetAllPlaylists();
@@ -618,7 +610,7 @@ void PlaylistManager::SaveAllPlaylists() {
for (QMap<int, Data>::const_iterator it = playlists_.constBegin(); it != playlists_.constEnd(); ++it) {
const Data &data = *it;
const QString filepath = path + QLatin1Char('/') + data.name + QLatin1Char('.') + extension;
Save(it.key(), filepath, path_type);
Save(it.key(), data.name, filepath, path_type);
}
}

View File

@@ -74,8 +74,6 @@ class PlaylistManager : public PlaylistManagerInterface {
// Returns the collection of playlists managed by this PlaylistManager.
QList<Playlist*> GetAllPlaylists() const override;
// Grays out and reloads all deleted songs in all playlists.
void InvalidateDeletedSongs() override;
// Removes all deleted songs from all playlists.
void RemoveDeletedSongs() override;
// Returns true if the playlist is open
@@ -99,7 +97,7 @@ class PlaylistManager : public PlaylistManagerInterface {
public Q_SLOTS:
void New(const QString &name, const SongList &songs = SongList(), const QString &special_type = QString()) override;
void Load(const QString &filename) override;
void Save(const int id, const QString &filename, const PlaylistSettings::PathType path_type) override;
void Save(const int id, const QString &playlist_name, const QString &filename, const PlaylistSettings::PathType path_type) override;
// Display a file dialog to let user choose a file before saving the file
void SaveWithUI(const int id, const QString &playlist_name);
void Rename(const int id, const QString &new_name) override;
@@ -150,7 +148,7 @@ class PlaylistManager : public PlaylistManagerInterface {
void OneOfPlaylistsChanged();
void UpdateSummaryText();
void UpdateCollectionSongs(const SongList &songs);
void ItemsLoadedForSavePlaylist(const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type);
void ItemsLoadedForSavePlaylist(const QString &playlist_name, const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type);
void PlaylistLoaded();
private:

View File

@@ -59,8 +59,6 @@ class PlaylistManagerInterface : public QObject {
// Returns the collection of playlists managed by this PlaylistManager.
virtual QList<Playlist*> GetAllPlaylists() const = 0;
// Grays out and reloads all deleted songs in all playlists.
virtual void InvalidateDeletedSongs() = 0;
// Removes all deleted songs from all playlists.
virtual void RemoveDeletedSongs() = 0;
@@ -81,7 +79,7 @@ class PlaylistManagerInterface : public QObject {
public Q_SLOTS:
virtual void New(const QString &name, const SongList &songs = SongList(), const QString &special_type = QString()) = 0;
virtual void Load(const QString &filename) = 0;
virtual void Save(const int id, const QString &filename, const PlaylistSettings::PathType path_type) = 0;
virtual void Save(const int id, const QString &playlist_name, const QString &filename, const PlaylistSettings::PathType path_type) = 0;
virtual void Rename(const int id, const QString &new_name) = 0;
virtual void Delete(const int id) = 0;
virtual bool Close(const int id) = 0;

View File

@@ -54,7 +54,7 @@ SongLoaderInserter::SongLoaderInserter(const SharedPtr<TaskManager> task_manager
SongLoaderInserter::~SongLoaderInserter() { qDeleteAll(pending_); }
void SongLoaderInserter::Load(Playlist *destination, int row, bool play_now, bool enqueue, bool enqueue_next, const QList<QUrl> &urls) {
void SongLoaderInserter::Load(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next, const QList<QUrl> &urls) {
destination_ = destination;
row_ = row;
@@ -69,15 +69,16 @@ void SongLoaderInserter::Load(Playlist *destination, int row, bool play_now, boo
for (const QUrl &url : urls) {
SongLoader *loader = new SongLoader(url_handlers_, collection_backend_, tagreader_client_, this);
SongLoader::Result ret = loader->Load(url);
const SongLoader::Result result = loader->Load(url);
if (ret == SongLoader::Result::BlockingLoadRequired) {
if (result == SongLoader::Result::BlockingLoadRequired) {
pending_.append(loader);
continue;
}
if (ret == SongLoader::Result::Success) {
if (result == SongLoader::Result::Success) {
songs_ << loader->songs();
playlist_name_ = loader->playlist_name();
}
else {
const QStringList errors = loader->errors();
@@ -101,7 +102,7 @@ void SongLoaderInserter::Load(Playlist *destination, int row, bool play_now, boo
// First, we add tracks (without metadata) into the playlist
// In the meantime, MusicBrainz will be queried to get songs' metadata.
// AudioCDTagsLoaded will be called next, and playlist's items will be updated.
void SongLoaderInserter::LoadAudioCD(Playlist *destination, int row, bool play_now, bool enqueue, bool enqueue_next) {
void SongLoaderInserter::LoadAudioCD(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next) {
destination_ = destination;
row_ = row;
@@ -113,8 +114,8 @@ void SongLoaderInserter::LoadAudioCD(Playlist *destination, int row, bool play_n
QObject::connect(loader, &SongLoader::AudioCDTracksLoadFinished, this, [this, loader]() { AudioCDTracksLoadFinished(loader); });
QObject::connect(loader, &SongLoader::LoadAudioCDFinished, this, &SongLoaderInserter::AudioCDTagsLoaded);
qLog(Info) << "Loading audio CD...";
SongLoader::Result ret = loader->LoadAudioCD();
if (ret == SongLoader::Result::Error) {
const SongLoader::Result result = loader->LoadAudioCD();
if (result == SongLoader::Result::Error) {
if (loader->errors().isEmpty())
Q_EMIT Error(tr("Error while loading audio CD."));
else {
@@ -166,7 +167,7 @@ void SongLoaderInserter::InsertSongs() {
// Insert songs (that haven't been completely loaded) to allow user to see and play them while not loaded completely
if (destination_) {
destination_->InsertSongsOrCollectionItems(songs_, row_, play_now_, enqueue_, enqueue_next_);
destination_->InsertSongsOrCollectionItems(songs_, playlist_name_, row_, play_now_, enqueue_, enqueue_next_);
}
}
@@ -180,10 +181,10 @@ void SongLoaderInserter::AsyncLoad() {
bool first_loaded = false;
for (int i = 0; i < pending_.count(); ++i) {
SongLoader *loader = pending_.value(i);
SongLoader::Result res = loader->LoadFilenamesBlocking();
const SongLoader::Result result = loader->LoadFilenamesBlocking();
task_manager_->SetTaskProgress(async_load_id, ++async_progress);
if (res == SongLoader::Result::Error) {
if (result == SongLoader::Result::Error) {
const QStringList errors = loader->errors();
for (const QString &error : errors) {
Q_EMIT Error(error);
@@ -199,6 +200,7 @@ void SongLoaderInserter::AsyncLoad() {
}
songs_ << loader->songs();
playlist_name_ = loader->playlist_name();
}
task_manager_->SetTaskFinished(async_load_id);

View File

@@ -52,8 +52,8 @@ class SongLoaderInserter : public QObject {
~SongLoaderInserter() override;
void Load(Playlist *destination, int row, bool play_now, bool enqueue, bool enqueue_next, const QList<QUrl> &urls);
void LoadAudioCD(Playlist *destination, int row, bool play_now, bool enqueue, bool enqueue_next);
void Load(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next, const QList<QUrl> &urls);
void LoadAudioCD(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next);
Q_SIGNALS:
void Error(const QString &message);
@@ -82,6 +82,7 @@ class SongLoaderInserter : public QObject {
bool enqueue_next_;
SongList songs_;
QString playlist_name_;
QList<SongLoader*> pending_;
};

View File

@@ -42,7 +42,7 @@ bool AsxIniParser::TryMagic(const QByteArray &data) const {
return data.toLower().contains("[reference]");
}
SongList AsxIniParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
ParserBase::LoadResult AsxIniParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
Q_UNUSED(playlist_path);
@@ -66,7 +66,9 @@ SongList AsxIniParser::Load(QIODevice *device, const QString &playlist_path, con
}
void AsxIniParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
void AsxIniParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
Q_UNUSED(playlist_name)
QTextStream s(device);
s << "[Reference]" << Qt::endl;

View File

@@ -51,8 +51,8 @@ class AsxIniParser : public ParserBase {
bool TryMagic(const QByteArray &data) const override;
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
};
#endif // ASXINIPARSER_H

View File

@@ -42,7 +42,7 @@ class CollectionBackendInterface;
ASXParser::ASXParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
: XMLParser(tagreader_client, collection_backend, parent) {}
SongList ASXParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
ParserBase::LoadResult ASXParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
Q_UNUSED(playlist_path);
@@ -133,8 +133,9 @@ return_song:
}
void ASXParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
void ASXParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
Q_UNUSED(playlist_name)
Q_UNUSED(dir)
Q_UNUSED(path_type)

View File

@@ -53,8 +53,8 @@ class ASXParser : public XMLParser {
bool TryMagic(const QByteArray &data) const override;
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
private:
Song ParseTrack(QXmlStreamReader *reader, const QDir &dir, const bool collection_lookup) const;

View File

@@ -66,7 +66,7 @@ constexpr char kDisc[] = "discnumber";
CueParser::CueParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
: ParserBase(tagreader_client, collection_backend, parent) {}
SongList CueParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
ParserBase::LoadResult CueParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
SongList ret;
@@ -378,8 +378,9 @@ qint64 CueParser::IndexToMarker(const QString &index) {
}
void CueParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
void CueParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
Q_UNUSED(playlist_name);
Q_UNUSED(songs);
Q_UNUSED(device);
Q_UNUSED(dir);

View File

@@ -56,8 +56,8 @@ class CueParser : public ParserBase {
bool TryMagic(const QByteArray &data) const override;
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
static QString FindCueFilename(const QString &filename);

View File

@@ -42,7 +42,7 @@ class CollectionBackendInterface;
M3UParser::M3UParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
: ParserBase(tagreader_client, collection_backend, parent) {}
SongList M3UParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
ParserBase::LoadResult M3UParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
Q_UNUSED(playlist_path);
@@ -125,7 +125,9 @@ bool M3UParser::ParseMetadata(const QString &line, M3UParser::Metadata *metadata
}
void M3UParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
void M3UParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
Q_UNUSED(playlist_name)
device->write("#EXTM3U\n");

View File

@@ -53,8 +53,8 @@ class M3UParser : public ParserBase {
bool TryMagic(const QByteArray &data) const override;
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
private:
enum class M3UType {

View File

@@ -46,6 +46,13 @@ class ParserBase : public QObject {
public:
explicit ParserBase(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent = nullptr);
class LoadResult {
public:
LoadResult(const SongList &_songs = SongList(), const QString &_playlist_name = QString()) : songs(_songs), playlist_name(_playlist_name) {}
SongList songs;
QString playlist_name;
};
virtual QString name() const = 0;
virtual QStringList file_extensions() const = 0;
virtual bool load_supported() const = 0;
@@ -59,8 +66,8 @@ class ParserBase : public QObject {
// This method might not return all the songs found in the playlist.
// Any playlist parser may decide to leave out some entries if it finds them incomplete or invalid.
// This means that the final resulting SongList should be considered valid (at least from the parser's point of view).
virtual SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const = 0;
virtual void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const = 0;
virtual LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const = 0;
virtual void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const = 0;
Q_SIGNALS:
void Error(const QString &error) const;

View File

@@ -195,7 +195,7 @@ SongList PlaylistParser::LoadFromFile(const QString &filename) const {
return SongList();
}
const SongList songs = parser->Load(&file, filename, fileinfo.absolutePath(), true);
const SongList songs = parser->Load(&file, filename, fileinfo.absolutePath(), true).songs;
file.close();
return songs;
@@ -210,11 +210,11 @@ SongList PlaylistParser::LoadFromDevice(QIODevice *device, const QString &path_h
return SongList();
}
return parser->Load(device, path_hint, dir_hint);
return parser->Load(device, path_hint, dir_hint).songs;
}
void PlaylistParser::Save(const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type) const {
void PlaylistParser::Save(const QString &playlist_name, const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type) const {
QFileInfo fileinfo(filename);
QDir dir(fileinfo.path());
@@ -248,7 +248,7 @@ void PlaylistParser::Save(const SongList &songs, const QString &filename, const
return;
}
parser->Save(songs, &file, dir, path_type);
parser->Save(playlist_name, songs, &file, dir, path_type);
file.close();

View File

@@ -66,7 +66,7 @@ class PlaylistParser : public QObject {
SongList LoadFromFile(const QString &filename) const;
SongList LoadFromDevice(QIODevice *device, const QString &path_hint = QString(), const QDir &dir_hint = QDir()) const;
void Save(const SongList &songs, const QString &filename, const PlaylistSettings::PathType) const;
void Save(const QString &playlist_name, const SongList &songs, const QString &filename, const PlaylistSettings::PathType) const;
Q_SIGNALS:
void Error(const QString &error) const;

View File

@@ -41,7 +41,7 @@ class CollectionBackendInterface;
PLSParser::PLSParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
: ParserBase(tagreader_client, collection_backend, parent) {}
SongList PLSParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
ParserBase::LoadResult PLSParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
Q_UNUSED(playlist_path);
@@ -83,7 +83,9 @@ SongList PLSParser::Load(QIODevice *device, const QString &playlist_path, const
}
void PLSParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
void PLSParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
Q_UNUSED(playlist_name)
QTextStream s(device);
s << "[playlist]" << Qt::endl;

View File

@@ -52,8 +52,8 @@ class PLSParser : public ParserBase {
bool TryMagic(const QByteArray &data) const override;
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
};
#endif // PLSPARSER_H

View File

@@ -46,21 +46,21 @@ bool WplParser::TryMagic(const QByteArray &data) const {
return data.contains("<?wpl") || data.contains("<smil>");
}
SongList WplParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
ParserBase::LoadResult WplParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
Q_UNUSED(playlist_path);
SongList ret;
QXmlStreamReader reader(device);
if (!Utilities::ParseUntilElement(&reader, u"smil"_s) || !Utilities::ParseUntilElement(&reader, u"body"_s)) {
return ret;
return LoadResult();
}
SongList songs;
while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, u"seq"_s)) {
ParseSeq(dir, &reader, &ret, collection_lookup);
ParseSeq(dir, &reader, &songs, collection_lookup);
}
return ret;
return songs;
}
@@ -98,7 +98,9 @@ void WplParser::ParseSeq(const QDir &dir, QXmlStreamReader *reader, SongList *so
}
void WplParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
void WplParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
Q_UNUSED(playlist_name)
QXmlStreamWriter writer(device);
writer.setAutoFormatting(true);

View File

@@ -55,8 +55,8 @@ class WplParser : public XMLParser {
bool TryMagic(const QByteArray &data) const override;
SongList Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup = true) const override;
void Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
LoadResult Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup = true) const override;
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
private:
void ParseSeq(const QDir &dir, QXmlStreamReader *reader, SongList *songs, const bool collection_lookup) const;

View File

@@ -44,17 +44,27 @@ class CollectionBackendInterface;
XSPFParser::XSPFParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
: XMLParser(tagreader_client, collection_backend, parent) {}
SongList XSPFParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
ParserBase::LoadResult XSPFParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
Q_UNUSED(playlist_path);
SongList songs;
QXmlStreamReader reader(device);
if (!Utilities::ParseUntilElement(&reader, u"playlist"_s) || !Utilities::ParseUntilElement(&reader, u"trackList"_s)) {
return songs;
QString playlist_name;
{
QXmlStreamReader reader(device);
if (Utilities::ParseUntilElement(&reader, u"playlist"_s) && Utilities::ParseUntilElement(&reader, u"title"_s)) {
playlist_name = reader.readElementText();
}
}
device->seek(0);
QXmlStreamReader reader(device);
if (!Utilities::ParseUntilElement(&reader, u"playlist"_s)) {
return LoadResult();
}
if (!Utilities::ParseUntilElement(&reader, u"trackList"_s)) {
return LoadResult();
}
SongList songs;
while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, u"track"_s)) {
const Song song = ParseTrack(&reader, dir, collection_lookup);
if (song.is_valid()) {
@@ -62,7 +72,7 @@ SongList XSPFParser::Load(QIODevice *device, const QString &playlist_path, const
}
}
return songs;
return LoadResult(songs, playlist_name);
}
@@ -140,7 +150,7 @@ return_song:
}
void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
void XSPFParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
QXmlStreamWriter writer(device);
writer.setAutoFormatting(true);
@@ -150,6 +160,8 @@ void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir,
writer.writeAttribute("version"_L1, "1"_L1);
writer.writeDefaultNamespace("http://xspf.org/ns/0/"_L1);
writer.writeTextElement("title"_L1, playlist_name);
Settings s;
s.beginGroup(PlaylistSettings::kSettingsGroup);
bool write_metadata = s.value(PlaylistSettings::kWriteMetadata, true).toBool();

View File

@@ -52,8 +52,8 @@ class XSPFParser : public XMLParser {
bool TryMagic(const QByteArray &data) const override;
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
private:
Song ParseTrack(QXmlStreamReader *reader, const QDir &dir, const bool collection_lookup) const;

View File

@@ -77,11 +77,7 @@ void AudioScrobbler::RemoveService(ScrobblerServicePtr service) {
QList<ScrobblerServicePtr> AudioScrobbler::GetAll() {
QList<ScrobblerServicePtr> services;
services = services_.values();
return services;
return services_.values();
}

View File

@@ -25,7 +25,6 @@
#include <memory>
#include <QObject>
#include <QStandardPaths>
#include <QString>
#include <QFile>
#include <QIODevice>
@@ -38,6 +37,7 @@
#include "core/song.h"
#include "core/logging.h"
#include "core/standardpaths.h"
#include "scrobblercache.h"
#include "scrobblercacheitem.h"
@@ -49,7 +49,7 @@ using std::make_shared;
ScrobblerCache::ScrobblerCache(const QString &filename, QObject *parent)
: QObject(parent),
timer_flush_(new QTimer(this)),
filename_(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') + filename),
filename_(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + QLatin1Char('/') + filename),
loaded_(false) {
ReadCache();

View File

@@ -70,6 +70,7 @@ void ScrobblerSettingsService::ReloadSettings() {
<< Song::Source::Tidal
<< Song::Source::Subsonic
<< Song::Source::Qobuz
<< Song::Source::Spotify
<< Song::Source::SomaFM
<< Song::Source::RadioParadise;
}

View File

@@ -248,6 +248,7 @@ void ScrobblingAPI20::Authenticate() {
default:
break;
}
}
void ScrobblingAPI20::RedirectArrived() {
@@ -474,6 +475,7 @@ void ScrobblingAPI20::Scrobble(const Song &song) {
}
StartSubmit(true);
}
void ScrobblingAPI20::StartSubmit(const bool initial) {
@@ -693,7 +695,7 @@ void ScrobblingAPI20::ScrobbleRequestFinished(QNetworkReply *reply, ScrobblerCac
qLog(Debug) << name_ << "Scrobble for" << song << "accepted";
}
}
}
StartSubmit();
@@ -922,6 +924,7 @@ void ScrobblingAPI20::Error(const QString &error, const QVariant &debug) {
if (settings_->show_error_dialog()) {
Q_EMIT ErrorMessage(tr("Scrobbler %1 error: %2").arg(name_, error));
}
}
QString ScrobblingAPI20::ErrorString(const ScrobbleErrorCode error) {

View File

@@ -37,7 +37,6 @@
#include <QSpinBox>
#include <QComboBox>
#include <QGroupBox>
#include <QStandardPaths>
#include "constants/behavioursettings.h"
#include "core/iconloader.h"

View File

@@ -24,11 +24,12 @@
#include <utility>
#include <limits>
#include <QStandardPaths>
#include <QAbstractItemModel>
#include <QItemSelectionModel>
#include <QString>
#include <QStringList>
#include <QStorageInfo>
#include <QFileInfo>
#include <QDir>
#include <QFileDialog>
#include <QCheckBox>
@@ -43,7 +44,9 @@
#include <QSettings>
#include <QMessageBox>
#include "constants/filesystemconstants.h"
#include "core/iconloader.h"
#include "core/standardpaths.h"
#include "core/settings.h"
#include "utilities/strutils.h"
#include "collection/collectionlibrary.h"
@@ -243,10 +246,16 @@ void CollectionSettingsPage::AddDirectory() {
Settings s;
s.beginGroup(kSettingsGroup);
QString path = s.value(kLastPath, QStandardPaths::writableLocation(QStandardPaths::MusicLocation)).toString();
QString path = s.value(kLastPath, StandardPaths::WritableLocation(StandardPaths::StandardLocation::MusicLocation)).toString();
path = QDir::cleanPath(QFileDialog::getExistingDirectory(this, tr("Add directory..."), path));
if (!path.isEmpty()) {
const QByteArray filesystemtype = QStorageInfo(QFileInfo(path).canonicalFilePath()).fileSystemType();
if (kRejectedFileSystems.contains(filesystemtype)) {
QMessageBox messagebox(QMessageBox::Critical, QObject::tr("Invalid collection directory"), QObject::tr("Can't add directory %1 with special filesystem %2 to collection").arg(path).arg(QString::fromUtf8(filesystemtype)));
(void)messagebox.exec();
return;
}
collectionsettings_directory_model_->AddDirectory(path);
}

View File

@@ -109,6 +109,7 @@ void ScrobblerSettingsPage::Load() {
ui_->checkbox_source_subsonic->setChecked(scrobbler_->sources().contains(Song::Source::Subsonic));
ui_->checkbox_source_tidal->setChecked(scrobbler_->sources().contains(Song::Source::Tidal));
ui_->checkbox_source_qobuz->setChecked(scrobbler_->sources().contains(Song::Source::Qobuz));
ui_->checkbox_source_spotify->setChecked(scrobbler_->sources().contains(Song::Source::Spotify));
ui_->checkbox_source_stream->setChecked(scrobbler_->sources().contains(Song::Source::Stream));
ui_->checkbox_source_somafm->setChecked(scrobbler_->sources().contains(Song::Source::SomaFM));
ui_->checkbox_source_radioparadise->setChecked(scrobbler_->sources().contains(Song::Source::RadioParadise));
@@ -152,6 +153,7 @@ void ScrobblerSettingsPage::Save() {
if (ui_->checkbox_source_subsonic->isChecked()) sources << Song::TextForSource(Song::Source::Subsonic);
if (ui_->checkbox_source_tidal->isChecked()) sources << Song::TextForSource(Song::Source::Tidal);
if (ui_->checkbox_source_qobuz->isChecked()) sources << Song::TextForSource(Song::Source::Qobuz);
if (ui_->checkbox_source_spotify->isChecked()) sources << Song::TextForSource(Song::Source::Spotify);
if (ui_->checkbox_source_stream->isChecked()) sources << Song::TextForSource(Song::Source::Stream);
if (ui_->checkbox_source_somafm->isChecked()) sources << Song::TextForSource(Song::Source::SomaFM);
if (ui_->checkbox_source_radioparadise->isChecked()) sources << Song::TextForSource(Song::Source::RadioParadise);

View File

@@ -39,8 +39,11 @@
</item>
<item>
<widget class="QCheckBox" name="checkbox_offline">
<property name="toolTip">
<string>With this option enabled, scrobbles will be cached to disk but not sent to the server. This option can be enabled in cases where the server or the internet connection is unstable, the scrobbles will be sent when the option is disabled.</string>
</property>
<property name="text">
<string>Work in offline mode (Only cache scrobbles)</string>
<string>Offline mode (Only cache scrobbles)</string>
</property>
</widget>
</item>
@@ -62,6 +65,9 @@
<layout class="QHBoxLayout" name="layout_submit">
<item>
<widget class="QLabel" name="label_submit">
<property name="toolTip">
<string>This is the delay between when a song is scrobbled and when scrobbles are submitted to the server. Setting the time to 0 seconds will submit scrobbles immediately.</string>
</property>
<property name="text">
<string>Submit scrobbles every</string>
</property>
@@ -95,16 +101,6 @@
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_submit_info">
<property name="text">
<string>(This is the delay between when a song is scrobbled and when scrobbles are submitted to the server. Setting the time to 0 seconds will submit scrobbles immediately).</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_albumartist">
<property name="text">
@@ -180,6 +176,27 @@
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="checkbox_source_somafm">
<property name="text">
<string>SomaFM</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="checkbox_source_radioparadise">
<property name="text">
<string>Radio Paradise</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="checkbox_source_spotify">
<property name="text">
<string>Spotify</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="checkbox_source_cdda">
<property name="text">
@@ -187,13 +204,6 @@
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="checkbox_source_somafm">
<property name="text">
<string>SomaFM</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="checkbox_source_stream">
<property name="text">
@@ -201,13 +211,6 @@
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="checkbox_source_radioparadise">
<property name="text">
<string>Radio Paradise</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="checkbox_source_unknown">
<property name="text">
@@ -426,6 +429,7 @@
<tabstop>spinbox_submit</tabstop>
<tabstop>checkbox_albumartist</tabstop>
<tabstop>checkbox_show_error_dialog</tabstop>
<tabstop>checkbox_strip_remastered</tabstop>
<tabstop>checkbox_source_collection</tabstop>
<tabstop>checkbox_source_local</tabstop>
<tabstop>checkbox_source_device</tabstop>
@@ -435,6 +439,7 @@
<tabstop>checkbox_source_subsonic</tabstop>
<tabstop>checkbox_source_tidal</tabstop>
<tabstop>checkbox_source_qobuz</tabstop>
<tabstop>checkbox_source_spotify</tabstop>
<tabstop>checkbox_source_somafm</tabstop>
<tabstop>checkbox_source_radioparadise</tabstop>
<tabstop>checkbox_lastfm_enable</tabstop>

View File

@@ -51,6 +51,7 @@ SubsonicSettingsPage::SubsonicSettingsPage(SettingsDialog *dialog, const SharedP
QObject::connect(ui_->button_test, &QPushButton::clicked, this, &SubsonicSettingsPage::TestClicked);
QObject::connect(ui_->button_deletesongs, &QPushButton::clicked, &*service_, &SubsonicService::DeleteSongs);
QObject::connect(ui_->checkbox_download_album_covers, &QCheckBox::toggled, this, &SubsonicSettingsPage::CheckboxDownloadAlbumCoversToggled);
QObject::connect(this, &SubsonicSettingsPage::Test, &*service_, &SubsonicService::SendPingWithCredentials);
@@ -78,6 +79,7 @@ void SubsonicSettingsPage::Load() {
ui_->checkbox_http2->setChecked(s.value(kHTTP2, false).toBool());
ui_->checkbox_verify_certificate->setChecked(s.value(kVerifyCertificate, false).toBool());
ui_->checkbox_download_album_covers->setChecked(s.value(kDownloadAlbumCovers, true).toBool());
ui_->checkbox_use_album_id_for_album_covers->setChecked(s.value(kUseAlbumIdForAlbumCovers, false).toBool());
ui_->checkbox_server_scrobbling->setChecked(s.value(kServerSideScrobbling, false).toBool());
const AuthMethod auth_method = static_cast<AuthMethod>(s.value(kAuthMethod, static_cast<int>(AuthMethod::MD5)).toInt());
@@ -90,6 +92,8 @@ void SubsonicSettingsPage::Load() {
break;
}
ui_->checkbox_use_album_id_for_album_covers->setEnabled(ui_->checkbox_download_album_covers->isChecked());
s.endGroup();
Init(ui_->layout_subsonicsettingspage->parentWidget());
@@ -109,6 +113,7 @@ void SubsonicSettingsPage::Save() {
s.setValue(kHTTP2, ui_->checkbox_http2->isChecked());
s.setValue(kVerifyCertificate, ui_->checkbox_verify_certificate->isChecked());
s.setValue(kDownloadAlbumCovers, ui_->checkbox_download_album_covers->isChecked());
s.setValue(kUseAlbumIdForAlbumCovers, ui_->checkbox_use_album_id_for_album_covers->isChecked());
s.setValue(kServerSideScrobbling, ui_->checkbox_server_scrobbling->isChecked());
if (ui_->auth_method_hex->isChecked()) {
s.setValue(kAuthMethod, static_cast<int>(AuthMethod::Hex));
@@ -116,10 +121,19 @@ void SubsonicSettingsPage::Save() {
else {
s.setValue(kAuthMethod, static_cast<int>(AuthMethod::MD5));
}
ui_->checkbox_use_album_id_for_album_covers->setEnabled(ui_->checkbox_download_album_covers->isChecked());
s.endGroup();
}
void SubsonicSettingsPage::CheckboxDownloadAlbumCoversToggled(bool enabled) {
ui_->checkbox_use_album_id_for_album_covers->setEnabled(enabled);
}
void SubsonicSettingsPage::TestClicked() {
if (ui_->server_url->text().isEmpty() || ui_->username->text().isEmpty() || ui_->password->text().isEmpty()) {

View File

@@ -51,6 +51,7 @@ class SubsonicSettingsPage : public SettingsPage {
void Test(const QUrl &url, const QString &username, const QString &password, const SubsonicSettings::AuthMethod auth_method, const bool redirect = false);
private Q_SLOTS:
void CheckboxDownloadAlbumCoversToggled(bool enabled);
void TestClicked();
void TestSuccess();
void TestFailure(const QString &failure_reason);

View File

@@ -171,6 +171,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_use_album_id_for_album_covers">
<property name="text">
<string>Use album ID for album covers</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_server_scrobbling">
<property name="text">

View File

@@ -64,7 +64,8 @@ SmartPlaylistWizard::SmartPlaylistWizard(const SharedPtr<Player> player,
setWizardStyle(QWizard::ClassicStyle);
#endif
#ifdef Q_OS_WIN32
if (QApplication::style() && QApplication::style()->objectName() == u"fusion"_s) {
// Workaround QTBUG-123853
if (QApplication::style() && QApplication::style()->objectName() != u"windowsvista"_s) {
setWizardStyle(QWizard::ClassicStyle);
}
#endif

View File

@@ -69,6 +69,7 @@ class SubsonicBaseRequest : public QObject {
bool http2() const { return service_->http2(); }
bool verify_certificate() const { return service_->verify_certificate(); }
bool download_album_covers() const { return service_->download_album_covers(); }
bool use_album_id_for_album_covers() const { return service_->use_album_id_for_album_covers(); }
private Q_SLOTS:
void HandleSSLErrors(const QList<QSslError> &ssl_errors);

View File

@@ -606,12 +606,20 @@ QString SubsonicRequest::ParseSong(Song &song, const QJsonObject &json_obj, cons
if (json_obj.contains("genre"_L1)) genre = json_obj["genre"_L1].toString();
QString cover_id;
if (json_obj.contains("coverArt"_L1)) {
if (json_obj["coverArt"_L1].type() == QJsonValue::String) {
cover_id = json_obj["coverArt"_L1].toString();
if (use_album_id_for_album_covers()) {
cover_id = album_id;
}
else {
if (json_obj.contains("coverArt"_L1)) {
if (json_obj["coverArt"_L1].type() == QJsonValue::String) {
cover_id = json_obj["coverArt"_L1].toString();
}
else {
cover_id = QString::number(json_obj["coverArt"_L1].toInt());
}
}
else {
cover_id = QString::number(json_obj["coverArt"_L1].toInt());
cover_id = song_id;
}
}

View File

@@ -122,6 +122,7 @@ class SubsonicRequest : public SubsonicBaseRequest {
QHash<QString, Request> album_songs_requests_pending_;
QMultiMap<QString, QString> album_covers_requests_sent_;
QMultiMap<QString, QUrl> album_covers_retrieved_;
int albums_requests_active_;

View File

@@ -82,6 +82,7 @@ SubsonicService::SubsonicService(const SharedPtr<TaskManager> task_manager,
http2_(false),
verify_certificate_(false),
download_album_covers_(true),
use_album_id_for_album_covers_(false),
auth_method_(SubsonicSettings::AuthMethod::MD5),
ping_redirects_(0) {
@@ -128,6 +129,7 @@ void SubsonicService::ReloadSettings() {
http2_ = s.value(SubsonicSettings::kHTTP2, false).toBool();
verify_certificate_ = s.value(SubsonicSettings::kVerifyCertificate, false).toBool();
download_album_covers_ = s.value(SubsonicSettings::kDownloadAlbumCovers, true).toBool();
use_album_id_for_album_covers_ = s.value(SubsonicSettings::kUseAlbumIdForAlbumCovers, false).toBool();
auth_method_ = static_cast<SubsonicSettings::AuthMethod>(s.value(SubsonicSettings::kAuthMethod, static_cast<int>(SubsonicSettings::AuthMethod::MD5)).toInt());
s.endGroup();

View File

@@ -80,6 +80,7 @@ class SubsonicService : public StreamingService {
bool http2() const { return http2_; }
bool verify_certificate() const { return verify_certificate_; }
bool download_album_covers() const { return download_album_covers_; }
bool use_album_id_for_album_covers() const { return use_album_id_for_album_covers_; }
SubsonicSettings::AuthMethod auth_method() const { return auth_method_; }
SharedPtr<CollectionBackend> collection_backend() const { return collection_backend_; }
@@ -123,6 +124,7 @@ class SubsonicService : public StreamingService {
bool http2_;
bool verify_certificate_;
bool download_album_covers_;
bool use_album_id_for_album_covers_;
SubsonicSettings::AuthMethod auth_method_;
QStringList errors_;

View File

@@ -39,7 +39,6 @@ using namespace Qt::Literals::StringLiterals;
SystemTrayIcon::SystemTrayIcon(QObject *parent)
: QSystemTrayIcon(parent),
menu_(new QMenu),
app_name_(QCoreApplication::applicationName()),
pixmap_playing_(u":/pictures/tiny-play.png"_s),
pixmap_paused_(u":/pictures/tiny-pause.png"_s),
action_play_pause_(nullptr),
@@ -51,8 +50,6 @@ SystemTrayIcon::SystemTrayIcon(QObject *parent)
trayicon_progress_(false),
song_progress_(0) {
app_name_[0] = app_name_[0].toUpper();
const QIcon icon = IconLoader::Load(u"strawberry"_s);
const QIcon icon_grey = IconLoader::Load(u"strawberry-grey"_s);
pixmap_normal_ = icon.pixmap(48, QIcon::Normal);
@@ -66,7 +63,7 @@ SystemTrayIcon::SystemTrayIcon(QObject *parent)
if (isSystemTrayAvailable()) {
available_ = true;
setIcon(icon);
setToolTip(app_name_);
setToolTip(QCoreApplication::applicationName());
}
QObject::connect(this, &QSystemTrayIcon::activated, this, &SystemTrayIcon::Clicked);
@@ -198,7 +195,7 @@ void SystemTrayIcon::SetNowPlaying(const Song &song, const QUrl &url) {
}
void SystemTrayIcon::ClearNowPlaying() {
if (available_) setToolTip(app_name_);
if (available_) setToolTip(QCoreApplication::applicationName());
}
void SystemTrayIcon::LoveVisibilityChanged(const bool value) {

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