Compare commits

..

114 Commits

Author SHA1 Message Date
Jonas Kvinge
784c86aa80 Add network remote WIP 2025-12-29 00:41:07 +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
Jonas Kvinge
d2205cfe81 Release 1.2.15 2025-11-25 02:50:34 +01:00
Jonas Kvinge
5830f247f6 Update Changelog 2025-11-25 02:05:26 +01:00
Jonas Kvinge
8b4c57d933 GroupedIconView: Remove deprecated QStyle::State_Editing 2025-11-23 03:14:57 +01:00
Jonas Kvinge
67cec09176 gitignore: Add .qtcreator 2025-11-23 03:14:24 +01:00
Jonas Kvinge
2df658e1f3 ListenBrainzScrobbler: Ignore connection closed 2025-11-23 01:11:59 +01:00
Jonas Kvinge
f3bc9b151c README: Update OBS URL's 2025-11-23 00:47:56 +01:00
Jonas Kvinge
b06b59d0c5 nsi: Bump ffmpeg for msvc x86_64 2025-11-22 14:20:40 +01:00
Jonas Kvinge
99d75ade06 CI: Bump MSVC SDK version 2025-11-22 02:15:20 +01:00
Jonas Kvinge
3f63246068 Add macos-15-intel runner 2025-11-22 00:47:30 +01:00
dependabot[bot]
b205a5f964 Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 19:46:25 +01:00
dependabot[bot]
aeaef12dd4 Bump vmactions/freebsd-vm from 1.2.6 to 1.2.7
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.2.6 to 1.2.7.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.2.6...v1.2.7)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-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-11-21 19:45:43 +01:00
Jonas Kvinge
02d76f17f7 CollectionModel: Make SortText static 2025-11-15 22:10:55 +01:00
Strawberry Bot
e4e12c6fa6 New translations 2025-11-13 23:25:27 +01:00
uninstall-your-browser
270ae6085b FilterParser: Convert number to nanoseconds for length filter 2025-11-13 23:23:19 +01:00
Jonas Kvinge
7065a405a5 CI: Remove macos-13 2025-11-12 01:10:19 +01:00
Jonas Kvinge
d8c72c3dd9 ParserBase: Remove use of QString::removeFirst 2025-11-11 21:37:19 +01:00
Jonas Kvinge
b65502e167 XSPFParser: Handle platform and url 2025-11-11 00:56:36 +01:00
Jonas Kvinge
132f8df853 ParserBase: Convert spotify URLs 2025-11-11 00:56:13 +01:00
Jonas Kvinge
12e3cffe63 CI: Fix ssh command 2025-11-10 20:43:09 +01:00
dependabot[bot]
56a637682d Bump vmactions/freebsd-vm from 1.2.5 to 1.2.6
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.2.5...v1.2.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-10 20:01:38 +01:00
dependabot[bot]
d9b105f89e Bump vmactions/openbsd-vm from 1.2.2 to 1.2.3
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.2.2 to 1.2.3.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.2.2...v1.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-10 17:26:57 +01:00
dependabot[bot]
bd6b45e43f Bump vmactions/freebsd-vm from 1.2.4 to 1.2.5
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.2.4 to 1.2.5.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.2.4...v1.2.5)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-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-11-03 18:54:16 +01:00
dependabot[bot]
539172fb70 Bump vmactions/openbsd-vm from 1.2.1 to 1.2.2
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.2.1...v1.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 18:11:33 +01:00
Jonas Kvinge
ebd92b3a7f nsi: Bump ffmpeg 2025-11-01 12:22:43 +01:00
Jonas Kvinge
b00ae5b210 nsi: Bump icu 2025-10-31 23:47:49 +01:00
Jonas Kvinge
c8e3cf981b main: Try different language codes for QtSparkle 2025-10-31 23:06:00 +01:00
Jonas Kvinge
038f69779f CI: Manually codesign libbrotli 2025-10-30 23:56:17 +01:00
Jonas Kvinge
a4de7559ac Fix loading language from system UI languages
Fixes #1847
2025-10-30 23:54:42 +01:00
dependabot[bot]
0537b072fe Bump actions/download-artifact from 5 to 6
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-30 01:36:11 +01:00
dependabot[bot]
2657c9f96a Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-30 01:35:56 +01:00
Jonas Kvinge
7e178b1f1a PlaylistView: Always keep EditKeyPressed 2025-10-30 01:35:30 +01:00
Jonas Kvinge
dd8513d02c PlaylistView: Disable EditKeyPressed when inline metadata editing is disabled 2025-10-27 20:19:50 +01:00
Andrew Tribick
5f0175094b Support unofficial::getopt-win32::getopt as a getopt library target
Handles the vcpkg case
2025-10-26 13:06:11 +01:00
Jonas Kvinge
b4c5b9e1e1 Turn on git revision 2025-10-26 13:05:56 +01:00
417 changed files with 16550 additions and 2296 deletions

View File

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

View File

@@ -97,7 +97,7 @@ jobs:
cmake --build build --config Release --parallel 4 cmake --build build --config Release --parallel 4
cmake --install build cmake --install build
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
@@ -134,14 +134,14 @@ jobs:
run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT
- name: Upload source - name: Upload source
if: matrix.opensuse_version == 'tumbleweed' if: matrix.opensuse_version == 'tumbleweed'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: source name: source
path: | path: |
/usr/src/packages/SOURCES/*.xz /usr/src/packages/SOURCES/*.xz
- name: Upload rpm - name: Upload rpm
if: matrix.opensuse_version != 'tumbleweed' if: matrix.opensuse_version != 'tumbleweed'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: opensuse-${{steps.set-subdir.outputs.subdir}} name: opensuse-${{steps.set-subdir.outputs.subdir}}
path: | path: |
@@ -209,7 +209,7 @@ jobs:
sparsehash-devel sparsehash-devel
rapidjson-devel rapidjson-devel
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
@@ -234,7 +234,7 @@ jobs:
working-directory: build working-directory: build
run: rpmbuild -ba strawberry.spec run: rpmbuild -ba strawberry.spec
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: fedora-${{matrix.fedora_version}} name: fedora-${{matrix.fedora_version}}
path: | path: |
@@ -307,7 +307,7 @@ jobs:
- name: Remove files - name: Remove files
run: rm -rf /usr/lib64/qt6/lib/cmake/Qt6Sql/{Qt6QMYSQL*,Qt6QODBCD*,Qt6QPSQL*,Qt6QIBase*} run: rm -rf /usr/lib64/qt6/lib/cmake/Qt6Sql/{Qt6QMYSQL*,Qt6QODBCD*,Qt6QPSQL*,Qt6QIBase*}
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
@@ -333,7 +333,7 @@ jobs:
run: rpmbuild -ba strawberry.spec run: rpmbuild -ba strawberry.spec
- name: Upload artifacts - name: Upload artifacts
if: matrix.openmandriva_version != 'cooker' if: matrix.openmandriva_version != 'cooker'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: openmandriva-${{matrix.openmandriva_version}} name: openmandriva-${{matrix.openmandriva_version}}
path: | path: |
@@ -409,7 +409,7 @@ jobs:
cmake --build build --config Release --parallel 4 cmake --build build --config Release --parallel 4
cmake --install build cmake --install build
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
@@ -434,7 +434,7 @@ jobs:
working-directory: build working-directory: build
run: rpmbuild -ba strawberry.spec run: rpmbuild -ba strawberry.spec
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: mageia-${{matrix.mageia_version}} name: mageia-${{matrix.mageia_version}}
path: | path: |
@@ -511,7 +511,7 @@ jobs:
cmake --build build --config Release --parallel 4 cmake --build build --config Release --parallel 4
cmake --install build cmake --install build
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
@@ -528,7 +528,7 @@ jobs:
- name: Copy deb - name: Copy deb
run: cp ../*.deb . run: cp ../*.deb .
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: debian-${{matrix.debian_version}} name: debian-${{matrix.debian_version}}
path: | path: |
@@ -607,7 +607,7 @@ jobs:
cmake --build build --config Release --parallel 4 cmake --build build --config Release --parallel 4
cmake --install build cmake --install build
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
@@ -624,7 +624,7 @@ jobs:
- name: Copy deb - name: Copy deb
run: cp ../*.deb ../*.ddeb . run: cp ../*.deb ../*.ddeb .
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: ubuntu-${{matrix.ubuntu_version}} name: ubuntu-${{matrix.ubuntu_version}}
path: | path: |
@@ -699,7 +699,7 @@ jobs:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
run: apt install -y keyboxd run: apt install -y keyboxd
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
@@ -735,16 +735,22 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive 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 - name: Build FreeBSD
id: build-freebsd id: build-freebsd
uses: vmactions/freebsd-vm@v1.2.4 uses: vmactions/freebsd-vm@v1.3.2
with: with:
usesh: true 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 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: | run: |
set -e set -e
@@ -760,13 +766,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
- name: Build OpenBSD - name: Build OpenBSD
id: build-openbsd id: build-openbsd
uses: vmactions/openbsd-vm@v1.2.1 uses: vmactions/openbsd-vm@v1.2.9
with: with:
usesh: true usesh: true
mem: 4096 mem: 4096
@@ -787,7 +793,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
runner: [ 'macos-13', 'macos-15' ] runner: [ 'macos-15-intel', 'macos-15' ]
buildtype: [ 'release' ] buildtype: [ 'release' ]
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
@@ -826,20 +832,20 @@ jobs:
rm -f uninstall.sh rm -f uninstall.sh
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
- name: Import certificate file - name: Import certificate file
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false 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: with:
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE }} p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE }}
p12-password: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }} p12-password: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
- name: Download macOS dependencies - name: Download macOS dependencies
run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-macos-dependencies/releases/latest/download/strawberry-macos-${{env.arch}}-${{env.buildtype}}.tar.xz run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-macos-dependencies$(test "${{env.arch}}" = "x86_64" && echo "-intel" || echo "")/releases/latest/download/strawberry-macos-${{env.arch}}-${{env.buildtype}}.tar.xz
- name: Extract macOS dependencies - name: Extract macOS dependencies
run: sudo tar -C / -xf strawberry-macos-${{env.arch}}-${{env.buildtype}}.tar.xz run: sudo tar -C / -xf strawberry-macos-${{env.arch}}-${{env.buildtype}}.tar.xz
@@ -890,9 +896,9 @@ jobs:
run: make deploy run: make deploy
- name: Manually Codesign - name: Manually Codesign
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.runner == 'macos-13' if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.runner == 'macos-15-intel'
working-directory: build working-directory: build
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libpcre2-8.0.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libfreetype.6.dylib,libzstd.1.dylib} strawberry.app/Contents/Frameworks/png.framework/png strawberry.app run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libpcre2-8.0.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libfreetype.6.dylib,libzstd.1.dylib,libbrotlicommon.1.dylib,libbrotlienc.1.dylib} strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
- name: Manually Codesign - name: Manually Codesign
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.runner == 'macos-15' if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.runner == 'macos-15'
@@ -977,7 +983,7 @@ jobs:
run: echo "cmake_buildtype=$(echo ${{env.buildtype}} | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}')" >> $GITHUB_ENV run: echo "cmake_buildtype=$(echo ${{env.buildtype}} | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}')" >> $GITHUB_ENV
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
@@ -1080,7 +1086,7 @@ jobs:
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
@@ -1310,7 +1316,7 @@ jobs:
- name: Set SDK version - name: Set SDK version
if: matrix.arch != 'arm64' if: matrix.arch != 'arm64'
shell: bash shell: bash
run: echo "sdk_version=10.0.19041.0" >> $GITHUB_ENV run: echo "sdk_version=10.0.22621.0" >> $GITHUB_ENV
- name: Set SDK version - name: Set SDK version
if: matrix.arch == 'arm64' if: matrix.arch == 'arm64'
@@ -1321,10 +1327,6 @@ jobs:
shell: cmd shell: cmd
run: choco install --no-progress rsync run: choco install --no-progress rsync
- name: Set SSH command
shell: bash
run: echo "ssh_command=/c/ProgramData/chocolatey/lib/rsync/tools/$(ls -1 /c/ProgramData/chocolatey/lib/rsync/tools/ | grep -v '\.zip$' | grep '^cwrsync' | tail -1)/bin/ssh.exe" >> $GITHUB_ENV
- name: Cleanup PATH - name: Cleanup PATH
uses: egor-tensin/cleanup-path@v4 uses: egor-tensin/cleanup-path@v4
with: with:
@@ -1409,7 +1411,7 @@ jobs:
vsversion: 2022 vsversion: 2022
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
@@ -1623,7 +1625,7 @@ jobs:
- name: rsync - name: rsync
if: steps.set-upload-path.outputs.upload_path != '' if: steps.set-upload-path.outputs.upload_path != ''
shell: bash shell: bash
run: rsync -e "${{env.ssh_command}} -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/StrawberrySetup*.exe ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{steps.set-upload-path.outputs.upload_path}}/ run: rsync -e "/c/ProgramData/chocolatey/lib/rsync/tools/bin/ssh.exe -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/StrawberrySetup*.exe ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{steps.set-upload-path.outputs.upload_path}}/
upload: upload:
@@ -1642,11 +1644,11 @@ jobs:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
run: sudo apt install -y git rsync run: sudo apt install -y git rsync
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v5 uses: actions/download-artifact@v7
with: with:
path: artifacts path: artifacts
- name: SSH Setup - name: SSH Setup
@@ -1690,7 +1692,7 @@ jobs:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
run: sudo apt install -y git jq gh run: sudo apt install -y git jq gh
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Show release assets - name: Show release assets
@@ -1698,7 +1700,7 @@ jobs:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: gh release view "${{github.event.release.tag_name}}" --json assets | jq -r '.assets[].name' run: gh release view "${{github.event.release.tag_name}}" --json assets | jq -r '.assets[].name'
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v5 uses: actions/download-artifact@v7
with: with:
path: artifacts path: artifacts
- name: Add artifacts to release - name: Add artifacts to release

2
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/build /build
/bin /bin
/CMakeLists.txt.user /CMakeLists.txt.user
/.qtcreator
/.kdev4 /.kdev4
/strawberry.kdev4 /strawberry.kdev4
/.vscode /.vscode
@@ -12,3 +13,4 @@
/CMakeSettings.json /CMakeSettings.json
/dist/scripts/maketarball.sh /dist/scripts/maketarball.sh
/debian/changelog /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); Discord_RegisterW(appId, wcommand);
} }

View File

@@ -218,7 +218,7 @@ set(QT_VERSION_MAJOR 6)
set(QT_MIN_VERSION 6.4.0) set(QT_MIN_VERSION 6.4.0)
set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR}) set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR})
set(QT_COMPONENTS Core Concurrent Gui Widgets Network Sql) set(QT_COMPONENTS Core Concurrent Gui Widgets Network Sql)
set(QT_OPTIONAL_COMPONENTS GuiPrivate LinguistTools Test) set(QT_OPTIONAL_COMPONENTS GuiPrivate LinguistTools Test Protobuf)
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
list(APPEND QT_OPTIONAL_COMPONENTS DBus) list(APPEND QT_OPTIONAL_COMPONENTS DBus)
endif() endif()
@@ -266,6 +266,8 @@ if(WIN32)
set(GETOPT_LIBRARIES getopt-win::getopt) set(GETOPT_LIBRARIES getopt-win::getopt)
elseif(TARGET getopt::getopt_shared) elseif(TARGET getopt::getopt_shared)
set(GETOPT_LIBRARIES getopt::getopt_shared) set(GETOPT_LIBRARIES getopt::getopt_shared)
elseif(TARGET unofficial::getopt-win32::getopt)
set(GETOPT_LIBRARIES unofficial::getopt-win32::getopt)
else() else()
message(FATAL_ERROR "Missing getopt") message(FATAL_ERROR "Missing getopt")
endif() endif()
@@ -276,6 +278,7 @@ if(APPLE OR WIN32)
if(TARGET "qtsparkle-qt${QT_VERSION_MAJOR}::qtsparkle") if(TARGET "qtsparkle-qt${QT_VERSION_MAJOR}::qtsparkle")
set(QTSPARKLE_FOUND ON) set(QTSPARKLE_FOUND ON)
endif() endif()
pkg_check_modules(TINYSVCMDNS IMPORTED_TARGET tinysvcmdns)
endif() endif()
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
@@ -379,6 +382,18 @@ optional_component(DISCORD_RPC ON "Discord Rich Presence"
DEPENDS "RapidJSON" RapidJSON_FOUND DEPENDS "RapidJSON" RapidJSON_FOUND
) )
if(WIN32)
optional_component(NETWORKREMOTE ON "Network remote"
DEPENDS "Qt Protobuf" Qt${QT_VERSION_MAJOR}Protobuf_FOUND
DEPENDS "tinysvcmdns" TINYSVCMDNS_FOUND
)
else()
optional_component(NETWORKREMOTE ON "Network remote"
DEPENDS "Qt Protobuf" Qt${QT_VERSION_MAJOR}Protobuf_FOUND
)
endif()
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ) if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
set(HAVE_CHROMAPRINT ON) set(HAVE_CHROMAPRINT ON)
endif() endif()
@@ -699,6 +714,7 @@ set(SOURCES
src/lyrics/elyricsnetlyricsprovider.cpp src/lyrics/elyricsnetlyricsprovider.cpp
src/lyrics/letraslyricsprovider.cpp src/lyrics/letraslyricsprovider.cpp
src/lyrics/lyricfindlyricsprovider.cpp src/lyrics/lyricfindlyricsprovider.cpp
src/lyrics/lrcliblyricsprovider.cpp
src/settings/settingsdialog.cpp src/settings/settingsdialog.cpp
src/settings/settingspage.cpp src/settings/settingspage.cpp
@@ -756,6 +772,7 @@ set(SOURCES
src/widgets/loginstatewidget.cpp src/widgets/loginstatewidget.cpp
src/widgets/ratingwidget.cpp src/widgets/ratingwidget.cpp
src/widgets/resizabletextedit.cpp src/widgets/resizabletextedit.cpp
src/widgets/filechooserwidget.cpp
src/osd/osdbase.cpp src/osd/osdbase.cpp
src/osd/osdpretty.cpp src/osd/osdpretty.cpp
@@ -995,6 +1012,7 @@ set(HEADERS
src/lyrics/elyricsnetlyricsprovider.h src/lyrics/elyricsnetlyricsprovider.h
src/lyrics/letraslyricsprovider.h src/lyrics/letraslyricsprovider.h
src/lyrics/lyricfindlyricsprovider.h src/lyrics/lyricfindlyricsprovider.h
src/lyrics/lrcliblyricsprovider.h
src/settings/settingsdialog.h src/settings/settingsdialog.h
src/settings/settingspage.h src/settings/settingspage.h
@@ -1053,6 +1071,7 @@ set(HEADERS
src/widgets/ratingwidget.h src/widgets/ratingwidget.h
src/widgets/forcescrollperpixel.h src/widgets/forcescrollperpixel.h
src/widgets/resizabletextedit.h src/widgets/resizabletextedit.h
src/widgets/filechooserwidget.h
src/osd/osdbase.h src/osd/osdbase.h
src/osd/osdpretty.h src/osd/osdpretty.h
@@ -1459,6 +1478,7 @@ optional_source(HAVE_QOBUZ
src/qobuz/qobuzrequest.cpp src/qobuz/qobuzrequest.cpp
src/qobuz/qobuzstreamurlrequest.cpp src/qobuz/qobuzstreamurlrequest.cpp
src/qobuz/qobuzfavoriterequest.cpp src/qobuz/qobuzfavoriterequest.cpp
src/qobuz/qobuzcredentialfetcher.cpp
src/settings/qobuzsettingspage.cpp src/settings/qobuzsettingspage.cpp
src/covermanager/qobuzcoverprovider.cpp src/covermanager/qobuzcoverprovider.cpp
HEADERS HEADERS
@@ -1468,12 +1488,64 @@ optional_source(HAVE_QOBUZ
src/qobuz/qobuzrequest.h src/qobuz/qobuzrequest.h
src/qobuz/qobuzstreamurlrequest.h src/qobuz/qobuzstreamurlrequest.h
src/qobuz/qobuzfavoriterequest.h src/qobuz/qobuzfavoriterequest.h
src/qobuz/qobuzcredentialfetcher.h
src/settings/qobuzsettingspage.h src/settings/qobuzsettingspage.h
src/covermanager/qobuzcoverprovider.h src/covermanager/qobuzcoverprovider.h
UI UI
src/settings/qobuzsettingspage.ui src/settings/qobuzsettingspage.ui
) )
if(HAVE_NETWORKREMOTE)
optional_source(HAVE_NETWORKREMOTE
SOURCES
src/core/zeroconf.cpp
src/networkremote/incomingdataparser.cpp
src/networkremote/networkremote.cpp
src/networkremote/outgoingdatacreator.cpp
src/networkremote/networkremoteclient.cpp
src/networkremote/songsender.cpp
src/settings/networkremotesettingspage.cpp
HEADERS
src/networkremote/networkremote.h
src/networkremote/incomingdataparser.h
src/networkremote/outgoingdatacreator.h
src/networkremote/networkremoteclient.h
src/networkremote/songsender.h
src/settings/networkremotesettingspage.h
UI
src/settings/networkremotesettingspage.ui
)
if(UNIX AND NOT APPLE)
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt${QT_VERSION_MAJOR}::qdbusxml2cpp LOCATION)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/avahi/avahiserver.cpp
${CMAKE_CURRENT_BINARY_DIR}/avahi/avahiserver.h
COMMAND ${QT_DBUSXML2CPP_EXECUTABLE}
${CMAKE_SOURCE_DIR}/src/avahi/org.freedesktop.Avahi.Server.xml
-p ${CMAKE_CURRENT_BINARY_DIR}/avahi/avahiserver
-i includes/dbus_metatypes.h
DEPENDS src/avahi/org.freedesktop.Avahi.Server.xml
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/avahi/avahientrygroup.cpp
${CMAKE_CURRENT_BINARY_DIR}/avahi/avahientrygroup.h
COMMAND ${QT_DBUSXML2CPP_EXECUTABLE}
${CMAKE_SOURCE_DIR}/src/avahi/org.freedesktop.Avahi.EntryGroup.xml
-p ${CMAKE_CURRENT_BINARY_DIR}/avahi/avahientrygroup
-i includes/dbus_metatypes.h
DEPENDS src/avahi/org.freedesktop.Avahi.EntryGroup.xml
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
list(APPEND SOURCES src/avahi/avahi.cpp ${CMAKE_CURRENT_BINARY_DIR}/avahi/avahientrygroup.cpp ${CMAKE_CURRENT_BINARY_DIR}/avahi/avahiserver.cpp)
list(APPEND HEADERS src/avahi/avahi.h ${CMAKE_CURRENT_BINARY_DIR}/avahi/avahientrygroup.h ${CMAKE_CURRENT_BINARY_DIR}/avahi/avahiserver.h)
endif()
optional_source(APPLE SOURCES src/core/bonjour.mm HEADERS src/core/bonjour.h)
optional_source(WIN32 SOURCES src/core/tinysvcmdns.cpp HEADERS src/core/tinysvcmdns.h)
endif()
qt_wrap_cpp(SOURCES ${HEADERS}) qt_wrap_cpp(SOURCES ${HEADERS})
qt_wrap_ui(SOURCES ${UI}) qt_wrap_ui(SOURCES ${UI})
qt_add_resources(SOURCES data/data.qrc data/icons.qrc) qt_add_resources(SOURCES data/data.qrc data/icons.qrc)
@@ -1512,6 +1584,12 @@ if(HAVE_TRANSLATIONS)
endif() endif()
endif() endif()
if(HAVE_NETWORKREMOTE)
qt_add_protobuf(NetworkRemoteMessages
PROTO_FILES src/networkremote/networkremotemessages.proto
)
endif()
target_include_directories(strawberry_lib PUBLIC target_include_directories(strawberry_lib PUBLIC
${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}
${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}
@@ -1544,6 +1622,7 @@ target_link_libraries(strawberry_lib PUBLIC
Qt${QT_VERSION_MAJOR}::Sql Qt${QT_VERSION_MAJOR}::Sql
$<$<BOOL:${HAVE_DBUS}>:Qt${QT_VERSION_MAJOR}::DBus> $<$<BOOL:${HAVE_DBUS}>:Qt${QT_VERSION_MAJOR}::DBus>
$<$<BOOL:${HAVE_QPA_QPLATFORMNATIVEINTERFACE}>:Qt${QT_VERSION_MAJOR}::GuiPrivate> $<$<BOOL:${HAVE_QPA_QPLATFORMNATIVEINTERFACE}>:Qt${QT_VERSION_MAJOR}::GuiPrivate>
$<$<BOOL:${HAVE_NETWORKREMOTE}>:Qt${QT_VERSION_MAJOR}::Protobuf>
ICU::uc ICU::uc
ICU::i18n ICU::i18n
$<$<BOOL:${HAVE_STREAMTAGREADER}>:PkgConfig::LIBSPARSEHASH> $<$<BOOL:${HAVE_STREAMTAGREADER}>:PkgConfig::LIBSPARSEHASH>
@@ -1563,6 +1642,7 @@ target_link_libraries(strawberry_lib PUBLIC
$<$<BOOL:${MSVC}>:WindowsApp> $<$<BOOL:${MSVC}>:WindowsApp>
KDAB::kdsingleapplication KDAB::kdsingleapplication
$<$<BOOL:${HAVE_DISCORD_RPC}>:discord-rpc> $<$<BOOL:${HAVE_DISCORD_RPC}>:discord-rpc>
$<$<BOOL:${HAVE_NETWORKREMOTE}>:NetworkRemoteMessages>
) )
if(APPLE) if(APPLE)
@@ -1581,6 +1661,10 @@ if(APPLE)
endif() endif()
endif() endif()
if(WIN32 AND HAVE_NETWORKREMOTE)
target_link_libraries(strawberry_lib PUBLIC PkgConfig::TINYSVCMDNS)
endif()
target_link_libraries(strawberry PUBLIC strawberry_lib) target_link_libraries(strawberry PUBLIC strawberry_lib)
if(NOT APPLE) if(NOT APPLE)

View File

@@ -2,6 +2,31 @@ Strawberry Music Player
======================= =======================
ChangeLog 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
* Fixed length filter search
* Fixed playlist parser converting Spotify URL's
* Removed use of deprecated QStyle::State_Editing
* Ignore connection closed errors for ListenBrainz
* (Windows) Support building with vcpkg unofficial::getopt-win32::getopt
Version 1.2.14 (2025.10.25): Version 1.2.14 (2025.10.25):
Bugfixes: Bugfixes:

View File

@@ -17,7 +17,9 @@ Its written in **C++ using the Qt framework**, designed for **audiophiles and
- **Forum:** https://forum.strawberrymusicplayer.org - **Forum:** https://forum.strawberrymusicplayer.org
- **GitHub:** https://github.com/strawberrymusicplayer/strawberry - **GitHub:** https://github.com/strawberrymusicplayer/strawberry
- **Latest builds:** https://builds.strawberrymusicplayer.org - **Latest builds:** https://builds.strawberrymusicplayer.org
- **openSUSE Build Service:** https://build.opensuse.org/package/show/home:jonaski:audio/strawberry - **openSUSE Build Service:**
- Stable: https://build.opensuse.org/package/show/home:jonaski:strawberry/strawberry
- Unstable: https://build.opensuse.org/package/show/home:jonaski:strawberry-dev/strawberry
- **Ubuntu PPAs:** - **Ubuntu PPAs:**
- Stable: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry - Stable: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry
- Unstable: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry-unstable - Unstable: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry-unstable
@@ -64,7 +66,7 @@ Supporting open-source developers helps ensure continued maintenance and improve
- Loudness analysis and EBU R128 normalization - Loudness analysis and EBU R128 normalization
- Editing tags and fetching missing tags via [MusicBrainz](https://musicbrainz.org/) - 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/) - 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 - Audio analyzer and equalizer
- Transfer music to USB, MTP and iPod devices - Transfer music to USB, MTP and iPod devices
- Scrobbling to [Last.fm](https://www.last.fm/) and [ListenBrainz](https://listenbrainz.org/) - 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_MAJOR 1)
set(STRAWBERRY_VERSION_MINOR 2) set(STRAWBERRY_VERSION_MINOR 2)
set(STRAWBERRY_VERSION_PATCH 14) set(STRAWBERRY_VERSION_PATCH 16)
#set(STRAWBERRY_VERSION_PRERELEASE rc1) #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}") set(majorminorpatch "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}.${STRAWBERRY_VERSION_PATCH}")

2
debian/control vendored
View File

@@ -60,7 +60,7 @@ Description: music player and music collection organizer
- Edit tags on audio files - Edit tags on audio files
- Automatically retrieve tags from MusicBrainz - Automatically retrieve tags from MusicBrainz
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify - 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 analyzer
- Audio equalizer - Audio equalizer
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic - 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>Edit tags on audio files</li>
<li>Automatically retrieve tags from MusicBrainz</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>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>Audio analyzer and equalizer</li>
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</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> <li>Scrobbler with support for Last.fm and ListenBrainz</li>
@@ -51,6 +51,8 @@
</screenshots> </screenshots>
<update_contact>eclipseo@fedoraproject.org</update_contact> <update_contact>eclipseo@fedoraproject.org</update_contact>
<releases> <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.14" date="2025-10-25"/>
<release version="1.2.13" date="2025-08-31"/> <release version="1.2.13" date="2025-08-31"/>
<release version="1.2.12" date="2025-08-12"/> <release version="1.2.12" date="2025-08-12"/>

View File

@@ -29,9 +29,7 @@ Features:
.br .br
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify - Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
.br .br
- 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
.br
- Support for multiple backends
.br .br
- Audio analyzer - Audio analyzer
.br .br

View File

@@ -93,8 +93,7 @@ Features:
- Edit tags on audio files - Edit tags on audio files
- Automatically retrieve tags from MusicBrainz - Automatically retrieve tags from MusicBrainz
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify - 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
- Support for multiple backends
- Audio analyzer - Audio analyzer
- Audio equalizer - Audio equalizer
- Scrobbler with support for Last.fm and ListenBrainz - Scrobbler with support for Last.fm and ListenBrainz

View File

@@ -477,15 +477,15 @@ Section "Strawberry" Strawberry
; Common files ; Common files
File "icudt77.dll" File "icudt78.dll"
!ifdef msvc && arch_arm64 !ifdef msvc && arch_arm64
File "fftw3.dll" File "fftw3.dll"
!else !else
File "libfftw3-3.dll" File "libfftw3-3.dll"
!endif !endif
!ifdef msvc && debug !ifdef msvc && debug
File "icuin77d.dll" File "icuin78d.dll"
File "icuuc77d.dll" File "icuuc78d.dll"
File "libxml2d.dll" File "libxml2d.dll"
File "Qt6Concurrentd.dll" File "Qt6Concurrentd.dll"
File "Qt6Cored.dll" File "Qt6Cored.dll"
@@ -494,8 +494,8 @@ Section "Strawberry" Strawberry
File "Qt6Sqld.dll" File "Qt6Sqld.dll"
File "Qt6Widgetsd.dll" File "Qt6Widgetsd.dll"
!else !else
File "icuin77.dll" File "icuin78.dll"
File "icuuc77.dll" File "icuuc78.dll"
File "libxml2.dll" File "libxml2.dll"
File "Qt6Concurrent.dll" File "Qt6Concurrent.dll"
File "Qt6Core.dll" File "Qt6Core.dll"
@@ -505,6 +505,7 @@ Section "Strawberry" Strawberry
File "Qt6Widgets.dll" File "Qt6Widgets.dll"
!endif !endif
!ifdef msvc && arch_x86
File "avcodec-61.dll" File "avcodec-61.dll"
File "avfilter-10.dll" File "avfilter-10.dll"
File "avformat-61.dll" File "avformat-61.dll"
@@ -512,6 +513,14 @@ Section "Strawberry" Strawberry
File "postproc-58.dll" File "postproc-58.dll"
File "swresample-5.dll" File "swresample-5.dll"
File "swscale-8.dll" File "swscale-8.dll"
!else
File "avcodec-62.dll"
File "avfilter-11.dll"
File "avformat-62.dll"
File "avutil-60.dll"
File "swresample-6.dll"
File "swscale-9.dll"
!endif
; Register Strawberry with Default Programs ; Register Strawberry with Default Programs
Var /GLOBAL AppIcon Var /GLOBAL AppIcon
@@ -1019,15 +1028,15 @@ Section "Uninstall"
; Common files ; Common files
Delete "$INSTDIR\icudt77.dll" Delete "$INSTDIR\icudt78.dll"
!ifdef msvc && arch_arm64 !ifdef msvc && arch_arm64
Delete "$INSTDIR\fftw3.dll" Delete "$INSTDIR\fftw3.dll"
!else !else
Delete "$INSTDIR\libfftw3-3.dll" Delete "$INSTDIR\libfftw3-3.dll"
!endif !endif
!ifdef msvc && debug !ifdef msvc && debug
Delete "$INSTDIR\icuin77d.dll" Delete "$INSTDIR\icuin78d.dll"
Delete "$INSTDIR\icuuc77d.dll" Delete "$INSTDIR\icuuc78d.dll"
Delete "$INSTDIR\libxml2d.dll" Delete "$INSTDIR\libxml2d.dll"
Delete "$INSTDIR\Qt6Concurrentd.dll" Delete "$INSTDIR\Qt6Concurrentd.dll"
Delete "$INSTDIR\Qt6Cored.dll" Delete "$INSTDIR\Qt6Cored.dll"
@@ -1036,8 +1045,8 @@ Section "Uninstall"
Delete "$INSTDIR\Qt6Sqld.dll" Delete "$INSTDIR\Qt6Sqld.dll"
Delete "$INSTDIR\Qt6Widgetsd.dll" Delete "$INSTDIR\Qt6Widgetsd.dll"
!else !else
Delete "$INSTDIR\icuin77.dll" Delete "$INSTDIR\icuin78.dll"
Delete "$INSTDIR\icuuc77.dll" Delete "$INSTDIR\icuuc78.dll"
Delete "$INSTDIR\libxml2.dll" Delete "$INSTDIR\libxml2.dll"
Delete "$INSTDIR\Qt6Concurrent.dll" Delete "$INSTDIR\Qt6Concurrent.dll"
Delete "$INSTDIR\Qt6Core.dll" Delete "$INSTDIR\Qt6Core.dll"
@@ -1047,6 +1056,7 @@ Section "Uninstall"
Delete "$INSTDIR\Qt6Widgets.dll" Delete "$INSTDIR\Qt6Widgets.dll"
!endif !endif
!ifdef msvc && arch_x86
Delete "$INSTDIR\avcodec-61.dll" Delete "$INSTDIR\avcodec-61.dll"
Delete "$INSTDIR\avfilter-10.dll" Delete "$INSTDIR\avfilter-10.dll"
Delete "$INSTDIR\avformat-61.dll" Delete "$INSTDIR\avformat-61.dll"
@@ -1054,6 +1064,14 @@ Section "Uninstall"
Delete "$INSTDIR\postproc-58.dll" Delete "$INSTDIR\postproc-58.dll"
Delete "$INSTDIR\swresample-5.dll" Delete "$INSTDIR\swresample-5.dll"
Delete "$INSTDIR\swscale-8.dll" Delete "$INSTDIR\swscale-8.dll"
!else
Delete "$INSTDIR\avcodec-62.dll"
Delete "$INSTDIR\avfilter-11.dll"
Delete "$INSTDIR\avformat-62.dll"
Delete "$INSTDIR\avutil-60.dll"
Delete "$INSTDIR\swresample-6.dll"
Delete "$INSTDIR\swscale-9.dll"
!endif
!ifdef mingw !ifdef mingw
Delete "$INSTDIR\gio-modules\libgiognutls.dll" Delete "$INSTDIR\gio-modules\libgiognutls.dll"

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<?xml-stylesheet type="text/xsl" href="introspect.xsl"?>
<!DOCTYPE node SYSTEM "introspect.dtd">
<!--
This file is part of avahi.
avahi is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
avahi 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 Lesser General Public
License along with avahi; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
-->
<node>
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg name="data" type="s" direction="out"/>
</method>
</interface>
<interface name="org.freedesktop.Avahi.EntryGroup">
<method name="Free"/>
<method name="Commit"/>
<method name="Reset"/>
<method name="GetState">
<arg name="state" type="i" direction="out"/>
</method>
<signal name="StateChanged">
<arg name="state" type="i"/>
<arg name="error" type="s"/>
</signal>
<method name="IsEmpty">
<arg name="empty" type="b" direction="out"/>
</method>
<method name="AddService">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="host" type="s" direction="in"/>
<arg name="port" type="q" direction="in"/>
<arg name="txt" type="aay" direction="in"/>
</method>
<method name="AddServiceSubtype">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="subtype" type="s" direction="in"/>
</method>
<method name="UpdateServiceTxt">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="txt" type="aay" direction="in"/>
</method>
<method name="AddAddress">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="address" type="s" direction="in"/>
</method>
<method name="AddRecord">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="clazz" type="q" direction="in"/>
<arg name="type" type="q" direction="in"/>
<arg name="ttl" type="u" direction="in"/>
<arg name="rdata" type="ay" direction="in"/>
</method>
</interface>
</node>

View File

@@ -0,0 +1,405 @@
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<?xml-stylesheet type="text/xsl" href="introspect.xsl"?>
<!DOCTYPE node SYSTEM "introspect.dtd">
<!--
This file is part of avahi.
avahi is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
avahi 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 Lesser General Public
License along with avahi; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
-->
<node>
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg name="data" type="s" direction="out"/>
</method>
</interface>
<interface name="org.freedesktop.Avahi.Server">
<method name="GetVersionString">
<arg name="version" type="s" direction="out"/>
</method>
<method name="GetAPIVersion">
<arg name="version" type="u" direction="out"/>
</method>
<method name="GetHostName">
<arg name="name" type="s" direction="out"/>
</method>
<method name="SetHostName">
<arg name="name" type="s" direction="in"/>
</method>
<method name="GetHostNameFqdn">
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetDomainName">
<arg name="name" type="s" direction="out"/>
</method>
<method name="IsNSSSupportAvailable">
<arg name="yes" type="b" direction="out"/>
</method>
<method name="GetState">
<arg name="state" type="i" direction="out"/>
</method>
<signal name="StateChanged">
<arg name="state" type="i"/>
<arg name="error" type="s"/>
</signal>
<method name="GetLocalServiceCookie">
<arg name="cookie" type="u" direction="out"/>
</method>
<method name="GetAlternativeHostName">
<arg name="name" type="s" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetAlternativeServiceName">
<arg name="name" type="s" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetNetworkInterfaceNameByIndex">
<arg name="index" type="i" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetNetworkInterfaceIndexByName">
<arg name="name" type="s" direction="in"/>
<arg name="index" type="i" direction="out"/>
</method>
<method name="ResolveHostName">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="ResolveAddress">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="address" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="ResolveService">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="type" type="s" direction="out"/>
<arg name="domain" type="s" direction="out"/>
<arg name="host" type="s" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="port" type="q" direction="out"/>
<arg name="txt" type="aay" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="EntryGroupNew">
<arg name="path" type="o" direction="out"/>
</method>
<method name="DomainBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="btype" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceTypeBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceResolverNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="HostNameResolverNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="AddressResolverNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="address" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="RecordBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="clazz" type="q" direction="in"/>
<arg name="type" type="q" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
</interface>
<interface name="org.freedesktop.Avahi.Server2">
<method name="GetVersionString">
<arg name="version" type="s" direction="out"/>
</method>
<method name="GetAPIVersion">
<arg name="version" type="u" direction="out"/>
</method>
<method name="GetHostName">
<arg name="name" type="s" direction="out"/>
</method>
<method name="SetHostName">
<arg name="name" type="s" direction="in"/>
</method>
<method name="GetHostNameFqdn">
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetDomainName">
<arg name="name" type="s" direction="out"/>
</method>
<method name="IsNSSSupportAvailable">
<arg name="yes" type="b" direction="out"/>
</method>
<method name="GetState">
<arg name="state" type="i" direction="out"/>
</method>
<signal name="StateChanged">
<arg name="state" type="i"/>
<arg name="error" type="s"/>
</signal>
<method name="GetLocalServiceCookie">
<arg name="cookie" type="u" direction="out"/>
</method>
<method name="GetAlternativeHostName">
<arg name="name" type="s" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetAlternativeServiceName">
<arg name="name" type="s" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetNetworkInterfaceNameByIndex">
<arg name="index" type="i" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetNetworkInterfaceIndexByName">
<arg name="name" type="s" direction="in"/>
<arg name="index" type="i" direction="out"/>
</method>
<method name="ResolveHostName">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="ResolveAddress">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="address" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="ResolveService">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="type" type="s" direction="out"/>
<arg name="domain" type="s" direction="out"/>
<arg name="host" type="s" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="port" type="q" direction="out"/>
<arg name="txt" type="aay" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="EntryGroupNew">
<arg name="path" type="o" direction="out"/>
</method>
<method name="DomainBrowserPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="btype" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceTypeBrowserPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceBrowserPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceResolverPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="HostNameResolverPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="AddressResolverPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="address" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="RecordBrowserPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="clazz" type="q" direction="in"/>
<arg name="type" type="q" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
</interface>
</node>

View File

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

View File

@@ -102,7 +102,6 @@ void AnalyzerContainer::AddAnalyzerType() {
action->setCheckable(true); action->setCheckable(true);
actions_ << action; actions_ << action;
QObject::connect(action, &QAction::triggered, [this, id]() { ChangeAnalyzer(id); }); QObject::connect(action, &QAction::triggered, [this, id]() { ChangeAnalyzer(id); });
} }
#endif // ANALYZERCONTAINER_H #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 // mxcl says null pixmaps cause crashes, so let's play it safe
std::fill(fade_bars_.begin(), fade_bars_.end(), QPixmap(1, 1)); std::fill(fade_bars_.begin(), fade_bars_.end(), QPixmap(1, 1));
} }
void BlockAnalyzer::resizeEvent(QResizeEvent *e) { void BlockAnalyzer::resizeEvent(QResizeEvent *e) {

View File

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

View File

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

View File

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

114
src/avahi/avahi.cpp Normal file
View File

@@ -0,0 +1,114 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QByteArray>
#include <QString>
#include <QDBusConnection>
#include <QDBusPendingReply>
#include <QDBusPendingCallWatcher>
#include "core/logging.h"
#include "avahi.h"
#include "avahi/avahiserver.h"
#include "avahi/avahientrygroup.h"
using namespace Qt::StringLiterals;
Avahi::Avahi(QObject *parent) : Zeroconf(parent), port_(0), entry_group_interface_(nullptr) {}
void Avahi::PublishInternal(const QString &domain, const QString &type, const QByteArray &name, quint16 port) {
domain_ = domain;
type_ = type;
name_ = name;
port_ = port;
OrgFreedesktopAvahiServerInterface server_interface(u"org.freedesktop.Avahi"_s, u"/"_s, QDBusConnection::systemBus());
QDBusPendingReply<QDBusObjectPath> reply = server_interface.EntryGroupNew();
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &Avahi::PublishInternalFinished);
}
void Avahi::PublishInternalFinished(QDBusPendingCallWatcher *watcher) {
const QDBusPendingReply<QDBusObjectPath> path_reply = watcher->reply();
watcher->deleteLater();
if (path_reply.isError()) {
qLog(Error) << "Failed to create Avahi entry group:" << path_reply.error();
qLog(Info) << "This might be because 'disable-user-service-publishing'" << "is set to 'yes' in avahi-daemon.conf";
return;
}
AddService(path_reply.reply().path());
}
void Avahi::AddService(const QString &path) {
entry_group_interface_ = new OrgFreedesktopAvahiEntryGroupInterface(u"org.freedesktop.Avahi"_s, path, QDBusConnection::systemBus());
QDBusPendingReply<> reply = entry_group_interface_->AddService(-1, -1, 0, QString::fromUtf8(name_.constData(), name_.size()), type_, domain_, QString(), port_);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &Avahi::AddServiceFinished);
}
void Avahi::AddServiceFinished(QDBusPendingCallWatcher *watcher) {
const QDBusPendingReply<QDBusObjectPath> path_reply = watcher->reply();
watcher->deleteLater();
if (path_reply.isError()) {
qLog(Error) << "Failed to add Avahi service:" << path_reply.error();
return;
}
Commit();
}
void Avahi::Commit() {
QDBusPendingReply<> reply = entry_group_interface_->Commit();
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &Avahi::CommitFinished);
}
void Avahi::CommitFinished(QDBusPendingCallWatcher *watcher) {
const QDBusPendingReply<QDBusObjectPath> path_reply = watcher->reply();
watcher->deleteLater();
entry_group_interface_->deleteLater();
entry_group_interface_ = nullptr;
if (path_reply.isError()) {
qLog(Debug) << "Commit error:" << path_reply.error();
}
else {
qLog(Debug) << "Remote interface published on Avahi";
}
}

58
src/avahi/avahi.h Normal file
View File

@@ -0,0 +1,58 @@
/*
* Strawberry Music Player
* Copyright 2024, 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 AVAHI_H
#define AVAHI_H
#include <QObject>
#include <QByteArray>
#include <QString>
#include "core/zeroconf.h"
class QDBusPendingCallWatcher;
class OrgFreedesktopAvahiEntryGroupInterface;
class Avahi : public Zeroconf {
Q_OBJECT
public:
explicit Avahi(QObject *parent = nullptr);
private:
void AddService(const QString &path);
void Commit();
private Q_SLOTS:
void PublishInternalFinished(QDBusPendingCallWatcher *watcher);
void AddServiceFinished(QDBusPendingCallWatcher *watcher);
void CommitFinished(QDBusPendingCallWatcher *watcher);
protected:
virtual void PublishInternal(const QString &domain, const QString &type, const QByteArray &name, quint16 port) override;
private:
QString domain_;
QString type_;
QByteArray name_;
quint16 port_;
OrgFreedesktopAvahiEntryGroupInterface *entry_group_interface_;
};
#endif // AVAHI_H

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<!DOCTYPE node SYSTEM "introspect.dtd">
<!--
This file is part of avahi.
avahi is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
avahi 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 Lesser General Public
License along with avahi; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
-->
<node>
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg name="data" type="s" direction="out"/>
</method>
</interface>
<interface name="org.freedesktop.Avahi.EntryGroup">
<method name="Free"/>
<method name="Commit"/>
<method name="Reset"/>
<method name="GetState">
<arg name="state" type="i" direction="out"/>
</method>
<signal name="StateChanged">
<arg name="state" type="i"/>
<arg name="error" type="s"/>
</signal>
<method name="IsEmpty">
<arg name="empty" type="b" direction="out"/>
</method>
<method name="AddService">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="host" type="s" direction="in"/>
<arg name="port" type="q" direction="in"/>
</method>
<method name="AddServiceSubtype">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="subtype" type="s" direction="in"/>
</method>
<method name="UpdateServiceTxt">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
</method>
<method name="AddAddress">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="address" type="s" direction="in"/>
</method>
<method name="AddRecord">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="clazz" type="q" direction="in"/>
<arg name="type" type="q" direction="in"/>
<arg name="ttl" type="u" direction="in"/>
<arg name="rdata" type="ay" direction="in"/>
</method>
</interface>
</node>

View File

@@ -0,0 +1,396 @@
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<!DOCTYPE node SYSTEM "introspect.dtd">
<!--
This file is part of avahi.
avahi is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
avahi 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 Lesser General Public
License along with avahi; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
-->
<node>
<interface name="org.freedesktop.Avahi.Server">
<method name="GetVersionString">
<arg name="version" type="s" direction="out"/>
</method>
<method name="GetAPIVersion">
<arg name="version" type="u" direction="out"/>
</method>
<method name="GetHostName">
<arg name="name" type="s" direction="out"/>
</method>
<method name="SetHostName">
<arg name="name" type="s" direction="in"/>
</method>
<method name="GetHostNameFqdn">
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetDomainName">
<arg name="name" type="s" direction="out"/>
</method>
<method name="IsNSSSupportAvailable">
<arg name="yes" type="b" direction="out"/>
</method>
<method name="GetState">
<arg name="state" type="i" direction="out"/>
</method>
<signal name="StateChanged">
<arg name="state" type="i"/>
<arg name="error" type="s"/>
</signal>
<method name="GetLocalServiceCookie">
<arg name="cookie" type="u" direction="out"/>
</method>
<method name="GetAlternativeHostName">
<arg name="name" type="s" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetAlternativeServiceName">
<arg name="name" type="s" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetNetworkInterfaceNameByIndex">
<arg name="index" type="i" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetNetworkInterfaceIndexByName">
<arg name="name" type="s" direction="in"/>
<arg name="index" type="i" direction="out"/>
</method>
<method name="ResolveHostName">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="ResolveAddress">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="address" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="ResolveService">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="type" type="s" direction="out"/>
<arg name="domain" type="s" direction="out"/>
<arg name="host" type="s" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="port" type="q" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="EntryGroupNew">
<arg name="path" type="o" direction="out"/>
</method>
<method name="DomainBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="btype" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceTypeBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceResolverNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="HostNameResolverNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="AddressResolverNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="address" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="RecordBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="clazz" type="q" direction="in"/>
<arg name="type" type="q" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
</interface>
<interface name="org.freedesktop.Avahi.Server2">
<method name="GetVersionString">
<arg name="version" type="s" direction="out"/>
</method>
<method name="GetAPIVersion">
<arg name="version" type="u" direction="out"/>
</method>
<method name="GetHostName">
<arg name="name" type="s" direction="out"/>
</method>
<method name="SetHostName">
<arg name="name" type="s" direction="in"/>
</method>
<method name="GetHostNameFqdn">
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetDomainName">
<arg name="name" type="s" direction="out"/>
</method>
<method name="IsNSSSupportAvailable">
<arg name="yes" type="b" direction="out"/>
</method>
<method name="GetState">
<arg name="state" type="i" direction="out"/>
</method>
<signal name="StateChanged">
<arg name="state" type="i"/>
<arg name="error" type="s"/>
</signal>
<method name="GetLocalServiceCookie">
<arg name="cookie" type="u" direction="out"/>
</method>
<method name="GetAlternativeHostName">
<arg name="name" type="s" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetAlternativeServiceName">
<arg name="name" type="s" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetNetworkInterfaceNameByIndex">
<arg name="index" type="i" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetNetworkInterfaceIndexByName">
<arg name="name" type="s" direction="in"/>
<arg name="index" type="i" direction="out"/>
</method>
<method name="ResolveHostName">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="ResolveAddress">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="address" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="ResolveService">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="type" type="s" direction="out"/>
<arg name="domain" type="s" direction="out"/>
<arg name="host" type="s" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="port" type="q" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="EntryGroupNew">
<arg name="path" type="o" direction="out"/>
</method>
<method name="DomainBrowserPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="btype" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceTypeBrowserPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceBrowserPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceResolverPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="HostNameResolverPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="AddressResolverPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="address" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="RecordBrowserPrepare">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="clazz" type="q" direction="in"/>
<arg name="type" type="q" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
</interface>
</node>

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * 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 * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * 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 CollectionLibrary::SyncPlaycountAndRatingToFilesAsync() {
(void)QtConcurrent::run(&CollectionLibrary::SyncPlaycountAndRatingToFiles, this); (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_) { 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_) { 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 * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * 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 * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -27,6 +27,7 @@
#include <QObject> #include <QObject>
#include <QList> #include <QList>
#include <QHash> #include <QHash>
#include <QMap>
#include <QString> #include <QString>
#include "includes/shared_ptr.h" #include "includes/shared_ptr.h"
@@ -71,6 +72,7 @@ class CollectionLibrary : public QObject {
private: private:
void SyncPlaycountAndRatingToFiles(); void SyncPlaycountAndRatingToFiles();
void SavePendingPlaycountsAndRatings();
public Q_SLOTS: public Q_SLOTS:
void ReloadSettings(); void ReloadSettings();
@@ -84,16 +86,26 @@ class CollectionLibrary : public QObject {
void IncrementalScan(); void IncrementalScan();
void CurrentSongChanged(const Song &song);
void Stopped();
private Q_SLOTS: private Q_SLOTS:
void ExitReceived(); void ExitReceived();
void SongsPlaycountChanged(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) const; void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
Q_SIGNALS: Q_SIGNALS:
void Error(const QString &error); void Error(const QString &error);
void ExitFinished(); void ExitFinished();
private: private:
class PendingSongSave {
public:
Song song;
bool save_playcount = false;
bool save_rating = false;
};
const SharedPtr<TaskManager> task_manager_; const SharedPtr<TaskManager> task_manager_;
const SharedPtr<TagReaderClient> tagreader_client_; const SharedPtr<TagReaderClient> tagreader_client_;
@@ -111,6 +123,10 @@ class CollectionLibrary : public QObject {
bool save_playcounts_to_files_; bool save_playcounts_to_files_;
bool save_ratings_to_files_; bool save_ratings_to_files_;
QUrl current_song_url_;
QMap<QUrl, SharedPtr<PendingSongSave>> pending_song_saves_;
}; };
#endif #endif

View File

@@ -212,6 +212,7 @@ void CollectionModel::ReloadSettings() {
const bool show_various_artists = settings.value(CollectionSettings::kVariousArtists, true).toBool(); 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_artists = settings.value(CollectionSettings::kSkipArticlesForArtists, true).toBool();
const bool sort_skip_articles_for_albums = settings.value(CollectionSettings::kSkipArticlesForAlbums, false).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(); use_disk_cache_ = settings.value(CollectionSettings::kSettingsDiskCacheEnable, false).toBool();
QPixmapCache::setCacheLimit(static_cast<int>(MaximumCacheSize(&settings, CollectionSettings::kSettingsCacheSize, CollectionSettings::kSettingsCacheSizeUnit, CollectionSettings::kSettingsCacheSizeDefault) / 1024)); 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_dividers != options_current_.show_dividers ||
show_various_artists != options_current_.show_various_artists || show_various_artists != options_current_.show_various_artists ||
sort_skip_articles_for_artists != options_current_.sort_skip_articles_for_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_pretty_covers = show_pretty_covers;
options_current_.show_dividers = show_dividers; options_current_.show_dividers = show_dividers;
options_current_.show_various_artists = show_various_artists; 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_artists = sort_skip_articles_for_artists;
options_current_.sort_skip_articles_for_albums = sort_skip_articles_for_albums; options_current_.sort_skip_articles_for_albums = sort_skip_articles_for_albums;
options_current_.use_sort_tags = use_sort_tags;
ScheduleReset(); ScheduleReset();
} }
@@ -705,7 +708,7 @@ CollectionItem *CollectionModel::CreateContainerItem(const GroupBy group_by, con
QString divider_key; QString divider_key;
if (options_active_.show_dividers && container_level == 0) { 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_key.isEmpty()) {
if (!divider_nodes_.contains(divider_key)) { if (!divider_nodes_.contains(divider_key)) {
CreateDividerItem(divider_key, DividerDisplayText(group_by, divider_key), parent); 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_level = container_level;
item->container_key = container_key; item->container_key = container_key;
item->display_text = DisplayText(group_by, song); 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()) { if (!divider_key.isEmpty()) {
item->sort_text.prepend(divider_key + QLatin1Char(' ')); 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) { switch (group_by) {
case GroupBy::AlbumArtist: 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: 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: 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: 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: 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: 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: 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: 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: case GroupBy::Disc:
return SortTextForNumber(std::max(0, song.disc())); return SortTextForNumber(std::max(0, song.disc()));
case GroupBy::Year: case GroupBy::Year:
return SortTextForNumber(std::max(0, song.year())) + QLatin1Char(' '); return SortTextForYear(song.year()) + QLatin1Char(' ');
case GroupBy::OriginalYear: case GroupBy::OriginalYear:
return SortTextForNumber(std::max(0, song.effective_originalyear())) + QLatin1Char(' '); return SortTextForYear(song.effective_originalyear()) + QLatin1Char(' ');
case GroupBy::Genre: case GroupBy::Genre:
return SortText(song.genre()); return SortText(song.genre());
case GroupBy::Composer: 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: 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: case GroupBy::Grouping:
return SortText(song.grouping()); return SortText(song.grouping());
case GroupBy::FileType: case GroupBy::FileType:
@@ -1162,14 +1165,14 @@ QString CollectionModel::SortTextForSong(const Song &song) {
QString CollectionModel::SortTextForYear(const int year) { 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; return QStringLiteral("0").repeated(qMax(0, 4 - str.length())) + str;
} }
QString CollectionModel::SortTextForBitrate(const int bitrate) { 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; 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()); if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::AlbumDisc: 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 (!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()); if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::YearAlbum: 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 (!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()); if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::YearAlbumDisc: 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 (!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()); if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::OriginalYearAlbum: 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 (!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()); if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
case GroupBy::OriginalYearAlbumDisc: 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 (!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()); if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
break; break;
@@ -1246,10 +1252,10 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const Song &song,
key = PrettyDisc(song.disc()); key = PrettyDisc(song.disc());
break; break;
case GroupBy::Year: case GroupBy::Year:
key = QString::number(std::max(0, song.year())); key = SortTextForYear(song.year());
break; break;
case GroupBy::OriginalYear: case GroupBy::OriginalYear:
key = QString::number(std::max(0, song.effective_originalyear())); key = SortTextForYear(song.effective_originalyear());
break; break;
case GroupBy::Genre: case GroupBy::Genre:
key = TextOrUnknown(song.genre()); key = TextOrUnknown(song.genre());
@@ -1341,7 +1347,7 @@ QString CollectionModel::DividerKey(const GroupBy group_by, const Song &song, co
case GroupBy::Bitdepth: case GroupBy::Bitdepth:
return SortTextForNumber(song.bitdepth()); return SortTextForNumber(song.bitdepth());
case GroupBy::Bitrate: case GroupBy::Bitrate:
return SortTextForNumber(song.bitrate()); return SortTextForBitrate(song.bitrate());
case GroupBy::None: case GroupBy::None:
case GroupBy::GroupByCount: case GroupBy::GroupByCount:
return QString(); return QString();

View File

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

View File

@@ -58,4 +58,3 @@ class CollectionPlaylistItem : public PlaylistItem {
}; };
#endif // COLLECTIONPLAYLISTITEM_H #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."; 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. // 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; QString fingerprint;
#ifdef HAVE_SONGFINGERPRINTING #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. else { // Search the DB by fingerprint.
QString fingerprint; QString fingerprint;

View File

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

View File

@@ -33,6 +33,7 @@
#cmakedefine HAVE_SPOTIFY #cmakedefine HAVE_SPOTIFY
#cmakedefine HAVE_QOBUZ #cmakedefine HAVE_QOBUZ
#cmakedefine HAVE_DISCORD_RPC #cmakedefine HAVE_DISCORD_RPC
#cmakedefine HAVE_NETWORKREMOTE
#cmakedefine HAVE_TAGLIB_DSFFILE #cmakedefine HAVE_TAGLIB_DSFFILE
#cmakedefine HAVE_TAGLIB_DSDIFFFILE #cmakedefine HAVE_TAGLIB_DSDIFFFILE

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,36 @@
/*
* Strawberry Music Player
* Copyright 2024, 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 NETWORKREMOTECONSTANTS_H
#define NETWORKREMOTECONSTANTS_H
#include <QStringList>
using namespace Qt::Literals::StringLiterals;
namespace NetworkRemoteConstants {
const QStringList kDefaultMusicExtensionsAllowedRemotely = { u"aac"_s, u"alac"_s, u"flac"_s, u"m3u"_s, u"m4a"_s, u"mp3"_s, u"ogg"_s, u"wav"_s, u"wmv"_s };
constexpr quint16 kDefaultServerPort = 5500;
constexpr char kTranscoderSettingPostfix[] = "/NetworkRemote";
constexpr quint32 kFileChunkSize = 100000;
} // namespace NetworkRemoteConstants
#endif // NETWORKREMOTECONSTANTS_H

View File

@@ -0,0 +1,35 @@
/*
* Strawberry Music Player
* Copyright 2024, 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 NETWORKREMOTESETTINGSCONSTANTS_H
#define NETWORKREMOTESETTINGSCONSTANTS_H
namespace NetworkRemoteSettingsConstants {
constexpr char kSettingsGroup[] = "NetworkRemote";
constexpr char kEnabled[] = "enabled";
constexpr char kPort[] = "port";
constexpr char kAllowPublicAccess[] = "allow_public_access";
constexpr char kUseAuthCode[] = "use_authcode";
constexpr char kAuthCode[] = "authcode";
constexpr char kFilesRootFolder[] = "files_root_folder";
} // namespace NetworkRemoteSettingsConstants
#endif // NETWORKREMOTESETTINGSCONSTANTS_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -59,6 +59,7 @@
#include "covermanager/albumcoverchoicecontroller.h" #include "covermanager/albumcoverchoicecontroller.h"
#include "lyrics/lyricsfetcher.h" #include "lyrics/lyricsfetcher.h"
#include "constants/contextsettings.h" #include "constants/contextsettings.h"
#include "constants/timeconstants.h"
#include "contextview.h" #include "contextview.h"
#include "contextalbum.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) { 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_fetcher_->Clear();
lyrics_tried_ = true; 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: protected:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
void contextMenuEvent(QContextMenuEvent*) override; void contextMenuEvent(QContextMenuEvent *e) override;
void dragEnterEvent(QDragEnterEvent*) override; void dragEnterEvent(QDragEnterEvent *e) override;
void dropEvent(QDropEvent*) override; void dropEvent(QDropEvent *e) override;
private: private:
void AddActions(); void AddActions();

View File

@@ -74,6 +74,7 @@
#include "lyrics/elyricsnetlyricsprovider.h" #include "lyrics/elyricsnetlyricsprovider.h"
#include "lyrics/letraslyricsprovider.h" #include "lyrics/letraslyricsprovider.h"
#include "lyrics/lyricfindlyricsprovider.h" #include "lyrics/lyricfindlyricsprovider.h"
#include "lyrics/lrcliblyricsprovider.h"
#include "scrobbler/audioscrobbler.h" #include "scrobbler/audioscrobbler.h"
#include "scrobbler/lastfmscrobbler.h" #include "scrobbler/lastfmscrobbler.h"
@@ -109,6 +110,10 @@
# include "moodbar/moodbarloader.h" # include "moodbar/moodbarloader.h"
#endif #endif
#ifdef HAVE_NETWORKREMOTE
# include "networkremote/networkremote.h"
#endif
#include "radios/radioservices.h" #include "radios/radioservices.h"
#include "radios/radiobackend.h" #include "radios/radiobackend.h"
@@ -117,8 +122,8 @@ using namespace std::chrono_literals;
class ApplicationImpl { class ApplicationImpl {
public: public:
explicit ApplicationImpl(Application *app) : explicit ApplicationImpl(Application *app)
tagreader_client_([app](){ : tagreader_client_([app]() {
TagReaderClient *client = new TagReaderClient(); TagReaderClient *client = new TagReaderClient();
app->MoveToNewThread(client); app->MoveToNewThread(client);
return client; return client;
@@ -182,6 +187,7 @@ class ApplicationImpl {
lyrics_providers->AddProvider(new ElyricsNetLyricsProvider(lyrics_providers->network())); lyrics_providers->AddProvider(new ElyricsNetLyricsProvider(lyrics_providers->network()));
lyrics_providers->AddProvider(new LetrasLyricsProvider(lyrics_providers->network())); lyrics_providers->AddProvider(new LetrasLyricsProvider(lyrics_providers->network()));
lyrics_providers->AddProvider(new LyricFindLyricsProvider(lyrics_providers->network())); lyrics_providers->AddProvider(new LyricFindLyricsProvider(lyrics_providers->network()));
lyrics_providers->AddProvider(new LrcLibLyricsProvider(lyrics_providers->network()));
lyrics_providers->ReloadSettings(); lyrics_providers->ReloadSettings();
return lyrics_providers; return lyrics_providers;
}), }),
@@ -214,6 +220,13 @@ class ApplicationImpl {
#ifdef HAVE_MOODBAR #ifdef HAVE_MOODBAR
moodbar_loader_([app]() { return new MoodbarLoader(app); }), moodbar_loader_([app]() { return new MoodbarLoader(app); }),
moodbar_controller_([app]() { return new MoodbarController(app->player(), app->moodbar_loader()); }), moodbar_controller_([app]() { return new MoodbarController(app->player(), app->moodbar_loader()); }),
#endif
#ifdef HAVE_NETWORKREMOTE
network_remote_([app]() {
NetworkRemote *networkremote = new NetworkRemote(app->database(), app->player(), app->collection_backend(), app->playlist_manager(), app->playlist_backend(), app->current_albumcover_loader(), app->scrobbler());
app->MoveToNewThread(networkremote);
return networkremote;
}),
#endif #endif
lastfm_import_([app]() { return new LastFMImport(app->network()); }) lastfm_import_([app]() { return new LastFMImport(app->network()); })
{} {}
@@ -239,6 +252,9 @@ class ApplicationImpl {
#ifdef HAVE_MOODBAR #ifdef HAVE_MOODBAR
Lazy<MoodbarLoader> moodbar_loader_; Lazy<MoodbarLoader> moodbar_loader_;
Lazy<MoodbarController> moodbar_controller_; Lazy<MoodbarController> moodbar_controller_;
#endif
#ifdef HAVE_NETWORKREMOTE
Lazy<NetworkRemote> network_remote_;
#endif #endif
Lazy<LastFMImport> lastfm_import_; Lazy<LastFMImport> lastfm_import_;
@@ -388,3 +404,6 @@ SharedPtr<LastFMImport> Application::lastfm_import() const { return p_->lastfm_i
SharedPtr<MoodbarController> Application::moodbar_controller() const { return p_->moodbar_controller_.ptr(); } SharedPtr<MoodbarController> Application::moodbar_controller() const { return p_->moodbar_controller_.ptr(); }
SharedPtr<MoodbarLoader> Application::moodbar_loader() const { return p_->moodbar_loader_.ptr(); } SharedPtr<MoodbarLoader> Application::moodbar_loader() const { return p_->moodbar_loader_.ptr(); }
#endif #endif
#ifdef HAVE_NETWORKREMOTE
SharedPtr<NetworkRemote> Application::network_remote() const { return p_->network_remote_.ptr(); }
#endif

View File

@@ -63,6 +63,9 @@ class RadioServices;
class MoodbarController; class MoodbarController;
class MoodbarLoader; class MoodbarLoader;
#endif #endif
#ifdef HAVE_NETWORKREMOTE
class NetworkRemote;
#endif
class Application : public QObject { class Application : public QObject {
Q_OBJECT Q_OBJECT
@@ -103,6 +106,10 @@ class Application : public QObject {
SharedPtr<MoodbarLoader> moodbar_loader() const; SharedPtr<MoodbarLoader> moodbar_loader() const;
#endif #endif
#ifdef HAVE_NETWORKREMOTE
SharedPtr<NetworkRemote> network_remote() const;
#endif
SharedPtr<LastFMImport> lastfm_import() const; SharedPtr<LastFMImport> lastfm_import() const;
void Exit(); void Exit();

24
src/core/bonjour.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef BONJOUR_H
#define BONJOUR_H
#include "zeroconf.h"
#ifdef __OBJC__
@class NetServicePublicationDelegate;
#else
class NetServicePublicationDelegate;
#endif // __OBJC__
class Bonjour : public Zeroconf {
public:
explicit Bonjour();
virtual ~Bonjour();
protected:
virtual void PublishInternal(const QString &domain, const QString &type, const QByteArray &name, const quint16 port);
private:
NetServicePublicationDelegate *delegate_;
};
#endif // BONJOUR_H

57
src/core/bonjour.mm Normal file
View File

@@ -0,0 +1,57 @@
#include "bonjour.h"
#import <Foundation/NSNetServices.h>
#import <Foundation/NSString.h>
#include "core/logging.h"
#include "core/scoped_nsautorelease_pool.h"
@interface NetServicePublicationDelegate : NSObject <NSNetServiceDelegate> {}
- (void)netServiceWillPublish:(NSNetService*)netService;
- (void)netService:(NSNetService*)netService didNotPublish:(NSDictionary*)errorDict;
- (void)netServiceDidStop:(NSNetService*)netService;
@end
@implementation NetServicePublicationDelegate
- (void)netServiceWillPublish:(NSNetService*)netService {
qLog(Debug) << "Publishing:" << [[netService name] UTF8String];
}
- (void)netService:(NSNetService*)netServie didNotPublish:(NSDictionary*)errorDict {
qLog(Debug) << "Failed to publish remote service with Bonjour";
NSLog(@"%@", errorDict);
}
- (void)netServiceDidStop:(NSNetService*)netService {
qLog(Debug) << "Unpublished:" << [[netService name] UTF8String];
}
@end
namespace {
NSString* NSStringFromQString(const QString& s) {
return [[NSString alloc] initWithUTF8String:s.toUtf8().constData()];
}
}
Bonjour::Bonjour() : delegate_([[NetServicePublicationDelegate alloc] init]) {}
Bonjour::~Bonjour() { [delegate_ release]; }
void Bonjour::PublishInternal(const QString& domain, const QString& type, const QByteArray& name, const quint16 port) {
ScopedNSAutoreleasePool pool;
NSNetService* service =
[[NSNetService alloc] initWithDomain:NSStringFromQString(domain)
type:NSStringFromQString(type)
name:[NSString stringWithUTF8String:name.constData()]
port:port];
if (service) {
[service setDelegate:delegate_];
[service publish];
}
}

View File

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

View File

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

View File

@@ -110,21 +110,32 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job, QString &error_te
bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) { bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
QString path = job.metadata_.url().toLocalFile(); const QString path = job.metadata_.url().toLocalFile();
QFileInfo fileInfo(path); const QFileInfo fileInfo(path);
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
if (job.use_trash_ && QFile::supportsMoveToTrash()) { if (job.use_trash_ && QFile::supportsMoveToTrash()) {
#else #else
if (job.use_trash_) { if (job.use_trash_) {
#endif #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()) { 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: public:
static void Init(); 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); 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: private:
explicit IconLoader() {} explicit IconLoader() {}
static bool system_icons_; static bool system_icons_;

View File

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

View File

@@ -58,7 +58,6 @@
#include <QShortcut> #include <QShortcut>
#include <QMessageBox> #include <QMessageBox>
#include <QErrorMessage> #include <QErrorMessage>
#include <QSettings>
#include <QColor> #include <QColor>
#include <QFrame> #include <QFrame>
#include <QItemSelectionModel> #include <QItemSelectionModel>
@@ -296,9 +295,6 @@ MainWindow::MainWindow(Application *app,
#ifdef HAVE_DISCORD_RPC #ifdef HAVE_DISCORD_RPC
discord_rich_presence_(discord_rich_presence), discord_rich_presence_(discord_rich_presence),
#endif #endif
error_dialog_([this]() {
return new ErrorDialog(this);
}),
console_([app, this]() { console_([app, this]() {
Console *console = new Console(app->database()); Console *console = new Console(app->database());
QObject::connect(console, &Console::Error, this, &MainWindow::ShowErrorDialog); QObject::connect(console, &Console::Error, this, &MainWindow::ShowErrorDialog);
@@ -700,6 +696,9 @@ MainWindow::MainWindow(Application *app,
QObject::connect(&*app_->task_manager(), &TaskManager::PauseCollectionWatchers, &*app_->collection(), &CollectionLibrary::PauseWatcher); 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_->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_->playlist_manager(), &PlaylistManager::CurrentSongChanged, &*app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::LoadAlbumCover);
QObject::connect(&*app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::AlbumCoverLoaded, this, &MainWindow::AlbumCoverLoaded); QObject::connect(&*app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::AlbumCoverLoaded, this, &MainWindow::AlbumCoverLoaded);
QObject::connect(album_cover_choice_controller_, &AlbumCoverChoiceController::Error, this, &MainWindow::ShowErrorDialog); QObject::connect(album_cover_choice_controller_, &AlbumCoverChoiceController::Error, this, &MainWindow::ShowErrorDialog);
@@ -978,29 +977,34 @@ MainWindow::MainWindow(Application *app,
ui_->action_open_cd->setVisible(false); ui_->action_open_cd->setVisible(false);
#endif #endif
#ifdef HAVE_NETWORKREMOTE
app_->network_remote();
#endif
// Load settings // Load settings
qLog(Debug) << "Loading 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 last used geometry to position window on the correct monitor
// Set window state only if the window was last maximized // Set window state only if the window was last maximized
if (settings_.contains("geometry")) { if (settings.contains("geometry")) {
restoreGeometry(settings_.value("geometry").toByteArray()); 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_->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 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; if (tab_mode == FancyTabWidget::Mode::None) tab_mode = default_mode;
ui_->tabs->SetMode(tab_mode); ui_->tabs->SetMode(tab_mode);
TabSwitched(); 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. // 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); ui_->splitter->setChildrenCollapsible(false);
@@ -1043,13 +1047,13 @@ MainWindow::MainWindow(Application *app,
case BehaviourSettings::StartupBehaviour::Remember: case BehaviourSettings::StartupBehaviour::Remember:
default:{ default:{
was_maximized_ = settings_.value(MainWindowSettings::kMaximized, true).toBool(); was_maximized_ = settings.value(MainWindowSettings::kMaximized, true).toBool();
if (was_maximized_) setWindowState(windowState() | Qt::WindowMaximized); 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 (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(); show();
} }
break; break;
@@ -1057,7 +1061,7 @@ MainWindow::MainWindow(Application *app,
} }
#endif #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_->sidebar_layout->setVisible(show_sidebar);
ui_->action_toggle_show_sidebar->setChecked(show_sidebar); ui_->action_toggle_show_sidebar->setChecked(show_sidebar);
@@ -1228,7 +1232,9 @@ void MainWindow::ReloadSettings() {
osd_->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 #ifdef HAVE_SUBSONIC
s.beginGroup(SubsonicSettings::kSettingsGroup); s.beginGroup(SubsonicSettings::kSettingsGroup);
@@ -1345,8 +1351,11 @@ void MainWindow::SaveSettings() {
ui_->playlist->view()->SaveSettings(); ui_->playlist->view()->SaveSettings();
app_->scrobbler()->WriteCache(); app_->scrobbler()->WriteCache();
settings_.setValue(MainWindowSettings::kShowSidebar, ui_->action_toggle_show_sidebar->isChecked()); Settings s;
settings_.setValue(MainWindowSettings::kSearchForCoverAuto, album_cover_choice_controller_->search_cover_auto_action()->isChecked()); 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 +1596,35 @@ void MainWindow::ToggleSidebar(const bool checked) {
ui_->sidebar_layout->setVisible(checked); ui_->sidebar_layout->setVisible(checked);
TabSwitched(); 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) { 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() { void MainWindow::SaveGeometry() {
if (!initialized_) return; if (!initialized_) return;
settings_.setValue(MainWindowSettings::kMaximized, isMaximized()); Settings s;
settings_.setValue(MainWindowSettings::kMinimized, isMinimized()); s.beginGroup(MainWindowSettings::kSettingsGroup);
settings_.setValue(MainWindowSettings::kHidden, isHidden()); s.setValue(MainWindowSettings::kMaximized, isMaximized());
settings_.setValue(MainWindowSettings::kGeometry, saveGeometry()); s.setValue(MainWindowSettings::kMinimized, isMinimized());
settings_.setValue(MainWindowSettings::kSplitterState, ui_->splitter->saveState()); s.setValue(MainWindowSettings::kHidden, isHidden());
s.setValue(MainWindowSettings::kGeometry, saveGeometry());
s.setValue(MainWindowSettings::kSplitterState, ui_->splitter->saveState());
s.endGroup();
} }
@@ -1745,7 +1766,12 @@ void MainWindow::SetHiddenInTray(const bool hidden) {
} }
void MainWindow::FilePathChanged(const QString &path) { 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) { void MainWindow::Seeked(const qint64 microseconds) {
@@ -2327,7 +2353,9 @@ void MainWindow::EditValue() {
void MainWindow::AddFile() { void MainWindow::AddFile() {
// Last used directory // 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()); PlaylistParser parser(app_->tagreader_client(), app_->collection_backend());
@@ -2337,7 +2365,7 @@ void MainWindow::AddFile() {
if (filenames.isEmpty()) return; if (filenames.isEmpty()) return;
// Save last used directory // Save last used directory
settings_.setValue("add_media_path", filenames[0]); s.setValue("add_media_path", filenames[0]);
// Convert to URLs // Convert to URLs
QList<QUrl> urls; QList<QUrl> urls;
@@ -2355,14 +2383,16 @@ void MainWindow::AddFile() {
void MainWindow::AddFolder() { void MainWindow::AddFolder() {
// Last used directory // 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 // Show dialog
directory = QFileDialog::getExistingDirectory(this, tr("Add folder"), directory); directory = QFileDialog::getExistingDirectory(this, tr("Add folder"), directory);
if (directory.isEmpty()) return; if (directory.isEmpty()) return;
// Save last used directory // Save last used directory
settings_.setValue("add_folder_path", directory); s.setValue("add_folder_path", directory);
// Add media // Add media
MimeData *mimedata = new MimeData; MimeData *mimedata = new MimeData;
@@ -3321,7 +3351,7 @@ void MainWindow::PlaylistDelete() {
if (DeleteConfirmationDialog::warning(files) != QDialogButtonBox::Yes) return; 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(); app_->player()->Stop();
} }

View File

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

View File

@@ -25,14 +25,15 @@
#include "mimedata.h" #include "mimedata.h"
MimeData::MimeData(const bool clear, const bool play_now, const bool enqueue, const bool enqueue_next_now, const bool open_in_new_playlist, QObject *parent) MimeData::MimeData(const bool clear, const bool play_now, const bool enqueue, const bool enqueue_next_now, const bool open_in_new_playlist, const int playlist_id, QObject *parent)
: override_user_settings_(false), : override_user_settings_(false),
clear_first_(clear), clear_first_(clear),
play_now_(play_now), play_now_(play_now),
enqueue_now_(enqueue), enqueue_now_(enqueue),
enqueue_next_now_(enqueue_next_now), enqueue_next_now_(enqueue_next_now),
open_in_new_playlist_(open_in_new_playlist), open_in_new_playlist_(open_in_new_playlist),
from_doubleclick_(false) { from_doubleclick_(false),
playlist_id_(playlist_id) {
Q_UNUSED(parent); Q_UNUSED(parent);

View File

@@ -29,7 +29,7 @@ class MimeData : public QMimeData {
Q_OBJECT Q_OBJECT
public: public:
explicit MimeData(const bool clear = false, const bool play_now = false, const bool enqueue = false, const bool enqueue_next_now = false, const bool open_in_new_playlist = false, QObject *parent = nullptr); explicit MimeData(const bool clear = false, const bool play_now = false, const bool enqueue = false, const bool enqueue_next_now = false, const bool open_in_new_playlist = false, const int playlist_id = -1, QObject *parent = nullptr);
// If this is set then MainWindow will not touch any of the other flags. // If this is set then MainWindow will not touch any of the other flags.
bool override_user_settings_; bool override_user_settings_;
@@ -57,6 +57,9 @@ class MimeData : public QMimeData {
// The MainWindow will set the above flags to the defaults set by the user. // The MainWindow will set the above flags to the defaults set by the user.
bool from_doubleclick_; bool from_doubleclick_;
// The Network Remote can use this MimeData to drop songs on another playlist than the one currently opened on the server
int playlist_id_;
// Returns a pretty name for a playlist containing songs described by this MimeData object. // Returns a pretty name for a playlist containing songs described by this MimeData object.
// By pretty name we mean the value of 'name_for_new_playlist_' or generic "Playlist" string if the 'name_for_new_playlist_' attribute is empty. // By pretty name we mean the value of 'name_for_new_playlist_' or generic "Playlist" string if the 'name_for_new_playlist_' attribute is empty.
QString get_name_for_new_playlist() const; QString get_name_for_new_playlist() const;

View File

@@ -29,6 +29,7 @@
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkInformation>
#include "networkaccessmanager.h" #include "networkaccessmanager.h"
#include "threadsafenetworkdiskcache.h" #include "threadsafenetworkdiskcache.h"
@@ -41,6 +42,22 @@ NetworkAccessManager::NetworkAccessManager(QObject *parent)
setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
setCache(new ThreadSafeNetworkDiskCache(this)); 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) { 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(); user_agent = network_request.header(QNetworkRequest::UserAgentHeader).toByteArray();
} }
else { else {
user_agent = QStringLiteral("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8(); user_agent = "Strawberry Music Player";
} }
QNetworkRequest new_network_request(network_request); 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 PlayPlaylistInternal(const EngineBase::TrackChangeFlags, const Playlist::AutoScroll autoscroll, const QString &playlist_name);
void FatalError(); void FatalError();
void ValidSongRequested(const QUrl&); void ValidSongRequested(const QUrl &url);
void InvalidSongRequested(const QUrl&); void InvalidSongRequested(const QUrl &url);
void HandleLoadResult(const UrlHandler::LoadResult &result); void HandleLoadResult(const UrlHandler::LoadResult &result);

View File

@@ -40,7 +40,6 @@ class QtFSListener : public FileSystemWatcherInterface {
private: private:
QFileSystemWatcher watcher_; QFileSystemWatcher watcher_;
}; };
#endif // QTFSLISTENER_H #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 // Only use then when you're certain the items currently in the pool are
// no longer needed. // no longer needed.
void Recycle(); void Recycle();
private: private:
NSAutoreleasePool *autorelease_pool_; 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_integrated_loudness_lufs_;
std::optional<double> ebur128_loudness_range_lu_; 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 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. 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), rating_(-1),
bpm_(-1), bpm_(-1),
id3v2_version_(0),
init_from_file_(false), init_from_file_(false),
suspicious_tags_(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_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_; } 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_title() { return &d->title_; }
QString *Song::mutable_album() { return &d->album_; } QString *Song::mutable_album() { return &d->album_; }
QString *Song::mutable_artist() { return &d->artist_; } 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_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_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_init_from_file(const bool v) { d->init_from_file_ = v; }
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = 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 { 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 { 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 { 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 { 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 { bool Song::performersort_supported() const {
@@ -818,7 +826,7 @@ bool Song::performersort_supported() const {
} }
bool Song::titlesort_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) { 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) { int Song::ColumnIndex(const QString &field) {
return static_cast<int>(kRowIdColumns.indexOf(field)); return static_cast<int>(kRowIdColumns.indexOf(field));
@@ -2150,4 +2162,3 @@ QString Song::GetNameForNewPlaylist(const SongList &songs) {
return result; return result;
} }

View File

@@ -234,6 +234,8 @@ class Song {
std::optional<double> ebur128_integrated_loudness_lufs() const; std::optional<double> ebur128_integrated_loudness_lufs() const;
std::optional<double> ebur128_loudness_range_lu() const; std::optional<double> ebur128_loudness_range_lu() const;
int id3v2_version() const;
QString *mutable_title(); QString *mutable_title();
QString *mutable_album(); QString *mutable_album();
QString *mutable_artist(); QString *mutable_artist();
@@ -349,6 +351,8 @@ class Song {
void set_ebur128_integrated_loudness_lufs(const std::optional<double> v); void set_ebur128_integrated_loudness_lufs(const std::optional<double> v);
void set_ebur128_loudness_range_lu(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_init_from_file(const bool v);
void set_stream_url(const QUrl &v); void set_stream_url(const QUrl &v);
@@ -439,6 +443,8 @@ class Song {
static bool save_embedded_cover_supported(const FileType filetype); 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 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 int ColumnIndex(const QString &field);
static QString JoinSpec(const QString &table); 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(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() { SongLoader::~SongLoader() {
@@ -106,6 +109,10 @@ SongLoader::~SongLoader() {
} }
void SongLoader::ParserError(const QString &error) {
errors_ << error;
}
SongLoader::Result SongLoader::Load(const QUrl &url) { SongLoader::Result SongLoader::Load(const QUrl &url) {
if (url.isEmpty()) return Result::Error; if (url.isEmpty()) return Result::Error;
@@ -287,6 +294,7 @@ SongLoader::Result SongLoader::LoadLocalAsync(const QString &filename) {
} }
if (parser) { // It's a playlist! 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(); qLog(Debug) << "Parsing using" << parser->name();
LoadPlaylist(parser, filename); LoadPlaylist(parser, filename);
return Result::Success; return Result::Success;
@@ -706,6 +714,10 @@ void SongLoader::MagicReady() {
StopTypefindAsync(true); StopTypefindAsync(true);
} }
if (parser_) {
QObject::connect(parser_, &ParserBase::Error, this, &SongLoader::ParserError, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
}
state_ = State::WaitingForData; state_ = State::WaitingForData;
if (!IsPipelinePlaying()) { if (!IsPipelinePlaying()) {
@@ -784,4 +796,3 @@ void SongLoader::CleanupPipeline() {
state_ = State::Finished; state_ = State::Finished;
} }

View File

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

View File

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

86
src/core/tinysvcmdns.cpp Normal file
View File

@@ -0,0 +1,86 @@
extern "C" {
#include "mdnsd.h"
}
#include <QObject>
#include <QList>
#include <QString>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QtEndian>
#include "tinysvcmdns.h"
#include "core/logging.h"
using namespace Qt::Literals::StringLiterals;
TinySVCMDNS::TinySVCMDNS(QObject *parent) : Zeroconf(parent) {
// Get all network interfaces
const QList<QNetworkInterface> network_interfaces = QNetworkInterface::allInterfaces();
for (const QNetworkInterface &network_interface : network_interfaces) {
// Only use up and non loopback interfaces
if (network_interface.flags().testFlag(network_interface.IsUp) && !network_interface.flags().testFlag(network_interface.IsLoopBack)) {
qLog(Debug) << "Interface" << network_interface.humanReadableName();
uint32_t ipv4 = 0;
QString ipv6;
// Now check all network addresses for this device
QList<QNetworkAddressEntry> network_address_entries = network_interface.addressEntries();
for (QNetworkAddressEntry network_address_entry : network_address_entries) {
QHostAddress host_address = network_address_entry.ip();
if (host_address.protocol() == QAbstractSocket::IPv4Protocol) {
ipv4 = qToBigEndian(host_address.toIPv4Address());
qLog(Debug) << " ipv4:" << host_address.toString();
}
else if (host_address.protocol() == QAbstractSocket::IPv6Protocol) {
ipv6 = host_address.toString();
qLog(Debug) << " ipv6:" << host_address.toString();
}
}
// Now start the service
CreateMdnsd(ipv4, ipv6);
}
}
}
TinySVCMDNS::~TinySVCMDNS() {
for (mdnsd *mdnsd : std::as_const(mdnsd_)) {
mdnsd_stop(mdnsd);
}
}
void TinySVCMDNS::CreateMdnsd(const uint32_t ipv4, const QString &ipv6) {
const QString host = QHostInfo::localHostName();
// Start the service
mdnsd *mdnsd = mdnsd_start();
// Set our hostname
const QString fullhostname = host + ".local"_L1;
mdnsd_set_hostname(mdnsd, fullhostname.toUtf8().constData(), ipv4);
// Add to the list
mdnsd_.append(mdnsd);
}
void TinySVCMDNS::PublishInternal(const QString &domain, const QString &type, const QByteArray &name, const quint16 port) {
// Some pointless text, so tinymDNS publishes the service correctly.
const char *txt[] = { "cat=nyan", nullptr };
for (mdnsd *mdnsd : mdnsd_) {
const QString fulltype = type + ".local"_L1;
mdnsd_register_svc(mdnsd, name.constData(), fulltype.toUtf8().constData(), port, nullptr, txt);
}
}

26
src/core/tinysvcmdns.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef TINYSVCMDNS_H
#define TINYSVCMDNS_H
#include <QList>
#include <QByteArray>
#include <QString>
#include "zeroconf.h"
struct mdnsd;
class TinySVCMDNS : public Zeroconf {
public:
explicit TinySVCMDNS(QObject *parent = nullptr);
virtual ~TinySVCMDNS();
protected:
virtual void PublishInternal(const QString &domain, const QString &type, const QByteArray &name, const quint16 port) override;
private:
void CreateMdnsd(const uint32_t ipv4, const QString &ipv6);
QList<mdnsd*> mdnsd_;
};
#endif // TINYSVCMDNS_H

View File

@@ -41,17 +41,19 @@ Translations::~Translations() {
} }
void Translations::LoadTranslation(const QString &prefix, const QString &path, const QString &language) { bool Translations::LoadTranslation(const QString &prefix, const QString &path, const QString &language) {
const QString basefilename = prefix + u'_' + language; const QString basefilename = prefix + u'_' + language;
QTranslator *t = new QTranslator; QTranslator *t = new QTranslator;
if (t->load(basefilename, path)) { if (!t->load(basefilename, path)) {
delete t;
return false;
}
qLog(Debug) << "Tranlations loaded from" << basefilename; qLog(Debug) << "Tranlations loaded from" << basefilename;
QCoreApplication::installTranslator(t); QCoreApplication::installTranslator(t);
translations_ << t; translations_ << t;
}
else { return true;
delete t;
}
} }

View File

@@ -31,7 +31,7 @@ class Translations {
public: public:
explicit Translations(); explicit Translations();
~Translations(); ~Translations();
void LoadTranslation(const QString &prefix, const QString &path, const QString &language); bool LoadTranslation(const QString &prefix, const QString &path, const QString &language);
private: private:
QList<QTranslator*> translations_; QList<QTranslator*> translations_;

View File

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

View File

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

69
src/core/zeroconf.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include "config.h"
#include <QObject>
#include <QByteArray>
#include <QString>
#ifdef HAVE_DBUS
# include "avahi/avahi.h"
#endif
#ifdef Q_OS_DARWIN
# include "bonjour.h"
#endif
#ifdef Q_OS_WIN32
# include "tinysvcmdns.h"
#endif
#include "zeroconf.h"
Zeroconf *Zeroconf::sInstance = nullptr;
Zeroconf::Zeroconf(QObject *parent) : QObject(parent) {}
Zeroconf::~Zeroconf() = default;
Zeroconf *Zeroconf::GetZeroconf() {
if (!sInstance) {
#ifdef HAVE_DBUS
sInstance = new Avahi;
#endif // HAVE_DBUS
#ifdef Q_OS_DARWIN
sInstance = new Bonjour;
#endif
#ifdef Q_OS_WIN32
sInstance = new TinySVCMDNS;
#endif
}
return sInstance;
}
QByteArray Zeroconf::TruncateName(const QString &name) {
QByteArray truncated_utf8;
for (const QChar c : name) {
if (truncated_utf8.size() + 1 >= 63) {
break;
}
truncated_utf8 += c.toLatin1();
}
// NULL-terminate the string.
truncated_utf8.append('\0');
return truncated_utf8;
}
void Zeroconf::Publish(const QString &domain, const QString &type, const QString &name, quint16 port) {
const QByteArray truncated_name = TruncateName(name);
PublishInternal(domain, type, truncated_name, port);
}

28
src/core/zeroconf.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef ZEROCONF_H
#define ZEROCONF_H
#include <QObject>
#include <QByteArray>
#include <QString>
class Zeroconf : public QObject {
public:
explicit Zeroconf(QObject *parent);
virtual ~Zeroconf();
void Publish(const QString &domain, const QString &type, const QString &name, quint16 port);
static Zeroconf *GetZeroconf();
// Truncate a QString to 63 bytes of UTF-8.
static QByteArray TruncateName(const QString &name);
protected:
virtual void PublishInternal(const QString &domain, const QString &type, const QByteArray &name, quint16 port) = 0;
private:
static Zeroconf *sInstance;
};
#endif // ZEROCONF_H

View File

@@ -812,8 +812,8 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
SaveCoverEmbeddedToCollectionSongs(*song, result); SaveCoverEmbeddedToCollectionSongs(*song, result);
break; break;
} }
}
[[fallthrough]]; [[fallthrough]];
}
case CoverOptions::CoverType::Cache: case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:{ case CoverOptions::CoverType::Album:{
cover_url = SaveCoverToFileAutomatic(song, result); cover_url = SaveCoverToFileAutomatic(song, result);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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