Compare commits

...

80 Commits

Author SHA1 Message Date
Jonas Kvinge
5c57c923d9 Add Dropbox 2025-12-29 00:48:02 +01:00
Strawberry Bot
da2f28811a New translations 2025-12-29 00:02:45 +01:00
Jonas Kvinge
0bfa736081 GstEnginePipeline: Add audioresample elements 2025-12-28 22:01:42 +01:00
Jonas Kvinge
1392bcbbe1 FilesystemMusicStorage: Fallback to delete if moving to trash fails
Fixes #1679
2025-12-28 21:28:49 +01:00
Jonas Kvinge
11705889f1 Show playlist load errors
Fixes #1470
2025-12-28 20:54:36 +01:00
dependabot[bot]
604dd2dbde Bump vmactions/freebsd-vm from 1.3.1 to 1.3.2
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.3.1...v1.3.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-28 19:00:35 +01:00
Stickman
25065ba98f Song: Include Opus for supported sort tags 2025-12-28 18:57:52 +01:00
Jonas Kvinge
7b16ec62bb Defer playcount and rating tag writes for currently playing Ogg songs
Fixes #1816
2025-12-28 18:33:49 +01:00
Jonas Kvinge
d8f31592b9 Remove settings member variables 2025-12-28 00:39:22 +01:00
Jonas Kvinge
80bb0f476d CollectionModel: Remove sort tags from container keys
Fixes #1899
2025-12-27 21:25:54 +01:00
dependabot[bot]
b7222ac85c Bump vmactions/openbsd-vm from 1.2.8 to 1.2.9
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.2.8 to 1.2.9.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.2.8...v1.2.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-27 20:02:32 +01:00
dependabot[bot]
241bca0828 Bump vmactions/openbsd-vm from 1.2.7 to 1.2.8
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.2.7 to 1.2.8.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.2.7...v1.2.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 19:51:35 +01:00
dependabot[bot]
90d86b10a3 Bump vmactions/freebsd-vm from 1.3.0 to 1.3.1
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.3.0...v1.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 16:51:53 +01:00
dependabot[bot]
4130c6670f Bump vmactions/openbsd-vm from 1.2.5 to 1.2.7
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.2.5 to 1.2.7.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.2.5...v1.2.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 21:10:30 +01:00
Jonas Kvinge
8d262959c1 GstEnginePipeline: Fix buffering issue near track end during gapless playback
Ignore buffering messages when within 5 seconds of track end and about-to-finish has been signaled. This prevents spurious buffering from blocking playback during track transitions with local files.

Fixes #1725
2025-12-20 01:36:49 +01:00
Jonas Kvinge
b9b70399d8 GstEnginePipeline: Fix possible race condition in pipeline destructor
Wait for ongoing state changes to complete before setting pipeline to NULL.
This prevents race conditions with async state transitions that can cause crashes in GStreamer elements.

Fixes #1875
2025-12-20 01:28:53 +01:00
Jonas Kvinge
527ccd212a SmartPlaylistsViewContainer: Ask for confirmation before resetting smart playlists 2025-12-19 01:03:46 +01:00
Jonas Kvinge
4a5afbeb1e SmartPlaylists: Add option to restore smart playlists to the defaults
Fixes #1848
2025-12-19 00:49:05 +01:00
Jonas Kvinge
63c14e014b EditTagDialog: Ignore unused const variables 2025-12-19 00:47:35 +01:00
Jonas Kvinge
801658c6b9 MainWindow: Check that current is the active playlist
Fixes #1783
2025-12-19 00:38:32 +01:00
Jonas Kvinge
16fe665295 TagReaderTagLib: Remove unused constants 2025-12-19 00:35:02 +01:00
Rob Stanfield
2bb0dbada2 Qobuz: Fix authentication and add automatic credential fetching
Qobuz API now requires intent=stream parameter for stream URL requests,
and the app_secret must be extracted using the Spoofbuz decoding method
from bundle.js rather than plain-text values.

Changes:
- Add intent=stream parameter to stream URL requests
- Add QobuzCredentialFetcher class to extract credentials from web player
- Add "Fetch Credentials" button to Qobuz settings page
- Decode obfuscated app secrets using seed/timezone/info/extras method

This fixes "Invalid Request Signature" errors that prevented playback.
2025-12-18 23:12:52 +01:00
Jonas Kvinge
2cd9498469 Add option to select ID3v2 version
Fixes #1861
2025-12-18 22:18:26 +01:00
Jonas Kvinge
d1ee27fff9 QobuzService: Remove QNetworkReply 2025-12-18 20:39:21 +01:00
Jonas Kvinge
91adf5ba32 NetworkAccessManager: Handle network state changes after system suspend/resume
Fixes #1521
2025-12-18 20:32:07 +01:00
Jonas Kvinge
d68f464269 Playlist: Don't automatically sort playlist before it's fully loaded
Fixes #1690
2025-12-18 20:14:36 +01:00
Jonas Kvinge
c684a95f89 GstEnginePipeline: Fix file descriptor exhaustion by using shared thread pool
Replace per-pipeline QThreadPool with a shared static pool to prevent
file descriptor and thread exhaustion. Each GstEnginePipeline was creating
its own thread pool, leading to resource accumulation during frequent
pipeline creation/destruction (track changes, seeking, crossfade).

The shared pool is limited to 2 threads max since state changes are
typically sequential per pipeline. This prevents the crash in g_wakeup_new()
when creating eventfd for new thread event dispatchers.

Fixes #1687
2025-12-18 19:58:23 +01:00
copilot-swe-agent[bot]
1d03bb2178 GstEnginePipeline: Fix crash in GStreamer decodebin3 when switching tracks
Add guard in AboutToFinishCallback to prevent race condition when pipeline is being torn down. This prevents the callback from trying to set next URL while the pipeline is being destroyed, which caused crashes in GStreamer's decodebin3.

Fixes issue where rapidly switching tracks could cause segmentation fault in gst_decodebin_input_link_to_slot.

See: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/4626

Fixes #1863

Co-Authored-By: Jonas Kvinge <jonas@jkvinge.net>
2025-12-18 19:44:03 +01:00
Jonas Kvinge
39f9128ecf gitignore: Add _codeql_detected_source_root 2025-12-18 19:39:10 +01:00
Jonas Kvinge
ca2e802239 GstEngine: Make sure device is set for pipeline
Fixes #1852
2025-12-18 00:21:00 +01:00
Jonas Kvinge
9a513a9a56 AutoExpandingTreeView: Scroll if cursor is out of visible area
Fixes #1489
2025-12-17 23:14:57 +01:00
Jonas Kvinge
1c2e87b741 Organize: Skip existing files if not overwriting
Fixes #1484
2025-12-17 22:58:17 +01:00
Jonas Kvinge
fe4d9979ce CollectionWatcher: Avoid re-scan of restored songs unless mtime is changed
Fixes #1819
2025-12-17 22:15:21 +01:00
Jonas Kvinge
d8ae790ebf Turn on git revision 2025-12-17 01:05:45 +01:00
Jonas Kvinge
ac31d79294 Release 1.2.16 2025-12-17 00:08:06 +01:00
Jonas Kvinge
4ffebd77b1 Update Changelog 2025-12-17 00:07:41 +01:00
Strawberry Bot
6682efae2f New translations 2025-12-16 22:41:10 +01:00
Jonas Kvinge
480161c6b7 Update Changelog 2025-12-16 22:38:55 +01:00
Jonas Kvinge
a8ba420d72 ErrorDialog: Use QApplication::activeWindow 2025-12-16 22:32:11 +01:00
dependabot[bot]
fc0ec91652 Bump actions/download-artifact from 6 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 18:15:02 +01:00
dependabot[bot]
0701b97324 Bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 18:14:44 +01:00
Jonas Kvinge
3867932e1e PlaylistView: Don't automatically scroll on dynamic playlists
Fixes #1427
2025-12-14 04:55:20 +01:00
Jonas Kvinge
e2907f6051 PlaylistView: Use Qt::CopyAction for drag
Fixes #1815
2025-12-14 04:41:58 +01:00
Jonas Kvinge
0827ec7f16 PlaylistSequence: Fix icon size
Fixes #1838
2025-12-14 04:28:25 +01:00
Jonas Kvinge
24d2adf363 PlaylistView: Set current index when automatically selecting track
Fixes #1825
2025-12-14 04:02:40 +01:00
Jonas Kvinge
592729d00b SliderSlider: Use SC_SliderGroove
Fixes #1675
2025-12-14 01:57:47 +01:00
Jonas Kvinge
c4a564bb56 NetworkAccessManager: Use full application name for user agent 2025-12-14 01:36:53 +01:00
Jonas Kvinge
812a02a3a1 Update Changelog 2025-12-14 01:04:45 +01:00
Strawberry Bot
944936914b New translations 2025-12-14 01:01:06 +01:00
Jonas Kvinge
e7c901d4f3 Update Changelog 2025-12-14 00:59:42 +01:00
Jonas Kvinge
8e996119af Make using sort tags optional 2025-12-14 00:52:19 +01:00
Jonas Kvinge
4348a654ca TagReaderResult: Fix file save error message 2025-12-14 00:52:19 +01:00
dependabot[bot]
f0be1c782a Bump vmactions/openbsd-vm from 1.2.4 to 1.2.5
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.2.4 to 1.2.5.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.2.4...v1.2.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-11 00:36:57 +01:00
dependabot[bot]
e9898d08bc Bump vmactions/freebsd-vm from 1.2.9 to 1.3.0
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.2.9 to 1.3.0.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.2.9...v1.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-11 00:36:24 +01:00
Jonas Kvinge
1ad13cd3b0 Add lyrics from lrclib.net 2025-12-09 18:45:57 +01:00
Jonas Kvinge
5c640e0e36 LyricsFetcherSearch: Improve handling timeout 2025-12-09 18:41:55 +01:00
Jonas Kvinge
059def8d0c Add duration to lyrics search request 2025-12-09 18:40:45 +01:00
Jonas Kvinge
cf15a1f423 CDDALister: Add Q_UNUSED 2025-12-09 01:33:13 +01:00
Jonas Kvinge
5d35b0eedd BlockAnalyzer: Formatting 2025-12-09 01:19:02 +01:00
Jonas Kvinge
5fcb71d08f Formatting 2025-12-09 01:16:41 +01:00
Jonas Kvinge
15c2237d4a AlbumCoverChoiceController: Fix incorrectly formatted switch 2025-12-08 23:55:13 +01:00
Jonas Kvinge
93af866185 Formatting 2025-12-08 23:49:48 +01:00
dependabot[bot]
109ff90401 Bump vmactions/freebsd-vm from 1.2.8 to 1.2.9
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.2.8 to 1.2.9.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.2.8...v1.2.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 17:11:17 +01:00
Jonas Kvinge
d4b06289c3 clang-format: Add new KeepEmptyLines option 2025-12-07 01:20:39 +01:00
dependabot[bot]
4351c555a0 Bump apple-actions/import-codesign-certs from 5 to 6
Bumps [apple-actions/import-codesign-certs](https://github.com/apple-actions/import-codesign-certs) from 5 to 6.
- [Release notes](https://github.com/apple-actions/import-codesign-certs/releases)
- [Commits](https://github.com/apple-actions/import-codesign-certs/compare/v5...v6)

---
updated-dependencies:
- dependency-name: apple-actions/import-codesign-certs
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-07 00:51:50 +01:00
Strawberry Bot
ce4db40983 New translations 2025-12-07 00:06:44 +01:00
Jonas Kvinge
d37fb7410c ResizableTextEdit: Set word wrap 2025-12-01 23:23:23 +01:00
Jonas Kvinge
f1cdd71494 ResizableTextEdit: Move updateGeometry after resize 2025-12-01 23:23:05 +01:00
Jonas Kvinge
000ba997fb Playlist: Preserve track order in album shuffle mode
Fixes #1623
2025-12-01 22:47:12 +01:00
dependabot[bot]
579efffd14 Bump vmactions/openbsd-vm from 1.2.3 to 1.2.4
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.2.3...v1.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 19:47:08 +01:00
dependabot[bot]
3a098c8a0c Bump vmactions/freebsd-vm from 1.2.7 to 1.2.8
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.2.7 to 1.2.8.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.2.7...v1.2.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 18:46:03 +01:00
bastimeyer
5bce0ae87f GlobalShortcutsBackendKGlobalAccel: Add globalShortcutRepeated signal 2025-11-30 18:25:26 +01:00
Jonas Kvinge
afe6967c46 GstEnginePipeline: Handle "missing-plugin" messages 2025-11-30 18:13:30 +01:00
Jonas Kvinge
e91bab6d42 GstEngine: Only emit error for debugstr if it's set 2025-11-30 18:13:30 +01:00
Jonas Kvinge
5a64247761 PlaylistView: Use lamda in sort 2025-11-30 16:58:08 +01:00
Jonas Kvinge
9ed89afdb2 SpotifyService: Use 127.0.0.1 in redirect URL 2025-11-30 16:32:56 +01:00
Jonas Kvinge
0ac338026c About: Update sponsor info 2025-11-30 02:47:39 +01:00
Jonas Kvinge
4e5f84a7b7 RichPresence: Use pretty title 2025-11-26 19:14:16 +01:00
Jonas Kvinge
320a3c6815 RichPresence: Add copyright 2025-11-26 19:14:05 +01:00
Jonas Kvinge
72dd1d783a Turn on git revision 2025-11-25 21:13:00 +01:00
404 changed files with 12897 additions and 2019 deletions

View File

@@ -130,7 +130,10 @@ InsertBraces: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
KeepEmptyLines:
AtEndOfFile: true
AtStartOfBlock: true
AtStartOfFile: true
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''

View File

@@ -134,14 +134,14 @@ jobs:
run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT
- name: Upload source
if: matrix.opensuse_version == 'tumbleweed'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: source
path: |
/usr/src/packages/SOURCES/*.xz
- name: Upload rpm
if: matrix.opensuse_version != 'tumbleweed'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: opensuse-${{steps.set-subdir.outputs.subdir}}
path: |
@@ -234,7 +234,7 @@ jobs:
working-directory: build
run: rpmbuild -ba strawberry.spec
- name: Upload artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: fedora-${{matrix.fedora_version}}
path: |
@@ -333,7 +333,7 @@ jobs:
run: rpmbuild -ba strawberry.spec
- name: Upload artifacts
if: matrix.openmandriva_version != 'cooker'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: openmandriva-${{matrix.openmandriva_version}}
path: |
@@ -434,7 +434,7 @@ jobs:
working-directory: build
run: rpmbuild -ba strawberry.spec
- name: Upload artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: mageia-${{matrix.mageia_version}}
path: |
@@ -528,7 +528,7 @@ jobs:
- name: Copy deb
run: cp ../*.deb .
- name: Upload artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: debian-${{matrix.debian_version}}
path: |
@@ -624,7 +624,7 @@ jobs:
- name: Copy deb
run: cp ../*.deb ../*.ddeb .
- name: Upload artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ubuntu-${{matrix.ubuntu_version}}
path: |
@@ -739,12 +739,18 @@ jobs:
with:
fetch-depth: 0
submodules: recursive
- name: Free disk space
run: |
df -h
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc
sudo apt-get clean
df -h
- name: Build FreeBSD
id: build-freebsd
uses: vmactions/freebsd-vm@v1.2.7
uses: vmactions/freebsd-vm@v1.3.2
with:
usesh: true
mem: 4096
mem: 8192
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 sparsehash rapidjson
run: |
set -e
@@ -766,7 +772,7 @@ jobs:
submodules: recursive
- name: Build OpenBSD
id: build-openbsd
uses: vmactions/openbsd-vm@v1.2.3
uses: vmactions/openbsd-vm@v1.2.9
with:
usesh: true
mem: 4096
@@ -833,7 +839,7 @@ jobs:
- name: Import certificate file
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
uses: apple-actions/import-codesign-certs@v5
uses: apple-actions/import-codesign-certs@v6
with:
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE }}
p12-password: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
@@ -1642,7 +1648,7 @@ jobs:
with:
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
path: artifacts
- name: SSH Setup
@@ -1694,7 +1700,7 @@ jobs:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: gh release view "${{github.event.release.tag_name}}" --json assets | jq -r '.assets[].name'
- name: Download artifacts
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
path: artifacts
- name: Add artifacts to release

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@
/CMakeSettings.json
/dist/scripts/maketarball.sh
/debian/changelog
_codeql_detected_source_root

View File

@@ -163,4 +163,3 @@ extern "C" void Discord_Register(const char *applicationId, const char *command)
Discord_RegisterW(appId, wcommand);
}

View File

@@ -379,6 +379,13 @@ optional_component(STREAMTAGREADER ON "Stream tagreader"
optional_component(DISCORD_RPC ON "Discord Rich Presence"
DEPENDS "RapidJSON" RapidJSON_FOUND
optional_component(DROPBOX ON "Streaming: Dropbox"
DEPENDS "Stream tagreader" HAVE_STREAMTAGREADER
)
optional_component(ONEDRIVE ON "Streaming: OneDrive"
DEPENDS "Stream tagreader" HAVE_STREAMTAGREADER
)
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
@@ -701,6 +708,7 @@ set(SOURCES
src/lyrics/elyricsnetlyricsprovider.cpp
src/lyrics/letraslyricsprovider.cpp
src/lyrics/lyricfindlyricsprovider.cpp
src/lyrics/lrcliblyricsprovider.cpp
src/settings/settingsdialog.cpp
src/settings/settingspage.cpp
@@ -775,6 +783,7 @@ set(SOURCES
src/streaming/streamingcollectionviewcontainer.cpp
src/streaming/streamingsearchview.cpp
src/streaming/streamsongmimedata.cpp
src/streaming/cloudstoragestreamingservice.cpp
src/radios/radioservices.cpp
src/radios/radiobackend.cpp
@@ -997,6 +1006,7 @@ set(HEADERS
src/lyrics/elyricsnetlyricsprovider.h
src/lyrics/letraslyricsprovider.h
src/lyrics/lyricfindlyricsprovider.h
src/lyrics/lrcliblyricsprovider.h
src/settings/settingsdialog.h
src/settings/settingspage.h
@@ -1070,6 +1080,7 @@ set(HEADERS
src/streaming/streamingtabsview.h
src/streaming/streamingcollectionview.h
src/streaming/streamingcollectionviewcontainer.h
src/streaming/cloudstoragestreamingservice.h
src/radios/radioservices.h
src/radios/radiobackend.h
@@ -1461,6 +1472,7 @@ optional_source(HAVE_QOBUZ
src/qobuz/qobuzrequest.cpp
src/qobuz/qobuzstreamurlrequest.cpp
src/qobuz/qobuzfavoriterequest.cpp
src/qobuz/qobuzcredentialfetcher.cpp
src/settings/qobuzsettingspage.cpp
src/covermanager/qobuzcoverprovider.cpp
HEADERS
@@ -1470,12 +1482,32 @@ optional_source(HAVE_QOBUZ
src/qobuz/qobuzrequest.h
src/qobuz/qobuzstreamurlrequest.h
src/qobuz/qobuzfavoriterequest.h
src/qobuz/qobuzcredentialfetcher.h
src/settings/qobuzsettingspage.h
src/covermanager/qobuzcoverprovider.h
UI
src/settings/qobuzsettingspage.ui
)
optional_source(HAVE_DROPBOX
SOURCES
src/dropbox/dropboxservice.cpp
src/dropbox/dropboxurlhandler.cpp
src/dropbox/dropboxbaserequest.cpp
src/dropbox/dropboxsongsrequest.cpp
src/dropbox/dropboxstreamurlrequest.cpp
src/settings/dropboxsettingspage.cpp
HEADERS
src/dropbox/dropboxservice.h
src/dropbox/dropboxurlhandler.h
src/dropbox/dropboxbaserequest.h
src/dropbox/dropboxsongsrequest.h
src/dropbox/dropboxstreamurlrequest.h
src/settings/dropboxsettingspage.h
UI
src/settings/dropboxsettingspage.ui
)
qt_wrap_cpp(SOURCES ${HEADERS})
qt_wrap_ui(SOURCES ${UI})
qt_add_resources(SOURCES data/data.qrc data/icons.qrc)

View File

@@ -2,6 +2,22 @@ Strawberry Music Player
=======================
ChangeLog
Version 1.2.16 (2025.12.16):
* Make Discord Rich presence use filename if song title is missing
* Added better error message when a GStreamer plugin is missing
* Preserve track order in album shuffle mode when restarting playback (#1623)
* Possible fixes for context word wrap
* Added lyrics from lrclib.net
* Added option to turn off the use of sort tags for the collection
* Fixed Spotify login
* Fixed error dialog shown minimized if another Strawberry window than the mainwindow was active
* Fixed seeking to the end of the track and back causing seeking to stop working (#1675)
* Set current index when automatically selecting track (#1825)
* Make icon size for shuffle and repeat buttons adjust to screen resolution (#1838)
* Fixed song being removed from playlist when dragging to another application (#1815)
* Don't automatically scroll on dynamic playlists (#1427)
Version 1.2.15 (2025.11.25):
* Fixed system default language not respected

View File

@@ -66,7 +66,7 @@ Supporting open-source developers helps ensure continued maintenance and improve
- Loudness analysis and EBU R128 normalization
- Editing tags and fetching missing tags via [MusicBrainz](https://musicbrainz.org/)
- Album art from: [Last.fm](https://www.last.fm/), [MusicBrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/), [Spotify](https://www.spotify.com/)
- Lyrics from: [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/), [lololyrics](https://www.lololyrics.com/), [songlyrics](https://www.songlyrics.com/), [azlyrics](https://www.azlyrics.com/), [elyrics](https://www.elyrics.net/), [letras](https://www.letras.mus.br), [LyricFind](https://lyrics.lyricfind.com)
- Lyrics from: [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/), [lololyrics](https://www.lololyrics.com/), [songlyrics](https://www.songlyrics.com/), [azlyrics](https://www.azlyrics.com/), [elyrics](https://www.elyrics.net/), [letras](https://www.letras.mus.br), [LyricFind](https://lyrics.lyricfind.com) and [lrclib.net](https://lrclib.net/)
- Audio analyzer and equalizer
- Transfer music to USB, MTP and iPod devices
- Scrobbling to [Last.fm](https://www.last.fm/) and [ListenBrainz](https://listenbrainz.org/)

View File

@@ -1,9 +1,9 @@
set(STRAWBERRY_VERSION_MAJOR 1)
set(STRAWBERRY_VERSION_MINOR 2)
set(STRAWBERRY_VERSION_PATCH 15)
set(STRAWBERRY_VERSION_PATCH 16)
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION OFF)
set(INCLUDE_GIT_REVISION ON)
set(majorminorpatch "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}.${STRAWBERRY_VERSION_PATCH}")

View File

@@ -98,6 +98,7 @@
<file>icons/128x128/somafm.png</file>
<file>icons/128x128/radioparadise.png</file>
<file>icons/128x128/musicbrainz.png</file>
<file>icons/128x128/dropbox.png</file>
<file>icons/64x64/albums.png</file>
<file>icons/64x64/alsa.png</file>
<file>icons/64x64/application-exit.png</file>
@@ -197,6 +198,7 @@
<file>icons/64x64/somafm.png</file>
<file>icons/64x64/radioparadise.png</file>
<file>icons/64x64/musicbrainz.png</file>
<file>icons/64x64/dropbox.png</file>
<file>icons/48x48/albums.png</file>
<file>icons/48x48/alsa.png</file>
<file>icons/48x48/application-exit.png</file>
@@ -300,6 +302,7 @@
<file>icons/48x48/somafm.png</file>
<file>icons/48x48/radioparadise.png</file>
<file>icons/48x48/musicbrainz.png</file>
<file>icons/48x48/dropbox.png</file>
<file>icons/32x32/albums.png</file>
<file>icons/32x32/alsa.png</file>
<file>icons/32x32/application-exit.png</file>
@@ -403,6 +406,7 @@
<file>icons/32x32/somafm.png</file>
<file>icons/32x32/radioparadise.png</file>
<file>icons/32x32/musicbrainz.png</file>
<file>icons/32x32/dropbox.png</file>
<file>icons/22x22/albums.png</file>
<file>icons/22x22/alsa.png</file>
<file>icons/22x22/application-exit.png</file>
@@ -506,5 +510,6 @@
<file>icons/22x22/somafm.png</file>
<file>icons/22x22/radioparadise.png</file>
<file>icons/22x22/musicbrainz.png</file>
<file>icons/22x22/dropbox.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

82
data/schema/schema-22.sql Normal file
View File

@@ -0,0 +1,82 @@
CREATE TABLE IF NOT EXISTS dropbox_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
fingerprint TEXT,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
lastseen INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1,
acoustid_id TEXT,
acoustid_fingerprint TEXT,
musicbrainz_album_artist_id TEXT,
musicbrainz_artist_id TEXT,
musicbrainz_original_artist_id TEXT,
musicbrainz_album_id TEXT,
musicbrainz_original_album_id TEXT,
musicbrainz_recording_id TEXT,
musicbrainz_track_id TEXT,
musicbrainz_disc_id TEXT,
musicbrainz_release_group_id TEXT,
musicbrainz_work_id TEXT,
ebur128_integrated_loudness_lufs REAL,
ebur128_loudness_range_lu REAL
);
UPDATE schema_version SET version=22;

View File

@@ -1018,6 +1018,87 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
);
CREATE TABLE IF NOT EXISTS dropbox_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
track INTEGER NOT NULL DEFAULT -1,
disc INTEGER NOT NULL DEFAULT -1,
year INTEGER NOT NULL DEFAULT -1,
originalyear INTEGER NOT NULL DEFAULT -1,
genre TEXT,
compilation INTEGER NOT NULL DEFAULT 0,
composer TEXT,
performer TEXT,
grouping TEXT,
comment TEXT,
lyrics TEXT,
artist_id TEXT,
album_id TEXT,
song_id TEXT,
beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0,
bitrate INTEGER NOT NULL DEFAULT -1,
samplerate INTEGER NOT NULL DEFAULT -1,
bitdepth INTEGER NOT NULL DEFAULT -1,
source INTEGER NOT NULL DEFAULT 0,
directory_id INTEGER NOT NULL DEFAULT -1,
url TEXT NOT NULL,
filetype INTEGER NOT NULL DEFAULT 0,
filesize INTEGER NOT NULL DEFAULT -1,
mtime INTEGER NOT NULL DEFAULT -1,
ctime INTEGER NOT NULL DEFAULT -1,
unavailable INTEGER DEFAULT 0,
fingerprint TEXT,
playcount INTEGER NOT NULL DEFAULT 0,
skipcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER NOT NULL DEFAULT -1,
lastseen INTEGER NOT NULL DEFAULT -1,
compilation_detected INTEGER DEFAULT 0,
compilation_on INTEGER NOT NULL DEFAULT 0,
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
cue_path TEXT,
rating INTEGER DEFAULT -1,
acoustid_id TEXT,
acoustid_fingerprint TEXT,
musicbrainz_album_artist_id TEXT,
musicbrainz_artist_id TEXT,
musicbrainz_original_artist_id TEXT,
musicbrainz_album_id TEXT,
musicbrainz_original_album_id TEXT,
musicbrainz_recording_id TEXT,
musicbrainz_track_id TEXT,
musicbrainz_disc_id TEXT,
musicbrainz_release_group_id TEXT,
musicbrainz_work_id TEXT,
ebur128_integrated_loudness_lufs REAL,
ebur128_loudness_range_lu REAL
);
CREATE TABLE IF NOT EXISTS playlists (
name TEXT NOT NULL,

2
debian/control vendored
View File

@@ -60,7 +60,7 @@ Description: music player and music collection organizer
- Edit tags on audio files
- Automatically retrieve tags from MusicBrainz
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net, letras.mus.br and LyricFind
- Lyrics from multiple sources
- Audio analyzer
- Audio equalizer
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic

View File

@@ -31,7 +31,7 @@
<li>Edit tags on audio files</li>
<li>Automatically retrieve tags from MusicBrainz</li>
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
<li>Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net, letras.mus.br and LyricFind</li>
<li>Lyrics from multiple sources</li>
<li>Audio analyzer and equalizer</li>
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
<li>Scrobbler with support for Last.fm and ListenBrainz</li>
@@ -51,6 +51,7 @@
</screenshots>
<update_contact>eclipseo@fedoraproject.org</update_contact>
<releases>
<release version="1.2.16" date="2025-12-16"/>
<release version="1.2.15" date="2025-11-25"/>
<release version="1.2.14" date="2025-10-25"/>
<release version="1.2.13" date="2025-08-31"/>

View File

@@ -29,9 +29,7 @@ Features:
.br
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
.br
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net, letras.mus.br and LyricFind
.br
- Support for multiple backends
- Lyrics from multiple sources
.br
- Audio analyzer
.br

View File

@@ -93,8 +93,7 @@ Features:
- Edit tags on audio files
- Automatically retrieve tags from MusicBrainz
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net, letras.mus.br and LyricFind
- Support for multiple backends
- Lyrics from multiple sources
- Audio analyzer
- Audio equalizer
- Scrobbler with support for Last.fm and ListenBrainz

View File

@@ -90,4 +90,3 @@ class AnalyzerBase : public QWidget {
};
#endif // ANALYZERBASE_H

View File

@@ -102,7 +102,6 @@ void AnalyzerContainer::AddAnalyzerType() {
action->setCheckable(true);
actions_ << action;
QObject::connect(action, &QAction::triggered, [this, id]() { ChangeAnalyzer(id); });
}
#endif // ANALYZERCONTAINER_H

View File

@@ -66,6 +66,7 @@ BlockAnalyzer::BlockAnalyzer(QWidget *parent)
// mxcl says null pixmaps cause crashes, so let's play it safe
std::fill(fade_bars_.begin(), fade_bars_.end(), QPixmap(1, 1));
}
void BlockAnalyzer::resizeEvent(QResizeEvent *e) {

View File

@@ -41,14 +41,14 @@ class BlockAnalyzer : public AnalyzerBase {
Q_OBJECT
public:
Q_INVOKABLE explicit BlockAnalyzer(QWidget*);
Q_INVOKABLE explicit BlockAnalyzer(QWidget *parent);
static const char *kName;
protected:
void transform(Scope&) override;
void transform(Scope &s) override;
void analyze(QPainter &p, const Scope &s, const bool new_frame) override;
void resizeEvent(QResizeEvent*) override;
void resizeEvent(QResizeEvent *e) override;
virtual void paletteChange(const QPalette &_palette);
void framerateChanged() override;

View File

@@ -40,7 +40,7 @@ class BoomAnalyzer : public AnalyzerBase {
Q_OBJECT
public:
Q_INVOKABLE explicit BoomAnalyzer(QWidget*);
Q_INVOKABLE explicit BoomAnalyzer(QWidget *parent);
static const char *kName;
@@ -70,7 +70,6 @@ class BoomAnalyzer : public AnalyzerBase {
QPixmap barPixmap_;
QPixmap canvas_;
};
#endif // BOOMANALYZER_H

View File

@@ -55,7 +55,7 @@ class FHT {
/**
* Recursive in-place Hartley transform. For internal use only!
*/
void _transform(float*, int, int);
void _transform(float *p, int n, int k);
public:
/**
@@ -68,7 +68,7 @@ class FHT {
~FHT();
int sizeExp() const;
int size() const;
void scale(float*, float) const;
void scale(float *p, float d) const;
/**
* Exponentially Weighted Moving Average (EWMA) filter.
@@ -90,12 +90,12 @@ class FHT {
/**
* Semi-logarithmic audio spectrum.
*/
void semiLogSpectrum(float*);
void semiLogSpectrum(float *p);
/**
* Fourier spectrum.
*/
void spectrum(float*);
void spectrum(float *p);
/**
* Calculates a mathematically correct FFT power spectrum.
@@ -103,7 +103,7 @@ class FHT {
* and factor the 0.5 in the final scaling factor.
* @see FHT::power2()
*/
void power(float*);
void power(float *p);
/**
* Calculates an FFT power spectrum with doubled values as a
@@ -112,14 +112,14 @@ class FHT {
* of @f$2^n@f$ input values. This is the fastest transform.
* @see FHT::power()
*/
void power2(float*);
void power2(float *p);
/**
* Discrete Hartley transform of data sets with 8 values.
*/
static void transform8(float*);
static void transform8(float *p);
void transform(float*);
void transform(float *p);
};
#endif // FHT_H

View File

@@ -140,7 +140,6 @@ class CollectionBackend : public CollectionBackendInterface {
Q_OBJECT
public:
Q_INVOKABLE explicit CollectionBackend(QObject *parent = nullptr);
~CollectionBackend();
@@ -331,4 +330,3 @@ class CollectionBackend : public CollectionBackendInterface {
};
#endif // COLLECTIONBACKEND_H

View File

@@ -26,7 +26,6 @@
class CollectionFilterOptions {
public:
explicit CollectionFilterOptions();
// Filter mode:

View File

@@ -135,4 +135,3 @@ class CollectionFilterWidget : public QWidget {
};
#endif // COLLECTIONFILTERWIDGET_H

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, 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
@@ -189,6 +189,26 @@ void CollectionLibrary::ReloadSettings() {
}
void CollectionLibrary::CurrentSongChanged(const Song &song) {
current_song_url_ = song.url();
if (!pending_song_saves_.isEmpty()) {
SavePendingPlaycountsAndRatings();
}
}
void CollectionLibrary::Stopped() {
current_song_url_ = QUrl();
if (!pending_song_saves_.isEmpty()) {
SavePendingPlaycountsAndRatings();
}
}
void CollectionLibrary::SyncPlaycountAndRatingToFilesAsync() {
(void)QtConcurrent::run(&CollectionLibrary::SyncPlaycountAndRatingToFiles, this);
@@ -212,18 +232,85 @@ void CollectionLibrary::SyncPlaycountAndRatingToFiles() {
}
void CollectionLibrary::SongsPlaycountChanged(const SongList &songs, const bool save_tags) const {
void CollectionLibrary::SongsPlaycountChanged(const SongList &songs, const bool save_tags) {
if (save_tags || save_playcounts_to_files_) {
tagreader_client_->SaveSongsPlaycountAsync(songs);
SongList songs_to_save_now;
for (const Song &song : songs) {
if (song.url().isLocalFile() && song.url() == current_song_url_ &&
(song.filetype() == Song::FileType::OggFlac || song.filetype() == Song::FileType::OggVorbis || song.filetype() == Song::FileType::OggOpus)) {
qLog(Debug) << "Deferring playcount save for currently playing file" << song.url().toLocalFile();
if (pending_song_saves_.contains(song.url())) {
SharedPtr<PendingSongSave> pending_song_save = pending_song_saves_[song.url()];
pending_song_save->save_playcount = true;
pending_song_save->song.set_playcount(song.playcount());
}
else {
SharedPtr<PendingSongSave> pending_song_save = make_shared<PendingSongSave>();
pending_song_save->save_playcount = true;
pending_song_save->song = song;
pending_song_saves_.insert(song.url(), pending_song_save);
}
}
else {
songs_to_save_now << song;
}
}
if (!songs_to_save_now.isEmpty()) {
tagreader_client_->SaveSongsPlaycountAsync(songs_to_save_now);
}
}
}
void CollectionLibrary::SongsRatingChanged(const SongList &songs, const bool save_tags) const {
void CollectionLibrary::SongsRatingChanged(const SongList &songs, const bool save_tags) {
if (save_tags || save_ratings_to_files_) {
tagreader_client_->SaveSongsRatingAsync(songs);
SongList songs_to_save_now;
for (const Song &song : songs) {
if (song.url().isLocalFile() && song.url() == current_song_url_ &&
(song.filetype() == Song::FileType::OggFlac || song.filetype() == Song::FileType::OggVorbis || song.filetype() == Song::FileType::OggOpus)) {
qLog(Debug) << "Deferring rating save for currently playing file" << song.url().toLocalFile();
if (pending_song_saves_.contains(song.url())) {
SharedPtr<PendingSongSave> pending_song_save = pending_song_saves_[song.url()];
pending_song_save->save_rating = true;
pending_song_save->song.set_rating(song.rating());
}
else {
SharedPtr<PendingSongSave> pending_song_save = make_shared<PendingSongSave>();
pending_song_save->save_rating = true;
pending_song_save->song = song;
pending_song_saves_.insert(song.url(), pending_song_save);
}
}
else {
songs_to_save_now << song;
}
}
if (!songs_to_save_now.isEmpty()) {
tagreader_client_->SaveSongsRatingAsync(songs_to_save_now);
}
}
}
void CollectionLibrary::SavePendingPlaycountsAndRatings() {
for (auto it = pending_song_saves_.constBegin(); it != pending_song_saves_.constEnd();) {
const QUrl url = it.key();
SharedPtr<PendingSongSave> pending_song_save = it.value();
if (url == current_song_url_) {
++it;
continue;
}
qLog(Debug) << "Saving deferred playcount/rating for" << url.toLocalFile();
if (pending_song_save->save_playcount) {
tagreader_client_->SaveSongsPlaycountAsync(SongList() << pending_song_save->song);
}
if (pending_song_save->save_rating) {
tagreader_client_->SaveSongsRatingAsync(SongList() << pending_song_save->song);
}
it = pending_song_saves_.erase(it);
}
}

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, 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 <QList>
#include <QHash>
#include <QMap>
#include <QString>
#include "includes/shared_ptr.h"
@@ -71,6 +72,7 @@ class CollectionLibrary : public QObject {
private:
void SyncPlaycountAndRatingToFiles();
void SavePendingPlaycountsAndRatings();
public Q_SLOTS:
void ReloadSettings();
@@ -84,16 +86,26 @@ class CollectionLibrary : public QObject {
void IncrementalScan();
void CurrentSongChanged(const Song &song);
void Stopped();
private Q_SLOTS:
void ExitReceived();
void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false) const;
void SongsRatingChanged(const SongList &songs, const bool save_tags = false) const;
void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false);
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
Q_SIGNALS:
void Error(const QString &error);
void ExitFinished();
private:
class PendingSongSave {
public:
Song song;
bool save_playcount = false;
bool save_rating = false;
};
const SharedPtr<TaskManager> task_manager_;
const SharedPtr<TagReaderClient> tagreader_client_;
@@ -111,6 +123,10 @@ class CollectionLibrary : public QObject {
bool save_playcounts_to_files_;
bool save_ratings_to_files_;
QUrl current_song_url_;
QMap<QUrl, SharedPtr<PendingSongSave>> pending_song_saves_;
};
#endif

View File

@@ -212,6 +212,7 @@ void CollectionModel::ReloadSettings() {
const bool show_various_artists = settings.value(CollectionSettings::kVariousArtists, true).toBool();
const bool sort_skip_articles_for_artists = settings.value(CollectionSettings::kSkipArticlesForArtists, true).toBool();
const bool sort_skip_articles_for_albums = settings.value(CollectionSettings::kSkipArticlesForAlbums, false).toBool();
const bool use_sort_tags = settings.value(CollectionSettings::kUseSortTags, true).toBool();
use_disk_cache_ = settings.value(CollectionSettings::kSettingsDiskCacheEnable, false).toBool();
QPixmapCache::setCacheLimit(static_cast<int>(MaximumCacheSize(&settings, CollectionSettings::kSettingsCacheSize, CollectionSettings::kSettingsCacheSizeUnit, CollectionSettings::kSettingsCacheSizeDefault) / 1024));
@@ -227,12 +228,14 @@ void CollectionModel::ReloadSettings() {
show_dividers != options_current_.show_dividers ||
show_various_artists != options_current_.show_various_artists ||
sort_skip_articles_for_artists != options_current_.sort_skip_articles_for_artists ||
sort_skip_articles_for_albums != options_current_.sort_skip_articles_for_albums) {
sort_skip_articles_for_albums != options_current_.sort_skip_articles_for_albums ||
use_sort_tags != options_current_.use_sort_tags) {
options_current_.show_pretty_covers = show_pretty_covers;
options_current_.show_dividers = show_dividers;
options_current_.show_various_artists = show_various_artists;
options_current_.sort_skip_articles_for_artists = sort_skip_articles_for_artists;
options_current_.sort_skip_articles_for_albums = sort_skip_articles_for_albums;
options_current_.use_sort_tags = use_sort_tags;
ScheduleReset();
}
@@ -705,7 +708,7 @@ CollectionItem *CollectionModel::CreateContainerItem(const GroupBy group_by, con
QString divider_key;
if (options_active_.show_dividers && container_level == 0) {
divider_key = DividerKey(group_by, song, SortText(group_by, song, options_active_.sort_skip_articles_for_artists, options_active_.sort_skip_articles_for_albums));
divider_key = DividerKey(group_by, song, SortText(group_by, song, options_active_.sort_skip_articles_for_artists, options_active_.sort_skip_articles_for_albums, options_active_.use_sort_tags));
if (!divider_key.isEmpty()) {
if (!divider_nodes_.contains(divider_key)) {
CreateDividerItem(divider_key, DividerDisplayText(group_by, divider_key), parent);
@@ -719,7 +722,7 @@ CollectionItem *CollectionModel::CreateContainerItem(const GroupBy group_by, con
item->container_level = container_level;
item->container_key = container_key;
item->display_text = DisplayText(group_by, song);
item->sort_text = SortText(group_by, song, options_active_.sort_skip_articles_for_artists, options_active_.sort_skip_articles_for_albums);
item->sort_text = SortText(group_by, song, options_active_.sort_skip_articles_for_artists, options_active_.sort_skip_articles_for_albums, options_active_.use_sort_tags);
if (!divider_key.isEmpty()) {
item->sort_text.prepend(divider_key + QLatin1Char(' '));
}
@@ -1074,37 +1077,37 @@ QString CollectionModel::PrettyFormat(const Song &song) {
}
QString CollectionModel::SortText(const GroupBy group_by, const Song &song, const bool sort_skip_articles_for_artists, const bool sort_skip_articles_for_albums) {
QString CollectionModel::SortText(const GroupBy group_by, const Song &song, const bool sort_skip_articles_for_artists, const bool sort_skip_articles_for_albums, const bool use_sort_tags) {
switch (group_by) {
case GroupBy::AlbumArtist:
return SortTextForName(song.effective_albumartistsort(), sort_skip_articles_for_artists);
return SortTextForName(use_sort_tags ? song.effective_albumartistsort() : song.effective_albumartist(), sort_skip_articles_for_artists);
case GroupBy::Artist:
return SortTextForName(song.effective_artistsort(), sort_skip_articles_for_artists);
return SortTextForName(use_sort_tags ? song.effective_artistsort() : song.artist(), sort_skip_articles_for_artists);
case GroupBy::Album:
return SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums);
return SortTextForName(use_sort_tags ? song.effective_albumsort() : song.album(), sort_skip_articles_for_albums);
case GroupBy::AlbumDisc:
return SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums) + SortTextForNumber(std::max(0, song.disc()));
return SortTextForName(use_sort_tags ? song.effective_albumsort() : song.album(), sort_skip_articles_for_albums) + SortTextForNumber(std::max(0, song.disc()));
case GroupBy::YearAlbum:
return SortTextForNumber(std::max(0, song.year())) + song.grouping() + SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums);
return SortTextForYear(song.year()) + song.grouping() + SortTextForName(use_sort_tags ? song.effective_albumsort() : song.album(), sort_skip_articles_for_albums);
case GroupBy::YearAlbumDisc:
return SortTextForNumber(std::max(0, song.year())) + SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums) + SortTextForNumber(std::max(0, song.disc()));
return SortTextForYear(song.year()) + SortTextForName(use_sort_tags ? song.effective_albumsort() : song.album(), sort_skip_articles_for_albums) + SortTextForNumber(std::max(0, song.disc()));
case GroupBy::OriginalYearAlbum:
return SortTextForNumber(std::max(0, song.effective_originalyear())) + song.grouping() + SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums);
return SortTextForYear(song.effective_originalyear()) + song.grouping() + SortTextForName(use_sort_tags ? song.effective_albumsort() : song.album(), sort_skip_articles_for_albums);
case GroupBy::OriginalYearAlbumDisc:
return SortTextForNumber(std::max(0, song.effective_originalyear())) + SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums) + SortTextForNumber(std::max(0, song.disc()));
return SortTextForYear(song.effective_originalyear()) + SortTextForName(use_sort_tags ? song.effective_albumsort() : song.album(), sort_skip_articles_for_albums) + SortTextForNumber(std::max(0, song.disc()));
case GroupBy::Disc:
return SortTextForNumber(std::max(0, song.disc()));
case GroupBy::Year:
return SortTextForNumber(std::max(0, song.year())) + QLatin1Char(' ');
return SortTextForYear(song.year()) + QLatin1Char(' ');
case GroupBy::OriginalYear:
return SortTextForNumber(std::max(0, song.effective_originalyear())) + QLatin1Char(' ');
return SortTextForYear(song.effective_originalyear()) + QLatin1Char(' ');
case GroupBy::Genre:
return SortText(song.genre());
case GroupBy::Composer:
return SortTextForName(song.effective_composersort(), sort_skip_articles_for_artists);
return SortTextForName(use_sort_tags ? song.effective_composersort() : song.composer(), sort_skip_articles_for_artists);
case GroupBy::Performer:
return SortTextForName(song.effective_performersort(), sort_skip_articles_for_artists);
return SortTextForName(use_sort_tags ? song.effective_performersort() : song.performer(), sort_skip_articles_for_artists);
case GroupBy::Grouping:
return SortText(song.grouping());
case GroupBy::FileType:
@@ -1162,14 +1165,14 @@ QString CollectionModel::SortTextForSong(const Song &song) {
QString CollectionModel::SortTextForYear(const int year) {
QString str = QString::number(year);
const QString str = QString::number(std::max(year, 0));
return QStringLiteral("0").repeated(qMax(0, 4 - str.length())) + str;
}
QString CollectionModel::SortTextForBitrate(const int bitrate) {
QString str = QString::number(bitrate);
const QString str = QString::number(bitrate);
return QStringLiteral("0").repeated(qMax(0, 3 - str.length())) + str;
}
@@ -1218,27 +1221,30 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const Song &song,
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break;
case GroupBy::AlbumDisc:
key = PrettyAlbumDisc(song.album(), song.disc());
key = TextOrUnknown(song.album());
key.append(QLatin1Char('-') + SortTextForNumber(song.disc()));
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break;
case GroupBy::YearAlbum:
key = PrettyYearAlbum(song.year(), song.album());
key = SortTextForYear(song.year()) + QLatin1Char('-') + TextOrUnknown(song.album());
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break;
case GroupBy::YearAlbumDisc:
key = PrettyYearAlbumDisc(song.year(), song.album(), song.disc());
key = SortTextForYear(song.year()) + QLatin1Char('-') + TextOrUnknown(song.album());
key.append(QLatin1Char('-') + SortTextForNumber(song.disc()));
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break;
case GroupBy::OriginalYearAlbum:
key = PrettyYearAlbum(song.effective_originalyear(), song.album());
key = SortTextForYear(song.effective_originalyear()) + QLatin1Char('-') + TextOrUnknown(song.album());
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break;
case GroupBy::OriginalYearAlbumDisc:
key = PrettyYearAlbumDisc(song.effective_originalyear(), song.album(), song.disc());
key = SortTextForYear(song.effective_originalyear()) + QLatin1Char('-') + TextOrUnknown(song.album());
key.append(QLatin1Char('-') + SortTextForNumber(song.disc()));
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break;
@@ -1246,10 +1252,10 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const Song &song,
key = PrettyDisc(song.disc());
break;
case GroupBy::Year:
key = QString::number(std::max(0, song.year()));
key = SortTextForYear(song.year());
break;
case GroupBy::OriginalYear:
key = QString::number(std::max(0, song.effective_originalyear()));
key = SortTextForYear(song.effective_originalyear());
break;
case GroupBy::Genre:
key = TextOrUnknown(song.genre());
@@ -1341,7 +1347,7 @@ QString CollectionModel::DividerKey(const GroupBy group_by, const Song &song, co
case GroupBy::Bitdepth:
return SortTextForNumber(song.bitdepth());
case GroupBy::Bitrate:
return SortTextForNumber(song.bitrate());
return SortTextForBitrate(song.bitrate());
case GroupBy::None:
case GroupBy::GroupByCount:
return QString();

View File

@@ -131,6 +131,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
show_various_artists(true),
sort_skip_articles_for_artists(false),
sort_skip_articles_for_albums(false),
use_sort_tags(true),
separate_albums_by_grouping(false) {}
Grouping group_by;
@@ -139,6 +140,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
bool show_various_artists;
bool sort_skip_articles_for_artists;
bool sort_skip_articles_for_albums;
bool use_sort_tags;
bool separate_albums_by_grouping;
CollectionFilterOptions filter_options;
};
@@ -178,14 +180,14 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
QMimeData *mimeData(const QModelIndexList &indexes) const override;
// Utility functions for manipulating text
QString DisplayText(const GroupBy group_by, const Song &song);
static QString DisplayText(const GroupBy group_by, const Song &song);
static QString TextOrUnknown(const QString &text);
static QString PrettyYearAlbum(const int year, const QString &album);
static QString PrettyAlbumDisc(const QString &album, const int disc);
static QString PrettyYearAlbumDisc(const int year, const QString &album, const int disc);
static QString PrettyDisc(const int disc);
static QString PrettyFormat(const Song &song);
static QString SortText(const GroupBy group_by, const Song &song, const bool sort_skip_articles_for_artists, const bool sort_skip_articles_for_albums);
static QString SortText(const GroupBy group_by, const Song &song, const bool sort_skip_articles_for_artists, const bool sort_skip_articles_for_albums, const bool use_sort_tags);
static QString SortText(QString text);
static QString SortTextForName(const QString &name, const bool sort_skip_articles);
static QString SortTextForNumber(const int number);

View File

@@ -41,9 +41,12 @@ bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) {
case Song::Source::Collection:
col = 0;
break;
default:
case Song::Source::Dropbox:
col = static_cast<int>(Song::kRowIdColumns.count());
break;
default:
col = static_cast<int>(Song::kRowIdColumns.count() * 2);
break;
}
song_.InitFromQuery(query, true, col);

View File

@@ -58,4 +58,3 @@ class CollectionPlaylistItem : public PlaylistItem {
};
#endif // COLLECTIONPLAYLISTITEM_H

View File

@@ -706,8 +706,14 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
qLog(Debug) << file << "is missing EBU R 128 loudness characteristics.";
}
// If the song is unavailable and nothing has changed, just mark it as available without re-scanning
// For CUE files with multiple sections, all sections share the same file and would have the same availability status
if (matching_song.unavailable() && !changed && !missing_fingerprint && !missing_loudness_characteristics) {
qLog(Debug) << "Unavailable song" << file << "restored without re-scanning.";
t->readded_songs << matching_songs;
}
// The song's changed or missing fingerprint - create fingerprint and reread the metadata from file.
if (t->ignores_mtime() || changed || missing_fingerprint || missing_loudness_characteristics) {
else if (t->ignores_mtime() || changed || missing_fingerprint || missing_loudness_characteristics) {
QString fingerprint;
#ifdef HAVE_SONGFINGERPRINTING
@@ -728,12 +734,6 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
}
}
// Nothing has changed - mark the song available without re-scanning
else if (matching_song.unavailable()) {
qLog(Debug) << "Unavailable song" << file << "restored.";
t->readded_songs << matching_songs;
}
}
else { // Search the DB by fingerprint.
QString fingerprint;

View File

@@ -137,7 +137,7 @@ class CollectionWatcher : public QObject {
QStringList files_changed_path_;
private:
ScanTransaction &operator=(const ScanTransaction&) { return *this; }
ScanTransaction &operator=(const ScanTransaction &transaction) { Q_UNUSED(transaction); return *this; }
int task_id_;
quint64 progress_;
@@ -261,7 +261,6 @@ class CollectionWatcher : public QObject {
static QStringList sValidImages;
qint64 last_scan_time_;
};
inline QString CollectionWatcher::NoExtensionPart(const QString &fileName) {

View File

@@ -33,6 +33,8 @@
#cmakedefine HAVE_SPOTIFY
#cmakedefine HAVE_QOBUZ
#cmakedefine HAVE_DISCORD_RPC
#cmakedefine HAVE_DROPBOX
#cmakedefine HAVE_ONEDRIVE
#cmakedefine HAVE_TAGLIB_DSFFILE
#cmakedefine HAVE_TAGLIB_DSDIFFFILE

View File

@@ -70,6 +70,6 @@ enum class BackgroundImagePosition {
BottomRight = 5
};
} // namespace
} // namespace AppearanceSettings
#endif // APPEARANCESETTINGS_H

View File

@@ -63,6 +63,6 @@ constexpr qint64 kDefaultBufferDuration = 4000;
constexpr double kDefaultBufferLowWatermark = 0.33;
constexpr double kDefaultBufferHighWatermark = 0.99;
} // namespace
} // namespace BackendSettings
#endif // BACKENDSETTINGS_H

View File

@@ -71,6 +71,6 @@ constexpr char kDoubleClickPlaylistAddMode[] = "doubleclick_playlist_addmode";
constexpr char kSeekStepSec[] = "seek_step_sec";
constexpr char kVolumeIncrement[] = "volume_increment";
} // namespace
} // namespace BehaviourSettings
#endif // BEHAVIOURSETTINGS_H

View File

@@ -37,7 +37,7 @@ constexpr char kPrettyCovers[] = "pretty_covers";
constexpr char kVariousArtists[] = "various_artists";
constexpr char kSkipArticlesForArtists[] = "skip_articles_for_artists";
constexpr char kSkipArticlesForAlbums[] = "skip_articles_for_albums";
constexpr char kShowSortText[] = "show_sort_text";
constexpr char kUseSortTags[] = "use_short_tags";
constexpr char kSettingsCacheSize[] = "cache_size";
constexpr char kSettingsCacheSizeUnit[] = "cache_size_unit";
constexpr char kSettingsDiskCacheEnable[] = "disk_cache_enable";
@@ -59,6 +59,6 @@ enum class CacheSizeUnit {
TB
};
} // namespace
} // namespace CollectionSettings
#endif // COLLECTIONSETTINGS_H

View File

@@ -43,6 +43,6 @@ constexpr char kSettingsSummaryFmt[] = "SummaryFmt";
constexpr char kDefaultFontFamily[] = "Noto Sans";
constexpr qreal kDefaultFontSizeHeadline = 11;
} // namespace
} // namespace ContextSettings
#endif // CONTEXTSETTINGS_H

View File

@@ -32,6 +32,6 @@ constexpr char kSaveOverwrite[] = "save_overwrite";
constexpr char kSaveLowercase[] = "save_lowercase";
constexpr char kSaveReplaceSpaces[] = "save_replace_spaces";
} // namespace
} // namespace CoversSettings
#endif // COVERSSETTINGS_H

View File

@@ -0,0 +1,30 @@
/*
* 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 DROPBOXCONSTANTS_H
#define DROPBOXCONSTANTS_H
namespace DropboxConstants {
constexpr char kApiUrl[] = "https://api.dropboxapi.com";
constexpr char kNotifyApiUrl[] = "https://notify.dropboxapi.com";
} // namespace
#endif // DROPBOXCONSTANTS_H

View File

@@ -0,0 +1,46 @@
/*
* 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 DROPBOXSETTINGS_H
#define DROPBOXSETTINGS_H
namespace DropboxSettings {
constexpr char kSettingsGroup[] = "Dropbox";
constexpr char kEnabled[] = "enabled";
constexpr char kSearchDelay[] = "searchdelay";
constexpr char kArtistsSearchLimit[] = "artistssearchlimit";
constexpr char kAlbumsSearchLimit[] = "albumssearchlimit";
constexpr char kSongsSearchLimit[] = "songssearchlimit";
constexpr char kFetchAlbums[] = "fetchalbums";
constexpr char kDownloadAlbumCovers[] = "downloadalbumcovers";
constexpr char kTokenType[] = "token_type";
constexpr char kAccessToken[] = "access_token";
constexpr char kRefreshToken[] = "refresh_token";
constexpr char kExpiresIn[] = "expires_in";
constexpr char kLoginTime[] = "login_time";
constexpr char kApiUrl[] = "https://api.dropboxapi.com";
constexpr char kNotifyApiUrl[] = "https://notify.dropboxapi.com";
} // namespace
#endif // DROPBOXSETTINGS_H

View File

@@ -26,6 +26,6 @@ constexpr char kSettingsGroup[] = "GlobalShortcuts";
constexpr char kUseKGlobalAccel[] = "use_kglobalaccel";
constexpr char kUseX11[] = "use_x11";
} // namespace
} // namespace GlobalShortcutsSettings
#endif // GLOBALSHORTCUTSSETTINGS_H

View File

@@ -25,6 +25,6 @@ namespace LyricsSettings {
constexpr char kSettingsGroup[] = "Lyrics";
constexpr char kProviders[] = "providers";
} // namespace
} // namespace LyricsSettings
#endif // LYRICSSETTINGS_H

View File

@@ -32,6 +32,6 @@ constexpr char kGeometry[] = "geometry";
constexpr char kSplitterState[] = "splitter_state";
constexpr char kDoNotShowSponsorMessage[] = "do_not_show_sponsor_message";
} // namespace
} // namespace MainWindowSettings
#endif // MAINWINDOWSETTINGS_H

View File

@@ -38,6 +38,6 @@ constexpr char kShow[] = "show";
constexpr char kStyle[] = "style";
constexpr char kSave[] = "save";
} // namespace
} // namespace MoodbarSettings
#endif // MOODBARSETTINGS_H

View File

@@ -32,6 +32,6 @@ constexpr char kUsername[] = "username";
constexpr char kPassword[] = "password";
constexpr char kEngine[] = "engine";
} // namespace
} // namespace NetworkProxySettings
#endif // NETWORKPROXYSETTINGS_H

View File

@@ -45,7 +45,7 @@ constexpr char kCustomTextEnabled[] = "CustomTextEnabled";
constexpr char kCustomText1[] = "CustomText1";
constexpr char kCustomText2[] = "CustomText2";
} // namespace
} // namespace OSDSettings
namespace OSDPrettySettings {
@@ -63,7 +63,7 @@ constexpr char kFading[] = "fading";
constexpr QRgb kPresetBlue = qRgb(102, 150, 227);
constexpr QRgb kPresetRed = qRgb(202, 22, 16);
} // namespace
} // namespace OSDPrettySettings
namespace DiscordRPCSettings {
@@ -79,6 +79,6 @@ enum class StatusDisplayType {
Song
};
} // namespace
} // namespace DiscordRPCSettings
#endif // NOTIFICATIONSSETTINGS_H

View File

@@ -63,7 +63,7 @@ constexpr char kLastSaveExtension[] = "last_save_extension";
constexpr char kLastSaveAllPath[] = "last_save_all_path";
constexpr char kLastSaveAllExtension[] = "last_save_all_extension";
} // namespace
} // namespace PlaylistSettings
Q_DECLARE_METATYPE(PlaylistSettings::PathType)

View File

@@ -43,6 +43,6 @@ constexpr char kCredentialsId[] = "credentials_id";
constexpr char kDeviceId[] = "device_id";
constexpr char kUserAuthToken[] = "user_auth_token";
} // namespace
} // namespace QobuzSettings
#endif // QOBUZSETTINGS_H

View File

@@ -35,6 +35,6 @@ constexpr char kStripRemastered[] = "strip_remastered";
constexpr char kSources[] = "sources";
constexpr char kUserToken[] = "user_token";
} // namespace
} // namespace ScrobblerSettings
#endif // SCROBBLERSETTINGS_H

View File

@@ -38,6 +38,6 @@ constexpr char kRefreshToken[] = "refresh_token";
constexpr char kExpiresIn[] = "expires_in";
constexpr char kLoginTime[] = "login_time";
} // namespace
} // namespace SpotifySettings
#endif // SPOTIFYSETTINGS_H

View File

@@ -41,6 +41,6 @@ constexpr char kUseAlbumIdForAlbumCovers[] = "usealbumidforalbumcovers";
constexpr char kServerSideScrobbling[] = "serversidescrobbling";
constexpr char kAuthMethod[] = "authmethod";
} // namespace
} // namespace SubsonicSettings
#endif // SUBSONICETTINGS_H

View File

@@ -48,6 +48,6 @@ enum class StreamUrlMethod {
PlaybackInfoPostPaywall
};
}
} // namespace TidalSettings
#endif // TIDALSETTINGS_H

View File

@@ -26,4 +26,3 @@ constexpr char kSettingsGroup[] = "Transcoder";
}
#endif // TRANSCODERSETTINGS_H

View File

@@ -61,7 +61,6 @@ class ContextAlbum : public QWidget {
void contextMenuEvent(QContextMenuEvent *e) override;
private:
struct PreviousCover {
explicit PreviousCover() : opacity(0.0) {}
QImage image;

View File

@@ -59,6 +59,7 @@
#include "covermanager/albumcoverchoicecontroller.h"
#include "lyrics/lyricsfetcher.h"
#include "constants/contextsettings.h"
#include "constants/timeconstants.h"
#include "contextview.h"
#include "contextalbum.h"
@@ -353,7 +354,7 @@ void ContextView::SearchLyrics() {
if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && action_search_lyrics_->isChecked() && !song_playing_.artist().isEmpty() && !song_playing_.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) {
lyrics_fetcher_->Clear();
lyrics_tried_ = true;
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.artist(), song_playing_.album(), song_playing_.title()));
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.artist(), song_playing_.album(), song_playing_.title(), song_playing_.length_nanosec() / kNsecPerSec));
}
}

View File

@@ -65,9 +65,9 @@ class ContextView : public QWidget {
protected:
void resizeEvent(QResizeEvent *e) override;
void contextMenuEvent(QContextMenuEvent*) override;
void dragEnterEvent(QDragEnterEvent*) override;
void dropEvent(QDropEvent*) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void dragEnterEvent(QDragEnterEvent *e) override;
void dropEvent(QDropEvent *e) override;
private:
void AddActions();

View File

@@ -74,6 +74,7 @@
#include "lyrics/elyricsnetlyricsprovider.h"
#include "lyrics/letraslyricsprovider.h"
#include "lyrics/lyricfindlyricsprovider.h"
#include "lyrics/lrcliblyricsprovider.h"
#include "scrobbler/audioscrobbler.h"
#include "scrobbler/lastfmscrobbler.h"
@@ -104,6 +105,10 @@
# include "covermanager/qobuzcoverprovider.h"
#endif
#ifdef HAVE_DROPBOX
# include "dropbox/dropboxservice.h"
#endif
#ifdef HAVE_MOODBAR
# include "moodbar/moodbarcontroller.h"
# include "moodbar/moodbarloader.h"
@@ -117,8 +122,8 @@ using namespace std::chrono_literals;
class ApplicationImpl {
public:
explicit ApplicationImpl(Application *app) :
tagreader_client_([app](){
explicit ApplicationImpl(Application *app)
: tagreader_client_([app]() {
TagReaderClient *client = new TagReaderClient();
app->MoveToNewThread(client);
return client;
@@ -182,6 +187,7 @@ class ApplicationImpl {
lyrics_providers->AddProvider(new ElyricsNetLyricsProvider(lyrics_providers->network()));
lyrics_providers->AddProvider(new LetrasLyricsProvider(lyrics_providers->network()));
lyrics_providers->AddProvider(new LyricFindLyricsProvider(lyrics_providers->network()));
lyrics_providers->AddProvider(new LrcLibLyricsProvider(lyrics_providers->network()));
lyrics_providers->ReloadSettings();
return lyrics_providers;
}),
@@ -198,6 +204,9 @@ class ApplicationImpl {
#endif
#ifdef HAVE_QOBUZ
streaming_services->AddService(make_shared<QobuzService>(app->task_manager(), app->database(), app->network(), app->url_handlers(), app->albumcover_loader()));
#endif
#ifdef HAVE_DROPBOX
streaming_services->AddService(make_shared<DropboxService>(app->task_manager(), app->database(), app->network(), app->url_handlers(), app->tagreader_client(), app->albumcover_loader()));
#endif
return streaming_services;
}),

View File

@@ -61,8 +61,8 @@ constexpr char kMagicAllSongsTables[] = "%allsongstables";
int Database::sNextConnectionId = 1;
QMutex Database::sNextConnectionIdMutex;
Database::Database(SharedPtr<TaskManager> task_manager, QObject *parent, const QString &database_name) :
QObject(parent),
Database::Database(SharedPtr<TaskManager> task_manager, QObject *parent, const QString &database_name)
: QObject(parent),
task_manager_(task_manager),
injected_database_name_(database_name),
query_hash_(0),
@@ -503,7 +503,9 @@ bool Database::IntegrityCheck(const QSqlDatabase &db) {
break;
}
else {
if (!error_reported) { Q_EMIT Error(tr("Database corruption detected.")); }
if (!error_reported) {
Q_EMIT Error(tr("Database corruption detected."));
}
Q_EMIT Error(u"Database: "_s + message);
error_reported = true;
}
@@ -594,8 +596,7 @@ void Database::BackupFile(const QString &filename) {
ret = sqlite3_backup_step(backup, 16);
const int page_count = sqlite3_backup_pagecount(backup);
task_manager_->SetTaskProgress(task_id, static_cast<quint64>(page_count - sqlite3_backup_remaining(backup)), static_cast<quint64>(page_count));
}
while (ret == SQLITE_OK);
} while (ret == SQLITE_OK);
if (ret != SQLITE_DONE) {
qLog(Error) << "Database backup failed";

View File

@@ -128,7 +128,6 @@ class Database : public QObject {
int startup_schema_version_;
QThread *original_thread_;
};
#endif // DATABASE_H

View File

@@ -110,21 +110,32 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job, QString &error_te
bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
QString path = job.metadata_.url().toLocalFile();
QFileInfo fileInfo(path);
const QString path = job.metadata_.url().toLocalFile();
const QFileInfo fileInfo(path);
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
if (job.use_trash_ && QFile::supportsMoveToTrash()) {
#else
if (job.use_trash_) {
#endif
return QFile::moveToTrash(path);
if (QFile::moveToTrash(path)) {
return true;
}
qLog(Warning) << "Moving file to trash failed for" << path << ", falling back to direct deletion";
}
bool success = false;
if (fileInfo.isDir()) {
return Utilities::RemoveRecursive(path);
success = Utilities::RemoveRecursive(path);
}
else {
success = QFile::remove(path);
}
return QFile::remove(path);
if (!success) {
qLog(Error) << "Failed to delete file" << path;
}
return success;
}

View File

@@ -27,6 +27,7 @@ class IconLoader {
public:
static void Init();
static QIcon Load(const QString &name, const bool system_icon = true, const int fixed_size = 0, const int min_size = 0, const int max_size = 0);
private:
explicit IconLoader() {}
static bool system_icons_;

View File

@@ -155,11 +155,7 @@ void LocalRedirectServer::WriteTemplate() const {
QBuffer image_buffer;
if (image_buffer.open(QIODevice::ReadWrite)) {
QApplication::style()
->standardIcon(QStyle::SP_DialogOkButton)
.pixmap(16)
.toImage()
.save(&image_buffer, "PNG");
QApplication::style()->standardIcon(QStyle::SP_DialogOkButton).pixmap(16).toImage().save(&image_buffer, "PNG");
page_data.replace("@IMAGE_DATA@"_L1, QString::fromUtf8(image_buffer.data().toBase64()));
image_buffer.close();
}

View File

@@ -58,7 +58,6 @@
#include <QShortcut>
#include <QMessageBox>
#include <QErrorMessage>
#include <QSettings>
#include <QColor>
#include <QFrame>
#include <QItemSelectionModel>
@@ -179,6 +178,9 @@
#ifdef HAVE_QOBUZ
# include "constants/qobuzsettings.h"
#endif
#ifdef HAVE_DROPBOX
# include "constants/dropboxsettings.h"
#endif
#include "streaming/streamingservices.h"
#include "streaming/streamingservice.h"
@@ -296,9 +298,6 @@ MainWindow::MainWindow(Application *app,
#ifdef HAVE_DISCORD_RPC
discord_rich_presence_(discord_rich_presence),
#endif
error_dialog_([this]() {
return new ErrorDialog(this);
}),
console_([app, this]() {
Console *console = new Console(app->database());
QObject::connect(console, &Console::Error, this, &MainWindow::ShowErrorDialog);
@@ -359,6 +358,9 @@ MainWindow::MainWindow(Application *app,
#endif
#ifdef HAVE_QOBUZ
qobuz_view_(new StreamingTabsView(app->streaming_services()->ServiceBySource(Song::Source::Qobuz), app->albumcover_loader(), QLatin1String(QobuzSettings::kSettingsGroup), this)),
#endif
#ifdef HAVE_DROPBOX
dropbox_view_(new StreamingSongsView(app->streaming_services()->ServiceBySource(Song::Source::Dropbox), QLatin1String(DropboxSettings::kSettingsGroup), this)),
#endif
radio_view_(new RadioViewContainer(this)),
lastfm_import_dialog_(new LastFMImportDialog(app_->lastfm_import(), this)),
@@ -445,6 +447,9 @@ MainWindow::MainWindow(Application *app,
#ifdef HAVE_QOBUZ
ui_->tabs->AddTab(qobuz_view_, u"qobuz"_s, IconLoader::Load(u"qobuz"_s, true, 0, 32), tr("Qobuz"));
#endif
#ifdef HAVE_DROPBOX
ui_->tabs->AddTab(dropbox_view_, u"dropbox"_s, IconLoader::Load(u"dropbox"_s, true, 0, 32), tr("Dropbox"));
#endif
// Add the playing widget to the fancy tab widget
ui_->tabs->AddBottomWidget(ui_->widget_playing);
@@ -700,6 +705,9 @@ MainWindow::MainWindow(Application *app,
QObject::connect(&*app_->task_manager(), &TaskManager::PauseCollectionWatchers, &*app_->collection(), &CollectionLibrary::PauseWatcher);
QObject::connect(&*app_->task_manager(), &TaskManager::ResumeCollectionWatchers, &*app_->collection(), &CollectionLibrary::ResumeWatcher);
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, &*app_->collection(), &CollectionLibrary::CurrentSongChanged);
QObject::connect(&*app_->player(), &Player::Stopped, &*app_->collection(), &CollectionLibrary::Stopped);
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, &*app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::LoadAlbumCover);
QObject::connect(&*app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::AlbumCoverLoaded, this, &MainWindow::AlbumCoverLoaded);
QObject::connect(album_cover_choice_controller_, &AlbumCoverChoiceController::Error, this, &MainWindow::ShowErrorDialog);
@@ -783,6 +791,12 @@ MainWindow::MainWindow(Application *app,
}
#endif
#ifdef HAVE_DROPBOX
QObject::connect(dropbox_view_, &StreamingSongsView::ShowErrorDialog, this, &MainWindow::ShowErrorDialog);
QObject::connect(dropbox_view_, &StreamingSongsView::OpenSettingsDialog, this, &MainWindow::OpenServiceSettingsDialog);
QObject::connect(dropbox_view_->view(), &StreamingCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
#endif
QObject::connect(radio_view_, &RadioViewContainer::Refresh, &*app_->radio_services(), &RadioServices::RefreshChannels);
QObject::connect(radio_view_->view(), &RadioView::GetChannels, &*app_->radio_services(), &RadioServices::GetChannels);
QObject::connect(radio_view_->view(), &RadioView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
@@ -980,27 +994,28 @@ MainWindow::MainWindow(Application *app,
// Load settings
qLog(Debug) << "Loading settings";
settings_.beginGroup(MainWindowSettings::kSettingsGroup);
Settings settings;
settings.beginGroup(MainWindowSettings::kSettingsGroup);
// Set last used geometry to position window on the correct monitor
// Set window state only if the window was last maximized
if (settings_.contains("geometry")) {
restoreGeometry(settings_.value("geometry").toByteArray());
if (settings.contains("geometry")) {
restoreGeometry(settings.value("geometry").toByteArray());
}
if (!settings_.contains(MainWindowSettings::kSplitterState) || !ui_->splitter->restoreState(settings_.value(MainWindowSettings::kSplitterState).toByteArray())) {
if (!settings.contains(MainWindowSettings::kSplitterState) || !ui_->splitter->restoreState(settings.value(MainWindowSettings::kSplitterState).toByteArray())) {
ui_->splitter->setSizes(QList<int>() << 20 << (width() - 20));
}
ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1).toInt());
ui_->tabs->setCurrentIndex(settings.value("current_tab", 1).toInt());
FancyTabWidget::Mode default_mode = FancyTabWidget::Mode::LargeSidebar;
FancyTabWidget::Mode tab_mode = static_cast<FancyTabWidget::Mode>(settings_.value("tab_mode", static_cast<int>(default_mode)).toInt());
FancyTabWidget::Mode tab_mode = static_cast<FancyTabWidget::Mode>(settings.value("tab_mode", static_cast<int>(default_mode)).toInt());
if (tab_mode == FancyTabWidget::Mode::None) tab_mode = default_mode;
ui_->tabs->SetMode(tab_mode);
TabSwitched();
file_view_->SetPath(settings_.value("file_path", QDir::homePath()).toString());
file_view_->SetPath(settings.value("file_path", QDir::homePath()).toString());
// Users often collapse one side of the splitter by mistake and don't know how to restore it. This must be set after the state is restored above.
ui_->splitter->setChildrenCollapsible(false);
@@ -1043,13 +1058,13 @@ MainWindow::MainWindow(Application *app,
case BehaviourSettings::StartupBehaviour::Remember:
default:{
was_maximized_ = settings_.value(MainWindowSettings::kMaximized, true).toBool();
was_maximized_ = settings.value(MainWindowSettings::kMaximized, true).toBool();
if (was_maximized_) setWindowState(windowState() | Qt::WindowMaximized);
was_minimized_ = settings_.value(MainWindowSettings::kMinimized, false).toBool();
was_minimized_ = settings.value(MainWindowSettings::kMinimized, false).toBool();
if (was_minimized_) setWindowState(windowState() | Qt::WindowMinimized);
if (!systemtrayicon_->IsSystemTrayAvailable() || !systemtrayicon_->isVisible() || !settings_.value(MainWindowSettings::kHidden, false).toBool()) {
if (!systemtrayicon_->IsSystemTrayAvailable() || !systemtrayicon_->isVisible() || !settings.value(MainWindowSettings::kHidden, false).toBool()) {
show();
}
break;
@@ -1057,7 +1072,7 @@ MainWindow::MainWindow(Application *app,
}
#endif
bool show_sidebar = settings_.value(MainWindowSettings::kShowSidebar, true).toBool();
bool show_sidebar = settings.value(MainWindowSettings::kShowSidebar, true).toBool();
ui_->sidebar_layout->setVisible(show_sidebar);
ui_->action_toggle_show_sidebar->setChecked(show_sidebar);
@@ -1228,7 +1243,9 @@ void MainWindow::ReloadSettings() {
osd_->ReloadSettings();
album_cover_choice_controller_->search_cover_auto_action()->setChecked(settings_.value(MainWindowSettings::kSearchForCoverAuto, true).toBool());
s.beginGroup(MainWindowSettings::kSettingsGroup);
album_cover_choice_controller_->search_cover_auto_action()->setChecked(s.value(MainWindowSettings::kSearchForCoverAuto, true).toBool());
s.endGroup();
#ifdef HAVE_SUBSONIC
s.beginGroup(SubsonicSettings::kSettingsGroup);
@@ -1278,6 +1295,18 @@ void MainWindow::ReloadSettings() {
}
#endif
#ifdef HAVE_DROPBOX
s.beginGroup(DropboxSettings::kSettingsGroup);
const bool enable_dropbox = s.value(DropboxSettings::kEnabled, false).toBool();
s.endGroup();
if (enable_dropbox) {
ui_->tabs->EnableTab(dropbox_view_);
}
else {
ui_->tabs->DisableTab(dropbox_view_);
}
#endif
ui_->tabs->ReloadSettings();
}
@@ -1324,10 +1353,12 @@ void MainWindow::ReloadAllSettings() {
qobuz_view_->ReloadSettings();
qobuz_view_->search_view()->ReloadSettings();
#endif
#ifdef HAVE_DROPBOX
dropbox_view_->ReloadSettings();
#endif
#ifdef HAVE_DISCORD_RPC
discord_rich_presence_->ReloadSettings();
#endif
}
void MainWindow::RefreshStyleSheet() {
@@ -1345,8 +1376,11 @@ void MainWindow::SaveSettings() {
ui_->playlist->view()->SaveSettings();
app_->scrobbler()->WriteCache();
settings_.setValue(MainWindowSettings::kShowSidebar, ui_->action_toggle_show_sidebar->isChecked());
settings_.setValue(MainWindowSettings::kSearchForCoverAuto, album_cover_choice_controller_->search_cover_auto_action()->isChecked());
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
s.setValue(MainWindowSettings::kShowSidebar, ui_->action_toggle_show_sidebar->isChecked());
s.setValue(MainWindowSettings::kSearchForCoverAuto, album_cover_choice_controller_->search_cover_auto_action()->isChecked());
s.endGroup();
}
@@ -1587,23 +1621,35 @@ void MainWindow::ToggleSidebar(const bool checked) {
ui_->sidebar_layout->setVisible(checked);
TabSwitched();
settings_.setValue(MainWindowSettings::kShowSidebar, checked);
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
s.setValue(MainWindowSettings::kShowSidebar, checked);
s.endGroup();
}
void MainWindow::ToggleSearchCoverAuto(const bool checked) {
settings_.setValue(MainWindowSettings::kSearchForCoverAuto, checked);
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
s.setValue(MainWindowSettings::kSearchForCoverAuto, checked);
s.endGroup();
}
void MainWindow::SaveGeometry() {
if (!initialized_) return;
settings_.setValue(MainWindowSettings::kMaximized, isMaximized());
settings_.setValue(MainWindowSettings::kMinimized, isMinimized());
settings_.setValue(MainWindowSettings::kHidden, isHidden());
settings_.setValue(MainWindowSettings::kGeometry, saveGeometry());
settings_.setValue(MainWindowSettings::kSplitterState, ui_->splitter->saveState());
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
s.setValue(MainWindowSettings::kMaximized, isMaximized());
s.setValue(MainWindowSettings::kMinimized, isMinimized());
s.setValue(MainWindowSettings::kHidden, isHidden());
s.setValue(MainWindowSettings::kGeometry, saveGeometry());
s.setValue(MainWindowSettings::kSplitterState, ui_->splitter->saveState());
s.endGroup();
}
@@ -1745,7 +1791,12 @@ void MainWindow::SetHiddenInTray(const bool hidden) {
}
void MainWindow::FilePathChanged(const QString &path) {
settings_.setValue("file_path", path);
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
s.setValue("file_path", path);
s.endGroup();
}
void MainWindow::Seeked(const qint64 microseconds) {
@@ -2327,7 +2378,9 @@ void MainWindow::EditValue() {
void MainWindow::AddFile() {
// Last used directory
QString directory = settings_.value("add_media_path", QDir::currentPath()).toString();
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
QString directory = s.value("add_media_path", QDir::currentPath()).toString();
PlaylistParser parser(app_->tagreader_client(), app_->collection_backend());
@@ -2337,7 +2390,7 @@ void MainWindow::AddFile() {
if (filenames.isEmpty()) return;
// Save last used directory
settings_.setValue("add_media_path", filenames[0]);
s.setValue("add_media_path", filenames[0]);
// Convert to URLs
QList<QUrl> urls;
@@ -2355,14 +2408,16 @@ void MainWindow::AddFile() {
void MainWindow::AddFolder() {
// Last used directory
QString directory = settings_.value("add_folder_path", QDir::currentPath()).toString();
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
QString directory = s.value("add_folder_path", QDir::currentPath()).toString();
// Show dialog
directory = QFileDialog::getExistingDirectory(this, tr("Add folder"), directory);
if (directory.isEmpty()) return;
// Save last used directory
settings_.setValue("add_folder_path", directory);
s.setValue("add_folder_path", directory);
// Add media
MimeData *mimedata = new MimeData;
@@ -2691,6 +2746,9 @@ void MainWindow::OpenServiceSettingsDialog(const Song::Source source) {
case Song::Source::Spotify:
settings_dialog_->OpenAtPage(SettingsDialog::Page::Spotify);
break;
case Song::Source::Dropbox:
settings_dialog_->OpenAtPage(SettingsDialog::Page::Dropbox);
break;
default:
break;
}
@@ -3321,7 +3379,7 @@ void MainWindow::PlaylistDelete() {
if (DeleteConfirmationDialog::warning(files) != QDialogButtonBox::Yes) return;
if (app_->player()->GetState() == EngineBase::State::Playing && app_->playlist_manager()->current()->rowCount() == selected_songs.count()) {
if (app_->player()->GetState() == EngineBase::State::Playing && app_->playlist_manager()->current() == app_->playlist_manager()->active() && app_->playlist_manager()->current()->rowCount() == selected_songs.count()) {
app_->player()->Stop();
}
@@ -3372,6 +3430,11 @@ void MainWindow::FocusSearchField() {
else if (ui_->tabs->currentIndex() == ui_->tabs->IndexOfTab(qobuz_view_) && !qobuz_view_->SearchFieldHasFocus()) {
qobuz_view_->FocusSearchField();
}
#endif
#ifdef HAVE_DROPBOX
else if (ui_->tabs->currentIndex() == ui_->tabs->IndexOfTab(dropbox_view_) && !dropbox_view_->SearchFieldHasFocus()) {
dropbox_view_->FocusSearchField();
}
#endif
else if (!ui_->playlist->SearchFieldHasFocus()) {
ui_->playlist->FocusSearchField();

View File

@@ -45,7 +45,6 @@
#include <QImage>
#include <QPixmap>
#include <QTimer>
#include <QSettings>
#include <QtEvents>
#include "includes/scoped_ptr.h"
@@ -53,7 +52,6 @@
#include "includes/lazy.h"
#include "core/platforminterface.h"
#include "core/song.h"
#include "core/settings.h"
#include "core/commandlineoptions.h"
#include "tagreader/tagreaderclient.h"
#include "osd/osdbase.h"
@@ -285,7 +283,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void Raise();
private:
void SaveSettings();
static void ApplyAddBehaviour(const BehaviourSettings::AddBehaviour b, MimeData *mimedata);
@@ -358,6 +355,9 @@ class MainWindow : public QMainWindow, public PlatformInterface {
#ifdef HAVE_QOBUZ
StreamingTabsView *qobuz_view_;
#endif
#ifdef HAVE_DROPBOX
StreamingSongsView *dropbox_view_;
#endif
RadioViewContainer *radio_view_;
@@ -391,7 +391,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
QTimer *track_position_timer_;
QTimer *track_slider_timer_;
Settings settings_;
bool keep_running_;
bool playing_widget_;
@@ -415,7 +414,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
bool playlists_loaded_;
bool delete_files_;
std::optional<CommandlineOptions> options_;
};
#endif // MAINWINDOW_H

View File

@@ -29,6 +29,7 @@
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkInformation>
#include "networkaccessmanager.h"
#include "threadsafenetworkdiskcache.h"
@@ -41,6 +42,22 @@ NetworkAccessManager::NetworkAccessManager(QObject *parent)
setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
setCache(new ThreadSafeNetworkDiskCache(this));
// Handle network state changes after system suspend/resume
// QNetworkInformation provides cross-platform network reachability monitoring in Qt 6
if (QNetworkInformation::loadDefaultBackend()) {
QNetworkInformation *network_info = QNetworkInformation::instance();
if (network_info) {
QObject::connect(network_info, &QNetworkInformation::reachabilityChanged, this, [this](QNetworkInformation::Reachability reachability) {
if (reachability == QNetworkInformation::Reachability::Online) {
// Clear connection cache to force reconnection after network becomes available
// This fixes issues after system suspend/resume
clearConnectionCache();
clearAccessCache();
}
});
}
}
}
QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkRequest &network_request, QIODevice *outgoing_data) {
@@ -50,7 +67,7 @@ QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkR
user_agent = network_request.header(QNetworkRequest::UserAgentHeader).toByteArray();
}
else {
user_agent = QStringLiteral("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8();
user_agent = "Strawberry Music Player";
}
QNetworkRequest new_network_request(network_request);

View File

@@ -121,8 +121,8 @@ class Player : public PlayerInterface {
void PlayPlaylistInternal(const EngineBase::TrackChangeFlags, const Playlist::AutoScroll autoscroll, const QString &playlist_name);
void FatalError();
void ValidSongRequested(const QUrl&);
void InvalidSongRequested(const QUrl&);
void ValidSongRequested(const QUrl &url);
void InvalidSongRequested(const QUrl &url);
void HandleLoadResult(const UrlHandler::LoadResult &result);

View File

@@ -40,7 +40,6 @@ class QtFSListener : public FileSystemWatcherInterface {
private:
QFileSystemWatcher watcher_;
};
#endif // QTFSLISTENER_H

View File

@@ -28,6 +28,7 @@ class ScopedNSAutoreleasePool {
// Only use then when you're certain the items currently in the pool are
// no longer needed.
void Recycle();
private:
NSAutoreleasePool *autorelease_pool_;

View File

@@ -353,6 +353,8 @@ struct Song::Private : public QSharedData {
std::optional<double> ebur128_integrated_loudness_lufs_;
std::optional<double> ebur128_loudness_range_lu_;
int id3v2_version_; // ID3v2 tag version (3 or 4), 0 if not applicable or unknown
bool init_from_file_; // Whether this song was loaded from a file using taglib.
bool suspicious_tags_; // Whether our encoding guesser thinks these tags might be incorrectly encoded.
@@ -400,6 +402,8 @@ Song::Private::Private(const Source source)
rating_(-1),
bpm_(-1),
id3v2_version_(0),
init_from_file_(false),
suspicious_tags_(false)
@@ -510,6 +514,8 @@ const QString &Song::musicbrainz_work_id() const { return d->musicbrainz_work_id
std::optional<double> Song::ebur128_integrated_loudness_lufs() const { return d->ebur128_integrated_loudness_lufs_; }
std::optional<double> Song::ebur128_loudness_range_lu() const { return d->ebur128_loudness_range_lu_; }
int Song::id3v2_version() const { return d->id3v2_version_; }
QString *Song::mutable_title() { return &d->title_; }
QString *Song::mutable_album() { return &d->album_; }
QString *Song::mutable_artist() { return &d->artist_; }
@@ -624,6 +630,8 @@ void Song::set_musicbrainz_work_id(const QString &v) { d->musicbrainz_work_id_ =
void Song::set_ebur128_integrated_loudness_lufs(const std::optional<double> v) { d->ebur128_integrated_loudness_lufs_ = v; }
void Song::set_ebur128_loudness_range_lu(const std::optional<double> v) { d->ebur128_loudness_range_lu_ = v; }
void Song::set_id3v2_version(const int v) { d->id3v2_version_ = v; }
void Song::set_init_from_file(const bool v) { d->init_from_file_ = v; }
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
@@ -797,19 +805,19 @@ bool Song::lyrics_supported() const {
}
bool Song::albumartistsort_supported() const {
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::OggOpus || d->filetype_ == FileType::MPEG;
}
bool Song::albumsort_supported() const {
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::OggOpus || d->filetype_ == FileType::MPEG;
}
bool Song::artistsort_supported() const {
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::OggOpus || d->filetype_ == FileType::MPEG;
}
bool Song::composersort_supported() const {
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::OggOpus || d->filetype_ == FileType::MPEG;
}
bool Song::performersort_supported() const {
@@ -818,7 +826,7 @@ bool Song::performersort_supported() const {
}
bool Song::titlesort_supported() const {
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::OggOpus || d->filetype_ == FileType::MPEG;
}
bool Song::save_embedded_cover_supported(const FileType filetype) {
@@ -833,6 +841,10 @@ bool Song::save_embedded_cover_supported(const FileType filetype) {
}
bool Song::id3v2_tags_supported() const {
return d->filetype_ == FileType::MPEG || d->filetype_ == FileType::WAV || d->filetype_ == FileType::AIFF;
}
int Song::ColumnIndex(const QString &field) {
return static_cast<int>(kRowIdColumns.indexOf(field));
@@ -1151,6 +1163,8 @@ QString Song::TextForSource(const Source source) {
case Source::Qobuz: return u"qobuz"_s;
case Source::SomaFM: return u"somafm"_s;
case Source::RadioParadise: return u"radioparadise"_s;
case Source::Dropbox: return u"dropbox"_s;
case Source::OneDrive: return u"onedrive"_s;
case Source::Unknown: return u"unknown"_s;
}
return u"unknown"_s;
@@ -1171,6 +1185,8 @@ QString Song::DescriptionForSource(const Source source) {
case Source::Qobuz: return u"Qobuz"_s;
case Source::SomaFM: return u"SomaFM"_s;
case Source::RadioParadise: return u"Radio Paradise"_s;
case Source::Dropbox: return u"Dropbox"_s;
case Source::OneDrive: return u"OneDrive"_s;
case Source::Unknown: return u"Unknown"_s;
}
return u"unknown"_s;
@@ -1190,6 +1206,8 @@ Song::Source Song::SourceFromText(const QString &source) {
if (source.compare("qobuz"_L1, Qt::CaseInsensitive) == 0) return Source::Qobuz;
if (source.compare("somafm"_L1, Qt::CaseInsensitive) == 0) return Source::SomaFM;
if (source.compare("radioparadise"_L1, Qt::CaseInsensitive) == 0) return Source::RadioParadise;
if (source.compare("dropbox"_L1, Qt::CaseInsensitive) == 0) return Source::Dropbox;
if (source.compare("onedrive"_L1, Qt::CaseInsensitive) == 0) return Source::OneDrive;
return Source::Unknown;
@@ -1209,6 +1227,8 @@ QIcon Song::IconForSource(const Source source) {
case Source::Qobuz: return IconLoader::Load(u"qobuz"_s);
case Source::SomaFM: return IconLoader::Load(u"somafm"_s);
case Source::RadioParadise: return IconLoader::Load(u"radioparadise"_s);
case Source::Dropbox: return IconLoader::Load(u"dropbox"_s);
case Source::OneDrive: return IconLoader::Load(u"onedrive"_s);
case Source::Unknown: return IconLoader::Load(u"edit-delete"_s);
}
return IconLoader::Load(u"edit-delete"_s);
@@ -1458,7 +1478,7 @@ Song::FileType Song::FiletypeByExtension(const QString &ext) {
bool Song::IsLinkedCollectionSource(const Source source) {
return source == Source::Collection;
return source == Source::Collection || source == Source::Dropbox;
}
@@ -1477,11 +1497,14 @@ QString Song::ImageCacheDir(const Source source) {
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/qobuzalbumcovers"_s;
case Source::Device:
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/devicealbumcovers"_s;
case Source::Dropbox:
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/dropboxalbumcovers"_s;
case Source::LocalFile:
case Source::CDDA:
case Source::Stream:
case Source::SomaFM:
case Source::RadioParadise:
case Source::OneDrive:
case Source::Unknown:
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/albumcovers"_s;
}
@@ -2150,4 +2173,3 @@ QString Song::GetNameForNewPlaylist(const SongList &songs) {
return result;
}

View File

@@ -76,7 +76,9 @@ class Song {
Qobuz = 8,
SomaFM = 9,
RadioParadise = 10,
Spotify = 11
Spotify = 11,
Dropbox = 12,
OneDrive = 13,
};
static const int kSourceCount = 16;
@@ -234,6 +236,8 @@ class Song {
std::optional<double> ebur128_integrated_loudness_lufs() const;
std::optional<double> ebur128_loudness_range_lu() const;
int id3v2_version() const;
QString *mutable_title();
QString *mutable_album();
QString *mutable_artist();
@@ -349,6 +353,8 @@ class Song {
void set_ebur128_integrated_loudness_lufs(const std::optional<double> v);
void set_ebur128_loudness_range_lu(const std::optional<double> v);
void set_id3v2_version(const int v);
void set_init_from_file(const bool v);
void set_stream_url(const QUrl &v);
@@ -439,6 +445,8 @@ class Song {
static bool save_embedded_cover_supported(const FileType filetype);
bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); };
bool id3v2_tags_supported() const;
static int ColumnIndex(const QString &field);
static QString JoinSpec(const QString &table);

View File

@@ -98,6 +98,9 @@ SongLoader::SongLoader(const SharedPtr<UrlHandlers> url_handlers,
QObject::connect(timeout_timer_, &QTimer::timeout, this, &SongLoader::Timeout);
QObject::connect(playlist_parser_, &PlaylistParser::Error, this, &SongLoader::ParserError);
QObject::connect(cue_parser_, &CueParser::Error, this, &SongLoader::ParserError);
}
SongLoader::~SongLoader() {
@@ -106,6 +109,10 @@ SongLoader::~SongLoader() {
}
void SongLoader::ParserError(const QString &error) {
errors_ << error;
}
SongLoader::Result SongLoader::Load(const QUrl &url) {
if (url.isEmpty()) return Result::Error;
@@ -287,6 +294,7 @@ SongLoader::Result SongLoader::LoadLocalAsync(const QString &filename) {
}
if (parser) { // It's a playlist!
QObject::connect(parser, &ParserBase::Error, this, &SongLoader::ParserError, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
qLog(Debug) << "Parsing using" << parser->name();
LoadPlaylist(parser, filename);
return Result::Success;
@@ -706,6 +714,10 @@ void SongLoader::MagicReady() {
StopTypefindAsync(true);
}
if (parser_) {
QObject::connect(parser_, &ParserBase::Error, this, &SongLoader::ParserError, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
}
state_ = State::WaitingForData;
if (!IsPipelinePlaying()) {
@@ -784,4 +796,3 @@ void SongLoader::CleanupPipeline() {
state_ = State::Finished;
}

View File

@@ -99,6 +99,7 @@ class SongLoader : public QObject {
void ScheduleTimeout();
void Timeout();
void StopTypefind();
void ParserError(const QString &error);
#ifdef HAVE_AUDIOCD
void AudioCDTracksLoadErrorSlot(const QString &error);
@@ -171,7 +172,6 @@ class SongLoader : public QObject {
QStringList errors_;
bool success_;
};
#endif // SONGLOADER_H

View File

@@ -41,4 +41,3 @@ class SongMimeData : public MimeData {
};
#endif // SONGMIMEDATA_H

View File

@@ -53,4 +53,3 @@ UrlHandler::LoadResult::LoadResult(const QUrl &media_url, const Type type, const
bit_depth_(-1),
length_nanosec_(-1),
error_(error) {}

View File

@@ -89,7 +89,6 @@ class UrlHandler : public QObject {
Q_SIGNALS:
void AsyncLoadComplete(const UrlHandler::LoadResult &result);
};
#endif // URLHANDLER_H

View File

@@ -589,6 +589,8 @@ void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art
case Song::Source::Tidal:
case Song::Source::Spotify:
case Song::Source::Qobuz:
case Song::Source::Dropbox:
case Song::Source::OneDrive:
StreamingServicePtr service = streaming_services_->ServiceBySource(song->source());
if (!service) break;
if (service->artists_collection_backend()) {
@@ -812,8 +814,8 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
SaveCoverEmbeddedToCollectionSongs(*song, result);
break;
}
}
[[fallthrough]];
}
case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:{
cover_url = SaveCoverToFileAutomatic(song, result);

View File

@@ -75,4 +75,3 @@ class AlbumCoverExporter : public QObject {
};
#endif // ALBUMCOVEREXPORTER_H

View File

@@ -113,7 +113,6 @@ class AlbumCoverFetcherSearch : public QObject {
SharedPtr<NetworkAccessManager> network_;
bool cancel_requested_;
};
#endif // ALBUMCOVERFETCHERSEARCH_H

View File

@@ -58,5 +58,4 @@ AlbumCoverLoaderOptions::Types AlbumCoverLoaderOptions::LoadTypes() {
s.endGroup();
return cover_types;
}

View File

@@ -44,8 +44,8 @@ class AlbumCoverLoaderResult {
const AlbumCoverImageResult &_album_cover = AlbumCoverImageResult(),
const QImage &_image_scaled = QImage(),
const QUrl &_art_manual_updated = QUrl(),
const QUrl &_art_automatic_updated = QUrl()) :
success(_success),
const QUrl &_art_automatic_updated = QUrl())
: success(_success),
type(_type),
album_cover(_album_cover),
image_scaled(_image_scaled),

View File

@@ -102,7 +102,7 @@ using std::make_shared;
namespace {
constexpr char kSettingsGroup[] = "CoverManager";
constexpr int kThumbnailSize = 120;
}
} // namespace
AlbumCoverManager::AlbumCoverManager(const SharedPtr<NetworkAccessManager> network,
const SharedPtr<CollectionBackend> collection_backend,
@@ -604,10 +604,7 @@ void AlbumCoverManager::AlbumCoverFetched(const quint64 id, const AlbumCoverImag
void AlbumCoverManager::UpdateStatusText() {
QString message = tr("Got %1 covers out of %2 (%3 failed)")
.arg(fetch_statistics_.chosen_images_)
.arg(jobs_)
.arg(fetch_statistics_.missing_images_);
QString message = tr("Got %1 covers out of %2 (%3 failed)").arg(fetch_statistics_.chosen_images_).arg(jobs_).arg(fetch_statistics_.missing_images_);
if (fetch_statistics_.bytes_transferred_ > 0) {
message += ", "_L1 + tr("%1 transferred").arg(Utilities::PrettySize(fetch_statistics_.bytes_transferred_));
@@ -1083,10 +1080,7 @@ void AlbumCoverManager::UpdateExportStatus(const int exported, const int skipped
progress_bar_->setValue(exported);
QString message = tr("Exported %1 covers out of %2 (%3 skipped)")
.arg(exported)
.arg(max)
.arg(skipped);
QString message = tr("Exported %1 covers out of %2 (%3 skipped)").arg(exported).arg(max).arg(skipped);
statusBar()->showMessage(message);
// End of the current process
@@ -1131,4 +1125,3 @@ void AlbumCoverManager::SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, Album
LoadAlbumCoverAsync(album_item);
}

View File

@@ -83,7 +83,7 @@ class AlbumCoverSearcher : public QDialog {
AlbumCoverImageResult Exec(const QString &artist, const QString &album);
protected:
void keyPressEvent(QKeyEvent*) override;
void keyPressEvent(QKeyEvent *e) override;
private Q_SLOTS:
void Search();

View File

@@ -45,8 +45,7 @@ CoverSearchStatisticsDialog::CoverSearchStatisticsDialog(QWidget *parent)
details_layout_ = new QVBoxLayout(ui_->details);
details_layout_->setSpacing(0);
setStyleSheet(
u"#details {"
setStyleSheet(u"#details {"
" background-color: palette(base);"
"}"
"#details QLabel[type=\"label\"] {"
@@ -67,10 +66,7 @@ void CoverSearchStatisticsDialog::Show(const CoverSearchStatistics &statistics)
QStringList providers(statistics.total_images_by_provider_.keys());
std::sort(providers.begin(), providers.end());
ui_->summary->setText(tr("Got %1 covers out of %2 (%3 failed)")
.arg(statistics.chosen_images_)
.arg(statistics.chosen_images_ + statistics.missing_images_)
.arg(statistics.missing_images_));
ui_->summary->setText(tr("Got %1 covers out of %2 (%3 failed)").arg(statistics.chosen_images_).arg(statistics.chosen_images_ + statistics.missing_images_).arg(statistics.missing_images_));
for (const QString &provider : std::as_const(providers)) {
AddLine(tr("Covers from %1").arg(provider), QString::number(statistics.chosen_images_by_provider_[provider]));

View File

@@ -138,4 +138,3 @@ void CDDADevice::SongLoadingFinished() {
WatchForDiscChanges(true);
}

View File

@@ -159,3 +159,11 @@ bool CDDALister::Init() {
return true;
}
bool CDDALister::AskForScan(const QString &id) const {
Q_UNUSED(id)
return false;
}

View File

@@ -47,11 +47,11 @@ class CDDALister : public DeviceLister {
quint64 DeviceCapacity(const QString &id) override;
quint64 DeviceFreeSpace(const QString &id) override;
QVariantMap DeviceHardwareInfo(const QString &id) override;
bool AskForScan(const QString&) const override { return false; }
QString MakeFriendlyName(const QString&) override;
QList<QUrl> MakeDeviceUrls(const QString&) override;
void UnmountDevice(const QString&) override;
void UpdateDeviceFreeSpace(const QString&) override;
bool AskForScan(const QString &id) const override;
QString MakeFriendlyName(const QString &id) override;
QList<QUrl> MakeDeviceUrls(const QString &id) override;
void UnmountDevice(const QString &id) override;
void UpdateDeviceFreeSpace(const QString &id) override;
bool Init() override;
bool CopyMusic() override { return false; }

View File

@@ -227,8 +227,7 @@ void DeviceDatabaseBackend::SetDeviceOptions(const int id, const QString &friend
QSqlDatabase db(db_->Connect());
SqlQuery q(db);
q.prepare(QStringLiteral(
"UPDATE devices"
q.prepare(QStringLiteral("UPDATE devices"
" SET friendly_name=:friendly_name,"
" icon=:icon_name,"
" transcode_mode=:transcode_mode,"

View File

@@ -77,7 +77,6 @@ class DeviceDatabaseBackend : public QObject {
private:
SharedPtr<Database> db_;
QThread *original_thread_;
};
#endif // DEVICEDATABASEBACKEND_H

View File

@@ -61,7 +61,6 @@ class DeviceItemDelegate : public CollectionItemDelegate {
static const int kIconPadding;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const override;
};
class DeviceView : public AutoExpandingTreeView {
@@ -80,7 +79,7 @@ class DeviceView : public AutoExpandingTreeView {
bool CanRecursivelyExpand(const QModelIndex &idx) const override;
protected:
void contextMenuEvent(QContextMenuEvent*) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void mouseDoubleClickEvent(QMouseEvent *e) override;
private Q_SLOTS:

View File

@@ -160,7 +160,10 @@ QVariantList GioLister::DeviceIcons(const QString &id) {
}
QString GioLister::DeviceManufacturer(const QString &id) { Q_UNUSED(id); return QString(); }
QString GioLister::DeviceManufacturer(const QString &id) {
Q_UNUSED(id);
return QString();
}
QString GioLister::DeviceModel(const QString &id) {

View File

@@ -119,8 +119,8 @@ class GioLister : public DeviceLister {
static void VolumeAddedCallback(GVolumeMonitor *volume_monitor, GVolume *volume, gpointer instance);
static void VolumeRemovedCallback(GVolumeMonitor *volume_monitor, GVolume *volume, gpointer instance);
static void MountAddedCallback(GVolumeMonitor *volume_monitor, GMount*, gpointer instance);
static void MountChangedCallback(GVolumeMonitor *volume_monitor, GMount*, gpointer instance);
static void MountAddedCallback(GVolumeMonitor *volume_monitor, GMount *mount, gpointer instance);
static void MountChangedCallback(GVolumeMonitor *volume_monitor, GMount *mount, gpointer instance);
static void MountRemovedCallback(GVolumeMonitor *volume_monitor, GMount *mount, gpointer instance);
static void VolumeMountFinished(GObject *object, GAsyncResult *result, gpointer instance);

View File

@@ -64,7 +64,6 @@ class MtpLoader : public QObject {
ScopedPtr<MtpConnection> connection_;
QThread *original_thread_;
bool abort_;
};
#endif // MTPLOADER_H

View File

@@ -411,10 +411,7 @@ Udisks2Lister::PartitionData Udisks2Lister::ReadPartitionData(const QDBusObjectP
}
QString Udisks2Lister::PartitionData::unique_id() const {
return u"Udisks2/%1/%2/%3/%4/%5"_s
.arg(serial, vendor, model)
.arg(capacity)
.arg(uuid);
return u"Udisks2/%1/%2/%3/%4/%5"_s.arg(serial, vendor, model).arg(capacity).arg(uuid);
}
Udisks2Lister::Udisks2Job::Udisks2Job() : is_mount(true) {}

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