Compare commits

..

42 Commits
0.5.1 ... 0.5.2

Author SHA1 Message Date
Jonas Kvinge
1a8fe18b8e Release 0.5.2 2019-01-26 18:13:04 +01:00
Jonas Kvinge
4599b4a9cc Fix thanks to 2019-01-26 18:10:04 +01:00
Jonas Kvinge
6ab6ab56dd Fix define 2019-01-26 17:56:18 +01:00
Jonas Kvinge
59f5dc21ac Update Changelog 2019-01-26 17:45:52 +01:00
Jonas Kvinge
8f316db49c Add raise() to make sure window is on top 2019-01-26 17:44:05 +01:00
Jonas Kvinge
4c2f28311c Remove commented code 2019-01-26 17:33:47 +01:00
Jonas Kvinge
6ae022fbec Update Changelog 2019-01-26 17:31:05 +01:00
Jonas Kvinge
cfbb0f8fc0 Update copyright 2019-01-26 17:30:54 +01:00
Jonas Kvinge
3ad2c2ad6a Update copyright 2019-01-26 17:30:44 +01:00
Jonas Kvinge
1ce553fb65 Copy album covers to file system devices 2019-01-26 17:18:26 +01:00
Jonas Kvinge
ec5ec83edc Change transcoder output file and add missing suffix 2019-01-26 14:57:25 +01:00
Jonas Kvinge
18087fd797 Update Changlog 2019-01-25 23:46:13 +01:00
Jonas Kvinge
f90de75e3a Add warning when enabling X11 shortcuts on gnome, cinnamon and KDE 2019-01-25 21:36:28 +01:00
Jonas Kvinge
3241815a11 Remove core/tagreaderclient.h include 2019-01-24 20:29:22 +01:00
Jonas Kvinge
db3f8d3b83 Fix uninitialised variables 2019-01-24 20:22:05 +01:00
Jonas Kvinge
9983dc3138 Rename globalshortcuts GSD D-Bus backend to avoid confusion 2019-01-24 20:17:50 +01:00
Jonas Kvinge
ad141c165b Update imobiledevice 2019-01-24 19:43:52 +01:00
Jonas Kvinge
93b252b9e7 Update README.md 2019-01-24 19:42:04 +01:00
Jonas Kvinge
42e245e334 Update README.md 2019-01-24 19:41:21 +01:00
Jonas Kvinge
cb844084e8 Add log to organise error dialog 2019-01-24 19:20:10 +01:00
Jonas Kvinge
3483736490 Fix remove misc from album title 2019-01-24 19:18:23 +01:00
Jonas Kvinge
119a75588e Fix aac mp4 transcoder 2019-01-24 19:16:39 +01:00
Jonas Kvinge
2f42242305 Use vbr by default 2019-01-24 19:14:51 +01:00
Jonas Kvinge
4990f44b10 Save album cover to gpod devices 2019-01-24 19:13:57 +01:00
Jonas Kvinge
85eba24167 Add iLister and AFC device 2019-01-24 19:10:42 +01:00
Jonas Kvinge
ce4be75803 Fix itdb track type 2019-01-24 19:10:10 +01:00
Jonas Kvinge
706529248a Fix find_path check for X11 headers 2019-01-24 18:25:54 +01:00
Jonas Kvinge
79f50f5343 Remove compile warning when missing X11/XF86keysym.h 2019-01-24 18:25:24 +01:00
Jonas Kvinge
7437c208ff Remove remastered from album title 2019-01-22 22:49:48 +01:00
Jonas Kvinge
b838893e88 Remove libQt5Gui-private-headers-devel 2019-01-22 18:16:46 +01:00
Jonas Kvinge
41e2a75675 Add error handling for mtp and gpod device 2019-01-21 18:58:54 +01:00
Jonas Kvinge
ad5e366aad Use ItemToIndex and fix memory leaks in devices 2019-01-21 17:44:37 +01:00
Jonas Kvinge
b35c641df6 Add libgstlibav.so to macdeploy 2019-01-19 22:02:56 +01:00
Jonas Kvinge
22c476d507 Add libgstlame.so to macdeploy (#63) 2019-01-19 18:00:47 +01:00
Jonas Kvinge
8f9b1d708b Update .travis.yml (#62) 2019-01-19 00:55:03 +01:00
Jonas Kvinge
9ce8b60cd2 unset dirs 2019-01-18 01:00:07 +01:00
Jonas Kvinge
9401ad2ba3 Check for libsingleapplication too 2019-01-18 00:40:54 +01:00
Jonas Kvinge
128b6c7ce9 Update Changelog 2019-01-13 14:48:01 +01:00
Jonas Kvinge
04d509f6eb Also do secondary check as a core app 2019-01-13 14:46:22 +01:00
Jonas Kvinge
c91cef3507 Add error handling/message for url handler 2019-01-13 00:06:08 +01:00
Jonas Kvinge
a9304a840f Remove libQt5Gui-private-headers-devel 2019-01-12 17:55:02 +01:00
Jonas Kvinge
3ba500b589 Turn back git revision 2019-01-12 17:45:13 +01:00
77 changed files with 879 additions and 495 deletions

View File

@@ -12,30 +12,37 @@ compiler:
before_install: before_install:
- echo $DEPLOY_KEY_ENC | base64 --decode | openssl aes-256-cbc -K $encrypted_83a41ac424a6_key -iv $encrypted_83a41ac424a6_iv -out ~/.ssh/id_rsa -d - echo $DEPLOY_KEY_ENC | base64 --decode | openssl aes-256-cbc -K $encrypted_83a41ac424a6_key -iv $encrypted_83a41ac424a6_iv -out ~/.ssh/id_rsa -d
- chmod 600 ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker build -f Dockerfile -t strawberry-build . ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run --name build -itd strawberry-build /bin/bash ; fi docker build -f Dockerfile -t strawberry-build .;
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build git clone https://github.com/jonaski/strawberry ; fi docker run --name build -itd strawberry-build /bin/bash;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then git fetch --unshallow ; fi docker exec build git clone https://github.com/jonaski/strawberry;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then git pull ; fi fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink python ; fi git fetch --unshallow;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install glib pkgconfig protobuf protobuf-c qt ; fi git pull;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install sqlite --with-fts ; fi brew update;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install gstreamer gst-plugins-base ; fi brew unlink python;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install gst-plugins-good --with-flac ; fi brew install glib pkgconfig protobuf protobuf-c qt;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install gst-plugins-bad gst-plugins-ugly gst-libav ; fi brew install sqlite --with-fts;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install chromaprint ; fi brew install gstreamer gst-plugins-base;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libcdio libmtp libimobiledevice libplist ; fi brew install gst-plugins-good --with-flac;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export Qt5_DIR=/usr/local/opt/qt5/lib/cmake ; fi brew install gst-plugins-bad gst-plugins-ugly gst-libav;
brew install chromaprint;
brew install libcdio libmtp libimobiledevice libplist;
export Qt5_DIR=/usr/local/opt/qt5/lib/cmake;
ls /usr/local/lib/gstreamer-1.0;
fi
before_script: before_script:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build cmake -Hstrawberry -Bbuild -DENABLE_STREAM_DEEZER=ON ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build cmake -Hstrawberry -Bbuild -DENABLE_STREAM_DEEZER=ON ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir build; cd build; cmake .. -DUSE_BUNDLE=ON -DENABLE_STREAM_DEEZER=ON ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir build; cd build; cmake .. -DUSE_BUNDLE=ON -DENABLE_STREAM_DEEZER=ON ; fi
script: script:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build make -C build -j8 ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build make -C build -j8 ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make -j8 ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo make install ; fi make -j8;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo ../dist/macos/macdeploy.py strawberry.app ; fi sudo make install;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ../dist/macos/create-dmg.sh strawberry.app ; fi sudo ../dist/macos/macdeploy.py strawberry.app;
../dist/macos/create-dmg.sh strawberry.app;
fi
after_success: after_success:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ls -lh strawberry.dmg; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ls -lh strawberry.dmg; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [[ "$TRAVIS_BRANCH" == "master" ]]; then rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [[ "$TRAVIS_BRANCH" == "master" ]]; then rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos; fi

View File

@@ -55,6 +55,8 @@ SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
{ {
server = nullptr; server = nullptr;
socket = nullptr; socket = nullptr;
memory = nullptr;
instanceNumber = -1;
} }
SingleApplicationPrivate::~SingleApplicationPrivate() SingleApplicationPrivate::~SingleApplicationPrivate()

View File

@@ -55,6 +55,8 @@ SingleCoreApplicationPrivate::SingleCoreApplicationPrivate( SingleCoreApplicatio
{ {
server = nullptr; server = nullptr;
socket = nullptr; socket = nullptr;
memory = nullptr;
instanceNumber = -1;
} }
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate()

View File

@@ -152,8 +152,8 @@ if(WIN32)
endif() endif()
if(X11_FOUND) if(X11_FOUND)
find_path(KEYSYMDEF_H keysymdef.h PATHS ${X11_INCLUDE_DIR}) find_path(KEYSYMDEF_H NAMES "keysymdef.h" PATHS "${X11_INCLUDE_DIR}" PATH_SUFFIXES "X11")
find_path(XF86KEYSYM_H XF86keysym.h PATHS ${X11_INCLUDE_DIR}) find_path(XF86KEYSYM_H NAMES "XF86keysym.h" PATHS "${XCB_INCLUDEDIR}" PATH_SUFFIXES "X11")
if(KEYSYMDEF_H) if(KEYSYMDEF_H)
set(HAVE_KEYSYMDEF_H ON) set(HAVE_KEYSYMDEF_H ON)
else() else()
@@ -203,7 +203,13 @@ endif()
# SingleApplication # SingleApplication
pkg_check_modules(SINGLEAPPLICATION singleapplication) pkg_check_modules(SINGLEAPPLICATION singleapplication)
if (NOT SINGLEAPPLICATION_FOUND)
pkg_check_modules(SINGLEAPPLICATION libsingleapplication)
endif()
pkg_check_modules(SINGLECOREAPPLICATION singlecoreapplication) pkg_check_modules(SINGLECOREAPPLICATION singlecoreapplication)
if (NOT SINGLECOREAPPLICATION_FOUND)
pkg_check_modules(SINGLECOREAPPLICATION libsinglecoreapplication)
endif()
if (SINGLEAPPLICATION_FOUND AND SINGLECOREAPPLICATION_FOUND) if (SINGLEAPPLICATION_FOUND AND SINGLECOREAPPLICATION_FOUND)
option(USE_SYSTEM_SINGLEAPPLICATION "Use system SingleApplication/SingleCoreApplication libraries" ON) option(USE_SYSTEM_SINGLEAPPLICATION "Use system SingleApplication/SingleCoreApplication libraries" ON)
else(SINGLEAPPLICATION_FOUND AND SINGLECOREAPPLICATION_FOUND) else(SINGLEAPPLICATION_FOUND AND SINGLECOREAPPLICATION_FOUND)
@@ -220,6 +226,8 @@ else(USE_SYSTEM_SINGLEAPPLICATION)
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication) set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication)
set(SINGLEAPPLICATION_LIBRARIES singleapplication) set(SINGLEAPPLICATION_LIBRARIES singleapplication)
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication) set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
unset(SINGLEAPPLICATION_LIBRARY_DIRS)
unset(SINGLECOREAPPLICATION_LIBRARY_DIRS)
endif(USE_SYSTEM_SINGLEAPPLICATION) endif(USE_SYSTEM_SINGLEAPPLICATION)
# Qocoa # Qocoa
@@ -326,11 +334,11 @@ optional_component(LIBMTP ON "Devices: MTP support"
DEPENDS "libmtp" LIBMTP_FOUND DEPENDS "libmtp" LIBMTP_FOUND
) )
optional_component(IMOBILEDEVICE ON "Devices: iPod Touch, iPhone, iPad support" optional_component(IMOBILEDEVICE ON "Devices: iPhone, iPod Touch, iPad and Apple TV support"
DEPENDS "libimobiledevice" LIBIMOBILEDEVICE_FOUND DEPENDS "libimobiledevice" LIBIMOBILEDEVICE_FOUND
DEPENDS "libplist" LIBPLIST_FOUND DEPENDS "libplist" LIBPLIST_FOUND
DEPENDS "libusbmuxd" LIBUSBMUXD_FOUND DEPENDS "libusbmuxd" LIBUSBMUXD_FOUND
DEPENDS "iPod classic support" LIBGPOD_FOUND DEPENDS "libgpod" LIBGPOD_FOUND
) )
optional_component(SPARKLE ON "Sparkle integration" optional_component(SPARKLE ON "Sparkle integration"
@@ -364,9 +372,6 @@ if (APPLE AND USE_BUNDLE AND NOT USE_BUNDLE_DIR)
set(USE_BUNDLE_DIR "../PlugIns") set(USE_BUNDLE_DIR "../PlugIns")
endif() endif()
#if(IMOBILEDEVICE_FOUND AND PLIST_FOUND)
#add_subdirectory(ext/gstafc)
#endif(IMOBILEDEVICE_FOUND AND PLIST_FOUND)
# Set up definitions and paths # Set up definitions and paths

View File

@@ -2,6 +2,23 @@ Strawberry Music Player
======================= =======================
ChangeLog ChangeLog
Version 0.5.2:
* Added error handling and message for URL handler
* Added SingleCoreApplication secondary check
* Fixed memory leaks in devices
* Fixed more stability issues in devices
* Remove remastered from album title when searching for lyrics, covers and scrobbling
* Fixed CMake check for X11 headers
* Enabled iLister and AFC device
* Added saving of album cover to gpod devices
* Fixed AAC/MP4 transcoder
* Added log to organise error dialog
* Added warning when enabling X11 shortcuts on Gnome, Cinnamon or KDE
* Fixed transcoder to use filename suffixes to solve issues when copying files to iPods
* Added option to copy album cover in organise dialog (filesystem and libgpod devices)
* Added raise() to make sure window is on top when strawberry is started twice
Version 0.5.1: Version 0.5.1:
* Added scrobbler with support for Last.fm, Libre.fm and ListenBrainz * Added scrobbler with support for Last.fm, Libre.fm and ListenBrainz

View File

@@ -9,7 +9,7 @@ run zypper --non-interactive --gpg-auto-import-keys install \
boost-devel protobuf-devel sqlite3-devel taglib-devel \ boost-devel protobuf-devel sqlite3-devel taglib-devel \
gstreamer-devel gstreamer-plugins-base-devel libxine-devel vlc-devel \ gstreamer-devel gstreamer-plugins-base-devel libxine-devel vlc-devel \
libQt5Core-devel libQt5Gui-devel libQt5Widgets-devel libQt5Concurrent-devel libQt5Network-devel libQt5Sql-devel \ libQt5Core-devel libQt5Gui-devel libQt5Widgets-devel libQt5Concurrent-devel libQt5Network-devel libQt5Sql-devel \
libQt5DBus-devel libqt5-qtx11extras-devel libQt5Gui-private-headers-devel libqt5-qtbase-common-devel \ libQt5DBus-devel libqt5-qtx11extras-devel libqt5-qtbase-common-devel \
libcdio-devel libgpod-devel libplist-devel libmtp-devel libusbmuxd-devel libchromaprint-devel libcdio-devel libgpod-devel libplist-devel libmtp-devel libusbmuxd-devel libchromaprint-devel
run mkdir -p /usr/src/app run mkdir -p /usr/src/app

View File

@@ -48,6 +48,14 @@ To build Strawberry from source you need the following installed on your system
* [ALSA library (linux)](https://www.alsa-project.org/) * [ALSA library (linux)](https://www.alsa-project.org/)
* [DBus (linux)](https://www.freedesktop.org/wiki/Software/dbus/) * [DBus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
* [PulseAudio (linux optional)](https://www.freedesktop.org/wiki/Software/PulseAudio/?) * [PulseAudio (linux optional)](https://www.freedesktop.org/wiki/Software/PulseAudio/?)
* [GStreamer](https://gstreamer.freedesktop.org/), [Xine](https://www.xine-project.org), [VLC](https://www.videolan.org), [Deezer](https://build-repo.deezer.com/native_sdk/deezer-native-sdk-v1.2.10.zip) or [Phonon](https://techbase.kde.org/Phonon)
Optional dependencies:
* Audio CD: [libcdio](https://www.gnu.org/software/libcdio/)
* MTP devices: [libmtp](http://libmtp.sourceforge.net/)
* iPod Classic devices: [libgpod](http://www.gtkpod.org/libgpod/)
* iPhone, iPod Touch, iPad and Apple TV devices: [libimobiledevice, libplist and libusbmuxd](https://www.libimobiledevice.org/)
Either GStreamer, Xine, VLC, Deezer or Phonon engine is required, but only GStreamer is fully implemented so far. Either GStreamer, Xine, VLC, Deezer or Phonon engine is required, but only GStreamer is fully implemented so far.
You should also install the gstreamer plugins base and good, and optionally bad and ugly. You should also install the gstreamer plugins base and good, and optionally bad and ugly.
@@ -56,12 +64,6 @@ Deezer streams with full songs are encrypted and only urls for preview streams (
Full length songs requires the use of deezers own engine (Deezer SDK). Full length songs requires the use of deezers own engine (Deezer SDK).
The Deezer SDK can be found here: https://build-repo.deezer.com/native_sdk/deezer-native-sdk-v1.2.10.zip The Deezer SDK can be found here: https://build-repo.deezer.com/native_sdk/deezer-native-sdk-v1.2.10.zip
Optional:
* [libcdio](https://www.gnu.org/software/libcdio/) - To enable Audio CD support
* [libmtp](http://libmtp.sourceforge.net/) - MTP support.
* [libgpod](http://www.gtkpod.org/libgpod/) - iPod Classic support.
### :wrench: Compiling from source ### :wrench: Compiling from source
### Get the code: ### Get the code:
@@ -78,6 +80,10 @@ Optional:
(dont change to the source directory, if you created the build directory inside the source directory type: cmake .. instead). (dont change to the source directory, if you created the build directory inside the source directory type: cmake .. instead).
### :penguin: Packaging status
[![Packaging status](https://repology.org/badge/vertical-allrepos/strawberry.svg)](https://repology.org/metapackage/strawberry/versions)
### :computer: Screenshot ### :computer: Screenshot

View File

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

10
dist/debian/copyright vendored
View File

@@ -59,6 +59,8 @@ Files: src/core/main.h
src/scrobbler/* src/scrobbler/*
src/tidal/* src/tidal/*
src/deezer/* src/deezer/*
transcoderoptionswavpack.cpp
transcoderoptionswavpack.h
Copyright: 2012-2014, 2017-2018, Jonas Kvinge <jonas@jkvinge.net> Copyright: 2012-2014, 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
License: GPL-3+ License: GPL-3+
@@ -114,6 +116,14 @@ Files: src/core/main.cpp
src/globalshortcuts/globalshortcuts.h src/globalshortcuts/globalshortcuts.h
src/settings/shortcutssettingspage.cpp src/settings/shortcutssettingspage.cpp
src/settings/shortcutssettingspage.h src/settings/shortcutssettingspage.h
src/organise/organise.cpp
src/organise/organise.h
src/organise/organisedialog.cpp
src/organise/organisedialog.h
src/organise/organiseerrordialog.cpp
src/organise/organiseerrordialog.h
src/transcoder/transcoder.cpp
src/transcoder/transcoder.h
Copyright: 2010, 2012-2014 David Sansome <me@davidsansome.com> Copyright: 2010, 2012-2014 David Sansome <me@davidsansome.com>
2012-2014, 2017-2018 Jonas Kvinge <jonas@jkvinge.net> 2012-2014, 2017-2018 Jonas Kvinge <jonas@jkvinge.net>
License: GPL-3+ License: GPL-3+

View File

@@ -100,6 +100,8 @@ GSTREAMER_PLUGINS = [
'libgsttaglib.so', 'libgsttaglib.so',
'libgstvorbis.so', 'libgstvorbis.so',
'libgstisomp4.so', 'libgstisomp4.so',
'libgstlame.so',
'libgstlibav.so',
] ]

View File

@@ -18,7 +18,6 @@ BuildRequires: desktop-file-utils
BuildRequires: appstream-glib BuildRequires: appstream-glib
BuildRequires: gcc-c++ BuildRequires: gcc-c++
BuildRequires: hicolor-icon-theme BuildRequires: hicolor-icon-theme
BuildRequires: libQt5Gui-private-headers-devel
BuildRequires: make BuildRequires: make
BuildRequires: git BuildRequires: git
BuildRequires: pkgconfig BuildRequires: pkgconfig

View File

@@ -527,8 +527,8 @@ if(HAVE_GLOBALSHORTCUTS)
SOURCES globalshortcuts/globalshortcut-x11.cpp SOURCES globalshortcuts/globalshortcut-x11.cpp
) )
optional_source(HAVE_DBUS optional_source(HAVE_DBUS
SOURCES globalshortcuts/globalshortcutbackend-dbus.cpp SOURCES globalshortcuts/globalshortcutbackend-gsd.cpp
HEADERS globalshortcuts/globalshortcutbackend-dbus.h HEADERS globalshortcuts/globalshortcutbackend-gsd.h
) )
optional_source(WIN32 optional_source(WIN32
SOURCES globalshortcuts/globalshortcut-win.cpp SOURCES globalshortcuts/globalshortcut-win.cpp

View File

@@ -2,6 +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-2019, 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
@@ -44,6 +45,13 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
const QFileInfo src = QFileInfo(job.source_); const QFileInfo src = QFileInfo(job.source_);
const QFileInfo dest = QFileInfo(root_ + "/" + job.destination_); const QFileInfo dest = QFileInfo(root_ + "/" + job.destination_);
QFileInfo cover_src;
QFileInfo cover_dest;
if (job.albumcover_ && !job.cover_source_.isEmpty() && !job.cover_dest_.isEmpty()) {
cover_src = QFileInfo(job.cover_source_);
cover_dest = QFileInfo(root_ + "/" + job.cover_dest_);
}
// Don't do anything if the destination is the same as the source // Don't do anything if the destination is the same as the source
if (src == dest) return true; if (src == dest) return true;
@@ -55,13 +63,29 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
} }
// Remove the destination file if it exists and we want to overwrite // Remove the destination file if it exists and we want to overwrite
if (job.overwrite_ && dest.exists()) QFile::remove(dest.absoluteFilePath()); if (job.overwrite_) {
if (dest.exists()) QFile::remove(dest.absoluteFilePath());
if (!cover_dest.filePath().isEmpty() && cover_dest.exists()) QFile::remove(cover_dest.absoluteFilePath());
}
// Copy or move // Copy or move
if (job.remove_original_) bool result(true);
return QFile::rename(src.absoluteFilePath(), dest.absoluteFilePath()); if (job.remove_original_) {
else result = QFile::rename(src.absoluteFilePath(), dest.absoluteFilePath());
return QFile::copy(src.absoluteFilePath(), dest.absoluteFilePath()); if (!cover_src.filePath().isEmpty() && !cover_dest.filePath().isEmpty()) {
QFile::rename(cover_src.absoluteFilePath(), cover_dest.absoluteFilePath());
}
}
else {
if (!dest.exists()) {
result = QFile::copy(src.absoluteFilePath(), dest.absoluteFilePath());
}
if (!cover_src.filePath().isEmpty() && !cover_dest.filePath().isEmpty() && !cover_dest.exists()) {
QFile::copy(cover_src.absoluteFilePath(), cover_dest.absoluteFilePath());
}
}
return result;
} }
@@ -76,4 +100,3 @@ bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
return QFile::remove(path); return QFile::remove(path);
} }

View File

@@ -2,6 +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-2019, 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
@@ -43,4 +44,3 @@ private:
}; };
#endif // FILESYSTEMMUSICSTORAGE_H #endif // FILESYSTEMMUSICSTORAGE_H

View File

@@ -1774,6 +1774,7 @@ void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByt
options.Load(string_options); options.Load(string_options);
if (options.is_empty()) { if (options.is_empty()) {
raise();
show(); show();
activateWindow(); activateWindow();
} }

View File

@@ -20,6 +20,4 @@
#include "musicstorage.h" #include "musicstorage.h"
MusicStorage::MusicStorage() MusicStorage::MusicStorage() {}
{
}

View File

@@ -61,6 +61,9 @@ class MusicStorage {
bool overwrite_; bool overwrite_;
bool mark_as_listened_; bool mark_as_listened_;
bool remove_original_; bool remove_original_;
bool albumcover_;
QString cover_source_;
QString cover_dest_;
ProgressFunction progress_; ProgressFunction progress_;
}; };

View File

@@ -235,6 +235,12 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
if (item->Url() != result.original_url_) return; if (item->Url() != result.original_url_) return;
switch (result.type_) { switch (result.type_) {
case UrlHandler::LoadResult::Error:
loading_async_ = QUrl();
EngineStateChanged(Engine::Error);
FatalError();
emit Error(result.error_);
break;
case UrlHandler::LoadResult::NoMoreTracks: case UrlHandler::LoadResult::NoMoreTracks:
qLog(Debug) << "URL handler for" << result.original_url_ << "said no more tracks"; qLog(Debug) << "URL handler for" << result.original_url_ << "said no more tracks";
@@ -703,13 +709,17 @@ void Player::TrackAboutToEnd() {
if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) { if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url); UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url);
switch (result.type_) { switch (result.type_) {
case UrlHandler::LoadResult::Error:
loading_async_ = QUrl();
EngineStateChanged(Engine::Error);
FatalError();
emit Error(result.error_);
return;
case UrlHandler::LoadResult::NoMoreTracks: case UrlHandler::LoadResult::NoMoreTracks:
return; return;
case UrlHandler::LoadResult::WillLoadAsynchronously: case UrlHandler::LoadResult::WillLoadAsynchronously:
loading_async_ = url; loading_async_ = url;
return; return;
case UrlHandler::LoadResult::TrackAvailable: case UrlHandler::LoadResult::TrackAvailable:
url = result.media_url_; url = result.media_url_;
break; break;

View File

@@ -142,7 +142,9 @@ const QString Song::kFtsUpdateSpec = Utilities::Updateify(Song::kFtsColumns).joi
const QString Song::kManuallyUnsetCover = "(unset)"; const QString Song::kManuallyUnsetCover = "(unset)";
const QString Song::kEmbeddedCover = "(embedded)"; const QString Song::kEmbeddedCover = "(embedded)";
const QRegExp Song::kCoverRemoveDisc(" ?-? ((\\(|\\[)?)(Disc|CD) ?([0-9]{1,2})((\\)|\\])?)$"); const QRegExp Song::kAlbumRemoveDisc(" ?-? ((\\(|\\[)?)(Disc|CD) ?([0-9]{1,2})((\\)|\\])?)$");
const QRegExp Song::kAlbumRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered) ?((\\)|\\])?)$");
const QRegExp Song::kTitleRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|Live) ?((\\)|\\])?)$");
struct Song::Private : public QSharedData { struct Song::Private : public QSharedData {
@@ -820,7 +822,7 @@ void Song::InitFromFilePartial(const QString &filename) {
void Song::InitArtManual() { void Song::InitArtManual() {
QString album2 = d->album_; QString album2 = d->album_;
album2.remove(Song::kCoverRemoveDisc); album2.remove(Song::kAlbumRemoveDisc);
//qLog(Debug) << __PRETTY_FUNCTION__ << d->artist_ << d->album_ << album2; //qLog(Debug) << __PRETTY_FUNCTION__ << d->artist_ << d->album_ << album2;
@@ -899,10 +901,9 @@ void Song::ToItdb(Itdb_Track *track) const {
track->bitrate = d->bitrate_; track->bitrate = d->bitrate_;
track->samplerate = d->samplerate_; track->samplerate = d->samplerate_;
//track->bithdepth = d->bithdepth_;
track->type1 = 0; track->type1 = (d->filetype_ == FileType_MPEG ? 1 : 0);
track->type2 = d->filetype_ == FileType_MP4 ? 0 : 1; track->type2 = (d->filetype_ == FileType_MPEG ? 1 : 0);
track->mediatype = 1; // Audio track->mediatype = 1; // Audio
track->size = d->filesize_; track->size = d->filesize_;
track->time_modified = d->mtime_; track->time_modified = d->mtime_;

View File

@@ -83,7 +83,9 @@ class Song {
static const QString kManuallyUnsetCover; static const QString kManuallyUnsetCover;
static const QString kEmbeddedCover; static const QString kEmbeddedCover;
static const QRegExp kCoverRemoveDisc; static const QRegExp kAlbumRemoveDisc;
static const QRegExp kAlbumRemoveMisc;
static const QRegExp kTitleRemoveMisc;
static const QRegExp kFilenameRemoveNonFatChars; static const QRegExp kFilenameRemoveNonFatChars;
static QString JoinSpec(const QString &table); static QString JoinSpec(const QString &table);

View File

@@ -28,6 +28,6 @@
#include "song.h" #include "song.h"
#include "urlhandler.h" #include "urlhandler.h"
UrlHandler::LoadResult::LoadResult(const QUrl &original_url, Type type, const QUrl &media_url, const Song::FileType &filetype, qint64 length_nanosec) : original_url_(original_url), type_(type), media_url_(media_url), filetype_(filetype), length_nanosec_(length_nanosec) {} UrlHandler::LoadResult::LoadResult(const QUrl &original_url, Type type, const QUrl &media_url, const Song::FileType &filetype, const qint64 length_nanosec, const QString error) : original_url_(original_url), type_(type), media_url_(media_url), filetype_(filetype), length_nanosec_(length_nanosec), error_(error) {}
UrlHandler::UrlHandler(QObject *parent) : QObject(parent) {} UrlHandler::UrlHandler(QObject *parent) : QObject(parent) {}

View File

@@ -52,9 +52,12 @@ class UrlHandler : public QObject {
// There was a track available. Its url is in media_url. // There was a track available. Its url is in media_url.
TrackAvailable, TrackAvailable,
// There was a error
Error,
}; };
LoadResult(const QUrl &original_url = QUrl(), Type type = NoMoreTracks, const QUrl &media_url = QUrl(), const Song::FileType &filetype = Song::FileType_Stream, qint64 length_nanosec_ = -1); LoadResult(const QUrl &original_url = QUrl(), Type type = NoMoreTracks, const QUrl &media_url = QUrl(), const Song::FileType &filetype = Song::FileType_Stream, const qint64 length_nanosec_ = -1, const QString error = QString());
// The url that the playlist item has in Url(). // The url that the playlist item has in Url().
// Might be something unplayable like lastfm://... // Might be something unplayable like lastfm://...
@@ -70,6 +73,9 @@ class UrlHandler : public QObject {
// Track length, if we are able to get it only now // Track length, if we are able to get it only now
qint64 length_nanosec_; qint64 length_nanosec_;
// Error message, if any
QString error_;
}; };
// Called by the Player when a song starts loading - gives the handler a chance to do something clever to get a playable track. // Called by the Player when a song starts loading - gives the handler a chance to do something clever to get a playable track.

View File

@@ -243,40 +243,6 @@ QString MakeTempDir(const QString template_name) {
} }
QString GetTemporaryFileName() {
QString file;
{
QTemporaryFile tempfile;
// Do not delete the file, we want to do something with it
tempfile.setAutoRemove(false);
tempfile.open();
file = tempfile.fileName();
}
return file;
}
QString SaveToTemporaryFile(const QByteArray &data) {
QTemporaryFile tempfile;
tempfile.setAutoRemove(false);
if (!tempfile.open()) {
return QString();
}
if (tempfile.write(data) != data.size()) {
tempfile.remove();
return QString();
}
tempfile.close();
return tempfile.fileName();
}
bool RemoveRecursive(const QString &path) { bool RemoveRecursive(const QString &path) {
QDir dir(path); QDir dir(path);
@@ -700,6 +666,10 @@ QString FiddleFileExtension(const QString &filename, const QString &new_extensio
return PathWithoutFilenameExtension(filename) + "." + new_extension; return PathWithoutFilenameExtension(filename) + "." + new_extension;
} }
QString GetEnv(const QString &key) {
return QString::fromLocal8Bit(qgetenv(key.toLocal8Bit()));
}
void SetEnv(const char *key, const QString &value) { void SetEnv(const char *key, const QString &value) {
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
@@ -767,6 +737,33 @@ QString GetRandomString(const int len, const QString &UseCharacters) {
} }
QString DesktopEnvironment() {
const QString de = GetEnv("XDG_CURRENT_DESKTOP");
if (!de.isEmpty()) return de;
if (!qEnvironmentVariableIsEmpty("KDE_FULL_SESSION")) return "KDE";
if (!qEnvironmentVariableIsEmpty("GNOME_DESKTOP_SESSION_ID")) return "Gnome";
QString session = GetEnv("DESKTOP_SESSION");
int slash = session.lastIndexOf('/');
if (slash != -1) {
QSettings desktop_file(QString(session + ".desktop"), QSettings::IniFormat);
desktop_file.beginGroup("Desktop Entry");
QString name = desktop_file.value("DesktopNames").toString();
desktop_file.endGroup();
if (!name.isEmpty()) return name;
session = session.mid(slash + 1);
}
if (session == "kde") return "KDE";
else if (session == "gnome") return "Gnome";
else if (session == "xfce") return "XFCE";
return "Unknown";
}
} // namespace Utilities } // namespace Utilities
ScopedWCharArray::ScopedWCharArray(const QString &str) ScopedWCharArray::ScopedWCharArray(const QString &str)

View File

@@ -61,8 +61,6 @@ quint64 FileSystemCapacity(const QString &path);
quint64 FileSystemFreeSpace(const QString &path); quint64 FileSystemFreeSpace(const QString &path);
QString MakeTempDir(const QString template_name = QString()); QString MakeTempDir(const QString template_name = QString());
QString GetTemporaryFileName();
QString SaveToTemporaryFile(const QByteArray &data);
bool RemoveRecursive(const QString &path); bool RemoveRecursive(const QString &path);
bool CopyRecursive(const QString &source, const QString &destination); bool CopyRecursive(const QString &source, const QString &destination);
@@ -122,6 +120,7 @@ QUrl GetRelativePathToStrawberryBin(const QUrl &url);
QString PathWithoutFilenameExtension(const QString &filename); QString PathWithoutFilenameExtension(const QString &filename);
QString FiddleFileExtension(const QString &filename, const QString &new_extension); QString FiddleFileExtension(const QString &filename, const QString &new_extension);
QString GetEnv(const QString &key);
void SetEnv(const char *key, const QString &value); void SetEnv(const char *key, const QString &value);
void IncreaseFDLimit(); void IncreaseFDLimit();
void CheckPortable(); void CheckPortable();
@@ -153,7 +152,9 @@ QString GetRandomStringWithChars(const int len);
QString GetRandomStringWithCharsAndNumbers(const int len); QString GetRandomStringWithCharsAndNumbers(const int len);
QString GetRandomString(const int len, const QString &UseCharacters); QString GetRandomString(const int len, const QString &UseCharacters);
} QString DesktopEnvironment();
} // namespace
class ScopedWCharArray { class ScopedWCharArray {
public: public:

View File

@@ -180,7 +180,8 @@ QString AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
QString AlbumCoverChoiceController::SearchForCover(Song *song) { QString AlbumCoverChoiceController::SearchForCover(Song *song) {
QString album = song->effective_album(); QString album = song->effective_album();
album.remove(Song::kCoverRemoveDisc); album.remove(Song::kAlbumRemoveDisc);
album.remove(Song::kAlbumRemoveMisc);
// Get something sensible to stick in the search box // Get something sensible to stick in the search box
QImage image = cover_searcher_->Exec(song->effective_albumartist(), album); QImage image = cover_searcher_->Exec(song->effective_albumartist(), album);
@@ -305,7 +306,7 @@ void AlbumCoverChoiceController::SaveCover(Song *song, const QString &cover) {
QString AlbumCoverChoiceController::SaveCoverInCache(const QString &artist, const QString &album, const QImage &image) { QString AlbumCoverChoiceController::SaveCoverInCache(const QString &artist, const QString &album, const QImage &image) {
QString album2(album); QString album2(album);
album2.remove(Song::kCoverRemoveDisc); album2.remove(Song::kAlbumRemoveDisc);
// Hash the artist and album into a filename for the image // Hash the artist and album into a filename for the image
QString filename(Utilities::Sha1CoverHash(artist, album2).toHex() + ".jpg"); QString filename(Utilities::Sha1CoverHash(artist, album2).toHex() + ".jpg");

View File

@@ -50,7 +50,8 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString
CoverSearchRequest request; CoverSearchRequest request;
request.artist = artist; request.artist = artist;
request.album = album; request.album = album;
request.album.remove(Song::kCoverRemoveDisc); request.album.remove(Song::kAlbumRemoveDisc);
request.album.remove(Song::kAlbumRemoveMisc);
request.search = false; request.search = false;
request.id = next_id_++; request.id = next_id_++;
request.fetchall = fetchall; request.fetchall = fetchall;
@@ -65,7 +66,8 @@ quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString
CoverSearchRequest request; CoverSearchRequest request;
request.artist = artist; request.artist = artist;
request.album = album; request.album = album;
request.album.remove(Song::kCoverRemoveDisc); request.album.remove(Song::kAlbumRemoveDisc);
request.album.remove(Song::kAlbumRemoveMisc);
request.search = true; request.search = true;
request.id = next_id_++; request.id = next_id_++;
request.fetchall = false; request.fetchall = false;

View File

@@ -99,8 +99,18 @@ void ConnectedDevice::InitBackendDirectory(const QString &mount_point, bool firs
} }
void ConnectedDevice::ConnectAsync() { emit ConnectFinished(unique_id_, true); }
void ConnectedDevice::Eject() { void ConnectedDevice::Eject() {
manager_->UnmountAsync(manager_->FindDeviceById(unique_id_));
DeviceInfo *info = manager_->FindDeviceById(unique_id_);
if (!info) return;
QModelIndex idx = manager_->ItemToIndex(info);
if (!idx.isValid()) return;
manager_->UnmountAsync(idx);
} }
void ConnectedDevice::FinishCopy(bool) { void ConnectedDevice::FinishCopy(bool) {
@@ -112,13 +122,27 @@ void ConnectedDevice::FinishDelete(bool) {
} }
MusicStorage::TranscodeMode ConnectedDevice::GetTranscodeMode() const { MusicStorage::TranscodeMode ConnectedDevice::GetTranscodeMode() const {
int index = manager_->FindDeviceById(unique_id_);
return MusicStorage::TranscodeMode(manager_->index(index, 0, QModelIndex()).data(DeviceManager::Role_TranscodeMode).toInt()); DeviceInfo *info = manager_->FindDeviceById(unique_id_);
if (!info) return MusicStorage::TranscodeMode();
QModelIndex idx = manager_->ItemToIndex(info);
if (!idx.isValid()) return MusicStorage::TranscodeMode();
return MusicStorage::TranscodeMode(idx.data(DeviceManager::Role_TranscodeMode).toInt());
} }
Song::FileType ConnectedDevice::GetTranscodeFormat() const { Song::FileType ConnectedDevice::GetTranscodeFormat() const {
int index = manager_->FindDeviceById(unique_id_);
return Song::FileType(manager_->index(index, 0, QModelIndex()).data(DeviceManager::Role_TranscodeFormat).toInt()); DeviceInfo *info = manager_->FindDeviceById(unique_id_);
if (!info) return Song::FileType_Unknown;
QModelIndex idx = manager_->ItemToIndex(info);
if (!idx.isValid()) return Song::FileType_Unknown;
return Song::FileType(idx.data(DeviceManager::Role_TranscodeFormat).toInt());
} }
void ConnectedDevice::BackendTotalSongCountUpdated(int count) { void ConnectedDevice::BackendTotalSongCountUpdated(int count) {

View File

@@ -50,6 +50,7 @@ class ConnectedDevice : public QObject,
~ConnectedDevice(); ~ConnectedDevice();
virtual bool Init() = 0; virtual bool Init() = 0;
virtual void ConnectAsync();
// For some devices (e.g. CD devices) we don't have callbacks to be notified when something change: // For some devices (e.g. CD devices) we don't have callbacks to be notified when something change:
// we can call this method to refresh device's state // we can call this method to refresh device's state
virtual void Refresh() {} virtual void Refresh() {}
@@ -71,6 +72,7 @@ class ConnectedDevice : public QObject,
signals: signals:
void TaskStarted(int id); void TaskStarted(int id);
void SongCountUpdated(int count); void SongCountUpdated(int count);
void ConnectFinished(const QString& id, bool success);
protected: protected:
void InitBackendDirectory(const QString &mount_point, bool first_time, bool rewrite_path = true); void InitBackendDirectory(const QString &mount_point, bool first_time, bool rewrite_path = true);

View File

@@ -123,16 +123,11 @@ void DeviceInfo::LoadIcon(const QVariantList &icons, const QString &name_hint) {
QString hint = QString(icons.first().toString() + name_hint).toLower(); QString hint = QString(icons.first().toString() + name_hint).toLower();
if (hint.contains("phone")) if (hint.contains("phone")) icon_name_ = "device-phone";
icon_name_ = "device-phone"; else if (hint.contains("ipod") || hint.contains("apple")) icon_name_ = "device-ipod";
else if (hint.contains("ipod") || hint.contains("apple")) else if ((hint.contains("usb")) && (hint.contains("reader"))) icon_name_ = "device-usb-flash";
icon_name_ = "device-ipod"; else if (hint.contains("usb")) icon_name_ = "device-usb-drive";
else if ((hint.contains("usb")) && (hint.contains("reader"))) else icon_name_ = "device";
icon_name_ = "device-usb-flash";
else if (hint.contains("usb"))
icon_name_ = "device-usb-drive";
else
icon_name_ = "device";
icon_ = IconLoader::Load(icon_name_); icon_ = IconLoader::Load(icon_name_);

View File

@@ -42,10 +42,7 @@
#include "devicedatabasebackend.h" #include "devicedatabasebackend.h"
#include "devicelister.h" #include "devicelister.h"
class Application;
class ConnectedDevice; class ConnectedDevice;
class DeviceLister;
class DeviceStateFilterModel;
// Devices can be in three different states: // Devices can be in three different states:
// 1) Remembered in the database but not physically connected at the moment. // 1) Remembered in the database but not physically connected at the moment.
@@ -85,7 +82,10 @@ class DeviceInfo : public SimpleTreeItem<DeviceInfo> {
// Sometimes the same device is discovered more than once. In this case the device will have multiple "backends". // Sometimes the same device is discovered more than once. In this case the device will have multiple "backends".
struct Backend { struct Backend {
Backend(DeviceLister *lister = nullptr, const QString &id = QString()) Backend(DeviceLister *lister = nullptr, const QString &id = QString())
: lister_(lister), unique_id_(id) {} :
lister_(lister),
unique_id_(id)
{}
DeviceLister *lister_; // nullptr if not physically connected DeviceLister *lister_; // nullptr if not physically connected
QString unique_id_; QString unique_id_;

View File

@@ -131,13 +131,16 @@ DeviceManager::DeviceManager(Application *app, QObject *parent)
#if defined(Q_OS_MACOS) and defined(HAVE_LIBMTP) #if defined(Q_OS_MACOS) and defined(HAVE_LIBMTP)
AddLister(new MacOsDeviceLister); AddLister(new MacOsDeviceLister);
#endif #endif
#ifdef HAVE_IMOBILEDEVICE
AddDeviceClass<FilesystemDevice>(); AddLister(new iLister);
#endif
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER) #if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
AddDeviceClass<CddaDevice>(); AddDeviceClass<CddaDevice>();
#endif #endif
AddDeviceClass<FilesystemDevice>();
#ifdef HAVE_LIBGPOD #ifdef HAVE_LIBGPOD
AddDeviceClass<GPodDevice>(); AddDeviceClass<GPodDevice>();
#endif #endif
@@ -146,6 +149,10 @@ DeviceManager::DeviceManager(Application *app, QObject *parent)
AddDeviceClass<MtpDevice>(); AddDeviceClass<MtpDevice>();
#endif #endif
#ifdef HAVE_IMOBILEDEVICE
AddDeviceClass<AfcDevice>();
#endif
} }
DeviceManager::~DeviceManager() { DeviceManager::~DeviceManager() {
@@ -157,6 +164,8 @@ DeviceManager::~DeviceManager() {
backend_->deleteLater(); backend_->deleteLater();
delete root_;
} }
void DeviceManager::LoadAllDevices() { void DeviceManager::LoadAllDevices() {
@@ -165,20 +174,20 @@ void DeviceManager::LoadAllDevices() {
DeviceDatabaseBackend::DeviceList devices = backend_->GetAllDevices(); DeviceDatabaseBackend::DeviceList devices = backend_->GetAllDevices();
for (const DeviceDatabaseBackend::Device &device : devices) { for (const DeviceDatabaseBackend::Device &device : devices) {
DeviceInfo *info = new DeviceInfo(DeviceInfo::Type_Root, root_); beginInsertRows(ItemToIndex(root_), devices_.count(), devices_.count());
DeviceInfo *info = new DeviceInfo(DeviceInfo::Type_Device, root_);
info->InitFromDb(device); info->InitFromDb(device);
beginInsertRows(QModelIndex(), devices_.count(), devices_.count());
devices_ << info; devices_ << info;
endInsertRows(); endInsertRows();
} }
} }
QVariant DeviceManager::data(const QModelIndex &index, int role) const { QVariant DeviceManager::data(const QModelIndex &idx, int role) const {
if (!index.isValid() || index.column() != 0) return QVariant(); if (!idx.isValid() || idx.column() != 0) return QVariant();
const DeviceInfo *info = IndexToItem(index); DeviceInfo *info = IndexToItem(idx);
if (!info) return QVariant(); if (!info) return QVariant();
switch (role) { switch (role) {
@@ -239,24 +248,22 @@ QVariant DeviceManager::data(const QModelIndex &index, int role) const {
case MusicStorage::Role_Storage: case MusicStorage::Role_Storage:
if (!info->device_ && info->database_id_ != -1) if (!info->device_ && info->database_id_ != -1)
const_cast<DeviceManager*>(this)->Connect(index.row()); const_cast<DeviceManager*>(this)->Connect(info);
if (!info->device_) return QVariant(); if (!info->device_) return QVariant();
return QVariant::fromValue<std::shared_ptr<MusicStorage>>(info->device_); return QVariant::fromValue<std::shared_ptr<MusicStorage>>(info->device_);
case MusicStorage::Role_StorageForceConnect: case MusicStorage::Role_StorageForceConnect:
if (!info->BestBackend()) return QVariant();
if (!info->device_) { if (!info->device_) {
if (info->database_id_ == -1 && info->BestBackend() && !info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) { if (info->database_id_ == -1 && !info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) {
if (info->BestBackend()->lister_->AskForScan(info->BestBackend()->unique_id_)) {
if (info->BestBackend() && info->BestBackend()->lister_->AskForScan(info->BestBackend()->unique_id_)) {
std::unique_ptr<QMessageBox> dialog(new QMessageBox(QMessageBox::Information, tr("Connect device"), tr("This is the first time you have connected this device. Strawberry will now scan the device to find music files - this may take some time."), QMessageBox::Cancel)); std::unique_ptr<QMessageBox> dialog(new QMessageBox(QMessageBox::Information, tr("Connect device"), tr("This is the first time you have connected this device. Strawberry will now scan the device to find music files - this may take some time."), QMessageBox::Cancel));
QPushButton *connect = dialog->addButton(tr("Connect device"), QMessageBox::AcceptRole); QPushButton *connect = dialog->addButton(tr("Connect device"), QMessageBox::AcceptRole);
dialog->exec(); dialog->exec();
if (dialog->clickedButton() != connect) return QVariant(); if (dialog->clickedButton() != connect) return QVariant();
} }
} }
const_cast<DeviceManager*>(this)->Connect(info);
const_cast<DeviceManager*>(this)->Connect(index.row());
} }
if (!info->device_) return QVariant(); if (!info->device_) return QVariant();
return QVariant::fromValue<std::shared_ptr<MusicStorage>>(info->device_); return QVariant::fromValue<std::shared_ptr<MusicStorage>>(info->device_);
@@ -298,21 +305,21 @@ void DeviceManager::AddLister(DeviceLister *lister) {
} }
int DeviceManager::FindDeviceById(const QString &id) const { DeviceInfo *DeviceManager::FindDeviceById(const QString &id) const {
for (int i = 0; i < devices_.count(); ++i) { for (int i = 0; i < devices_.count(); ++i) {
for (const DeviceInfo::Backend &backend : devices_[i]->backends_) { for (const DeviceInfo::Backend &backend : devices_[i]->backends_) {
if (backend.unique_id_ == id) return i; if (backend.unique_id_ == id) return devices_[i];
} }
} }
return -1; return nullptr;
} }
int DeviceManager::FindDeviceByUrl(const QList<QUrl> &urls) const { DeviceInfo *DeviceManager::FindDeviceByUrl(const QList<QUrl> &urls) const {
if (urls.isEmpty()) return -1; if (urls.isEmpty()) return nullptr;
for (int i = 0; i < devices_.count(); ++i) { for (int i = 0; i < devices_.count(); ++i) {
for (const DeviceInfo::Backend &backend : devices_[i]->backends_) { for (const DeviceInfo::Backend &backend : devices_[i]->backends_) {
@@ -320,12 +327,12 @@ int DeviceManager::FindDeviceByUrl(const QList<QUrl> &urls) const {
QList<QUrl> device_urls = backend.lister_->MakeDeviceUrls(backend.unique_id_); QList<QUrl> device_urls = backend.lister_->MakeDeviceUrls(backend.unique_id_);
for (const QUrl &url : device_urls) { for (const QUrl &url : device_urls) {
if (urls.contains(url)) return i; if (urls.contains(url)) return devices_[i];
} }
} }
} }
return -1; return nullptr;
} }
@@ -337,24 +344,22 @@ void DeviceManager::PhysicalDeviceAdded(const QString &id) {
qLog(Info) << "Device added:" << id << lister->DeviceUniqueIDs(); qLog(Info) << "Device added:" << id << lister->DeviceUniqueIDs();
// Do we have this device already? // Do we have this device already?
int i = FindDeviceById(id); DeviceInfo *info = FindDeviceById(id);
if (i != -1) { if (info) {
DeviceInfo *info = devices_[i];
for (int backend_index = 0 ; backend_index < info->backends_.count() ; ++backend_index) { for (int backend_index = 0 ; backend_index < info->backends_.count() ; ++backend_index) {
if (info->backends_[backend_index].unique_id_ == id) { if (info->backends_[backend_index].unique_id_ == id) {
info->backends_[backend_index].lister_ = lister; info->backends_[backend_index].lister_ = lister;
break; break;
} }
} }
QModelIndex idx = ItemToIndex(info);
emit dataChanged(index(i, 0), index(i, 0)); if (idx.isValid()) emit dataChanged(idx, idx);
} }
else { else {
// Check if we have another device with the same URL // Check if we have another device with the same URL
i = FindDeviceByUrl(lister->MakeDeviceUrls(id)); info = FindDeviceByUrl(lister->MakeDeviceUrls(id));
if (i != -1) { if (info) {
// Add this device's lister to the existing device // Add this device's lister to the existing device
DeviceInfo *info = devices_[i];
info->backends_ << DeviceInfo::Backend(lister, id); info->backends_ << DeviceInfo::Backend(lister, id);
// If the user hasn't saved the device in the DB yet then overwrite the device's name and icon etc. // If the user hasn't saved the device in the DB yet then overwrite the device's name and icon etc.
@@ -363,18 +368,17 @@ void DeviceManager::PhysicalDeviceAdded(const QString &id) {
info->size_ = lister->DeviceCapacity(id); info->size_ = lister->DeviceCapacity(id);
info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_); info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_);
} }
QModelIndex idx = ItemToIndex(info);
emit dataChanged(index(i, 0), index(i, 0)); if (idx.isValid()) emit dataChanged(idx, idx);
} }
else { else {
// It's a completely new device // It's a completely new device
DeviceInfo *info = new DeviceInfo(DeviceInfo::Type_Root, root_); beginInsertRows(ItemToIndex(root_), devices_.count(), devices_.count());
DeviceInfo *info = new DeviceInfo(DeviceInfo::Type_Device, root_);
info->backends_ << DeviceInfo::Backend(lister, id); info->backends_ << DeviceInfo::Backend(lister, id);
info->friendly_name_ = lister->MakeFriendlyName(id); info->friendly_name_ = lister->MakeFriendlyName(id);
info->size_ = lister->DeviceCapacity(id); info->size_ = lister->DeviceCapacity(id);
info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_); info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_);
beginInsertRows(QModelIndex(), devices_.count(), devices_.count());
devices_ << info; devices_ << info;
endInsertRows(); endInsertRows();
} }
@@ -388,13 +392,11 @@ void DeviceManager::PhysicalDeviceRemoved(const QString &id) {
qLog(Info) << "Device removed:" << id; qLog(Info) << "Device removed:" << id;
int i = FindDeviceById(id); DeviceInfo *info = FindDeviceById(id);
if (i == -1) { if (!info) return;
// Shouldn't happen
return;
}
DeviceInfo *info = devices_[i]; QModelIndex idx = ItemToIndex(info);
if (!idx.isValid()) return;
if (info->database_id_ != -1) { if (info->database_id_ != -1) {
// Keep the structure around, but just "disconnect" it // Keep the structure around, but just "disconnect" it
@@ -407,9 +409,9 @@ void DeviceManager::PhysicalDeviceRemoved(const QString &id) {
if (info->device_ && info->device_->lister() == lister) info->device_.reset(); if (info->device_ && info->device_->lister() == lister) info->device_.reset();
if (!info->device_) emit DeviceDisconnected(i); if (!info->device_) emit DeviceDisconnected(idx);
emit dataChanged(index(i, 0), index(i, 0)); emit dataChanged(idx, idx);
} }
else { else {
// If this was the last lister for the device then remove it from the model // If this was the last lister for the device then remove it from the model
@@ -421,16 +423,9 @@ void DeviceManager::PhysicalDeviceRemoved(const QString &id) {
} }
if (info->backends_.isEmpty()) { if (info->backends_.isEmpty()) {
beginRemoveRows(QModelIndex(), i, i); beginRemoveRows(ItemToIndex(root_), idx.row(), idx.row());
devices_.removeAt(i); devices_.removeAll(info);
root_->Delete(info->row);
for (const QModelIndex &idx : persistentIndexList()) {
if (idx.row() == i)
changePersistentIndex(idx, QModelIndex());
else if (idx.row() > i)
changePersistentIndex(idx, index(idx.row() - 1, idx.column()));
}
endRemoveRows(); endRemoveRows();
} }
} }
@@ -442,29 +437,38 @@ void DeviceManager::PhysicalDeviceChanged(const QString &id) {
DeviceLister *lister = qobject_cast<DeviceLister*>(sender()); DeviceLister *lister = qobject_cast<DeviceLister*>(sender());
Q_UNUSED(lister); Q_UNUSED(lister);
int i = FindDeviceById(id); DeviceInfo *info = FindDeviceById(id);
if (i == -1) { if (!info) return;
// Shouldn't happen
return;
}
// TODO // TODO
} }
std::shared_ptr<ConnectedDevice> DeviceManager::Connect(int row) { std::shared_ptr<ConnectedDevice> DeviceManager::Connect(QModelIndex idx) {
DeviceInfo *info = devices_[row];
if (info->device_) // Already connected
return info->device_;
std::shared_ptr<ConnectedDevice> ret; std::shared_ptr<ConnectedDevice> ret;
if (!info->BestBackend()->lister_) // Not physically connected DeviceInfo *info = IndexToItem(idx);
return ret; if (!info) return ret;
if (info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) { return Connect(info);
// Mount the device
}
std::shared_ptr<ConnectedDevice> DeviceManager::Connect(DeviceInfo *info) {
std::shared_ptr<ConnectedDevice> ret;
if (!info) return ret;
if (info->device_) { // Already connected
return info->device_;
}
if (!info->BestBackend() || !info->BestBackend()->lister_) { // Not physically connected
return ret;
}
if (info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) { // Mount the device
info->BestBackend()->lister_->MountDevice(info->BestBackend()->unique_id_); info->BestBackend()->lister_->MountDevice(info->BestBackend()->unique_id_);
return ret; return ret;
} }
@@ -525,8 +529,10 @@ std::shared_ptr<ConnectedDevice> DeviceManager::Connect(int row) {
Q_ARG(QUrl, device_url), Q_ARG(QUrl, device_url),
Q_ARG(DeviceLister*, info->BestBackend()->lister_), Q_ARG(DeviceLister*, info->BestBackend()->lister_),
Q_ARG(QString, info->BestBackend()->unique_id_), Q_ARG(QString, info->BestBackend()->unique_id_),
Q_ARG(DeviceManager*, this), Q_ARG(Application*, app_), Q_ARG(DeviceManager*, this),
Q_ARG(int, info->database_id_), Q_ARG(bool, first_time)); Q_ARG(Application*, app_),
Q_ARG(int, info->database_id_),
Q_ARG(bool, first_time));
ret.reset(static_cast<ConnectedDevice*>(instance)); ret.reset(static_cast<ConnectedDevice*>(instance));
@@ -541,91 +547,138 @@ std::shared_ptr<ConnectedDevice> DeviceManager::Connect(int row) {
return ret; return ret;
} }
info->device_ = ret; info->device_ = ret;
QModelIndex index = ItemToIndex(info);
if (!index.isValid()) return ret;
emit dataChanged(index, index); QModelIndex idx = ItemToIndex(info);
if (!idx.isValid()) return ret;
emit dataChanged(idx, idx);
connect(info->device_.get(), SIGNAL(TaskStarted(int)), SLOT(DeviceTaskStarted(int))); connect(info->device_.get(), SIGNAL(TaskStarted(int)), SLOT(DeviceTaskStarted(int)));
connect(info->device_.get(), SIGNAL(SongCountUpdated(int)), SLOT(DeviceSongCountUpdated(int))); connect(info->device_.get(), SIGNAL(SongCountUpdated(int)), SLOT(DeviceSongCountUpdated(int)));
connect(info->device_.get(), SIGNAL(ConnectFinished(const QString&, bool)), SLOT(DeviceConnectFinished(const QString&, bool)));
emit DeviceConnected(row); ret->ConnectAsync();
return ret; return ret;
} }
std::shared_ptr<ConnectedDevice> DeviceManager::GetConnectedDevice(int row) const { void DeviceManager::DeviceConnectFinished(const QString &id, bool success) {
return devices_[row]->device_;
DeviceInfo *info = FindDeviceById(id);
if (!info) return;
QModelIndex idx = ItemToIndex(info);
if (!idx.isValid()) return;
if (success) {
emit DeviceConnected(idx);
}
else {
info->device_.reset();
}
} }
int DeviceManager::GetDatabaseId(int row) const { DeviceInfo *DeviceManager::GetDevice(QModelIndex idx) const {
return devices_[row]->database_id_;
DeviceInfo *info = IndexToItem(idx);
return info;
} }
DeviceLister *DeviceManager::GetLister(int row) const { std::shared_ptr<ConnectedDevice> DeviceManager::GetConnectedDevice(QModelIndex idx) const {
return devices_[row]->BestBackend()->lister_;
std::shared_ptr<ConnectedDevice> ret;
DeviceInfo *info = IndexToItem(idx);
if (!info) return ret;
return info->device_;
} }
void DeviceManager::Disconnect(int row) { std::shared_ptr<ConnectedDevice> DeviceManager::GetConnectedDevice(DeviceInfo *info) const {
DeviceInfo *info = devices_[row]; std::shared_ptr<ConnectedDevice> ret;
if (!info || !info->device_) if (!info) return ret;
return; return info->device_;
}
int DeviceManager::GetDatabaseId(QModelIndex idx) const {
if (!idx.isValid()) return -1;
DeviceInfo *info = IndexToItem(idx);
if (!info) return -1;
return info->database_id_;
}
DeviceLister *DeviceManager::GetLister(QModelIndex idx) const {
if (!idx.isValid()) return nullptr;
DeviceInfo *info = IndexToItem(idx);
if (!info || !info->BestBackend()) return nullptr;
return info->BestBackend()->lister_;
}
void DeviceManager::Disconnect(QModelIndex idx) {
if (!idx.isValid()) return;
DeviceInfo *info = IndexToItem(idx);
if (!info || !info->device_) return;
info->device_.reset(); info->device_.reset();
emit DeviceDisconnected(row);
QModelIndex index = ItemToIndex(info); emit DeviceDisconnected(idx);
if (!index.isValid()) return; emit dataChanged(idx, idx);
emit dataChanged(index, index);
} }
void DeviceManager::Forget(int row) { void DeviceManager::Forget(QModelIndex idx) {
if (!idx.isValid()) return;
DeviceInfo *info = IndexToItem(idx);
if (!info) return;
DeviceInfo *info = devices_[row];
if (info->database_id_ == -1) return; if (info->database_id_ == -1) return;
if (info->device_) Disconnect(idx);
if (info->device_) Disconnect(row);
backend_->RemoveDevice(info->database_id_); backend_->RemoveDevice(info->database_id_);
info->database_id_ = -1; info->database_id_ = -1;
if (!info->BestBackend()->lister_) { if (!info->BestBackend() || (info->BestBackend() && !info->BestBackend()->lister_)) { // It's not attached any more so remove it from the list
// It's not attached any more so remove it from the list beginRemoveRows(ItemToIndex(root_), idx.row(), idx.row());
beginRemoveRows(QModelIndex(), row, row); devices_.removeAll(info);
devices_.removeAt(row); root_->Delete(info->row);
for (const QModelIndex &idx : persistentIndexList()) {
if (idx.row() == row)
changePersistentIndex(idx, QModelIndex());
else if (idx.row() > row)
changePersistentIndex(idx, index(idx.row() - 1, idx.column()));
}
endRemoveRows(); endRemoveRows();
} }
else { else { // It's still attached, set the name and icon back to what they were originally
// It's still attached, set the name and icon back to what they were originally
const QString id = info->BestBackend()->unique_id_; const QString id = info->BestBackend()->unique_id_;
info->friendly_name_ = info->BestBackend()->lister_->MakeFriendlyName(id); info->friendly_name_ = info->BestBackend()->lister_->MakeFriendlyName(id);
info->LoadIcon(info->BestBackend()->lister_->DeviceIcons(id), info->friendly_name_); info->LoadIcon(info->BestBackend()->lister_->DeviceIcons(id), info->friendly_name_);
dataChanged(index(row, 0), index(row, 0)); dataChanged(idx, idx);
} }
} }
void DeviceManager::SetDeviceOptions(int row, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format) { void DeviceManager::SetDeviceOptions(QModelIndex idx, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format) {
if (!idx.isValid()) return;
DeviceInfo *info = IndexToItem(idx);
if (!info) return;
DeviceInfo *info = devices_[row];
info->friendly_name_ = friendly_name; info->friendly_name_ = friendly_name;
info->LoadIcon(QVariantList() << icon_name, friendly_name); info->LoadIcon(QVariantList() << icon_name, friendly_name);
info->transcode_mode_ = mode; info->transcode_mode_ = mode;
info->transcode_format_ = format; info->transcode_format_ = format;
emit dataChanged(index(row, 0), index(row, 0)); emit dataChanged(idx, idx);
if (info->database_id_ != -1) if (info->database_id_ != -1)
backend_->SetDeviceOptions(info->database_id_, friendly_name, icon_name, mode, format); backend_->SetDeviceOptions(info->database_id_, friendly_name, icon_name, mode, format);
@@ -659,42 +712,49 @@ void DeviceManager::TasksChanged() {
for (const TaskManager::Task &task : tasks) { for (const TaskManager::Task &task : tasks) {
if (!active_tasks_.contains(task.id)) continue; if (!active_tasks_.contains(task.id)) continue;
QPersistentModelIndex index = active_tasks_[task.id]; QPersistentModelIndex idx = active_tasks_[task.id];
if (!index.isValid()) continue; if (!idx.isValid()) continue;
DeviceInfo *info = IndexToItem(index); DeviceInfo *info = IndexToItem(idx);
if (task.progress_max) if (task.progress_max)
info->task_percentage_ = float(task.progress) / task.progress_max * 100; info->task_percentage_ = float(task.progress) / task.progress_max * 100;
else else
info->task_percentage_ = 0; info->task_percentage_ = 0;
emit dataChanged(index, index); emit dataChanged(idx, idx);
finished_tasks.removeAll(index); finished_tasks.removeAll(idx);
} }
for (const QPersistentModelIndex &index : finished_tasks) { for (const QPersistentModelIndex &idx : finished_tasks) {
if (!index.isValid()) continue;
if (!idx.isValid()) continue;
DeviceInfo *info = IndexToItem(idx);
if (!info) continue;
DeviceInfo *info = devices_[index.row()];
info->task_percentage_ = -1; info->task_percentage_ = -1;
emit dataChanged(index, index); emit dataChanged(idx, idx);
active_tasks_.remove(active_tasks_.key(index)); active_tasks_.remove(active_tasks_.key(idx));
} }
} }
void DeviceManager::UnmountAsync(int row) { void DeviceManager::UnmountAsync(QModelIndex idx) {
Q_ASSERT(QMetaObject::invokeMethod(this, "Unmount", Q_ARG(int, row))); Q_ASSERT(QMetaObject::invokeMethod(this, "Unmount", Q_ARG(QModelIndex, idx)));
} }
void DeviceManager::Unmount(int row) { void DeviceManager::Unmount(QModelIndex idx) {
if (!idx.isValid()) return;
DeviceInfo *info = IndexToItem(idx);
if (!info) return;
DeviceInfo *info = devices_[row];
if (info->database_id_ != -1 && !info->device_) return; if (info->database_id_ != -1 && !info->device_) return;
if (info->device_) Disconnect(row); if (info->device_) Disconnect(idx);
if (info->BestBackend()->lister_) if (info->BestBackend()->lister_)
info->BestBackend()->lister_->UnmountDevice(info->BestBackend()->unique_id_); info->BestBackend()->lister_->UnmountDevice(info->BestBackend()->unique_id_);
@@ -706,13 +766,13 @@ void DeviceManager::DeviceSongCountUpdated(int count) {
ConnectedDevice *device = qobject_cast<ConnectedDevice*>(sender()); ConnectedDevice *device = qobject_cast<ConnectedDevice*>(sender());
if (!device) return; if (!device) return;
int row = FindDeviceById(device->unique_id()); DeviceInfo *info = FindDeviceById(device->unique_id());
if (row == -1) return; if (!info) return;
QModelIndex index = ItemToIndex(devices_[row]); QModelIndex idx = ItemToIndex(info);
if (!index.isValid()) return; if (!idx.isValid()) return;
emit dataChanged(index, index); emit dataChanged(idx, idx);
} }
@@ -721,21 +781,14 @@ void DeviceManager::LazyPopulate(DeviceInfo *parent, bool signal) {
parent->lazy_loaded = true; parent->lazy_loaded = true;
} }
DeviceInfo *DeviceManager::ItemFromRow(int row) {
return devices_[row];
}
QString DeviceManager::DeviceNameByID(QString unique_id) { QString DeviceManager::DeviceNameByID(QString unique_id) {
int row = FindDeviceById(unique_id); DeviceInfo *info = FindDeviceById(unique_id);
if (row == -1) return QString();
DeviceInfo *info = devices_[row];
if (!info) return QString(); if (!info) return QString();
QModelIndex index = ItemToIndex(info); QModelIndex idx = ItemToIndex(info);
if (!index.isValid()) return QString(); if (!idx.isValid()) return QString();
return data(index, DeviceManager::Role_FriendlyName).toString(); return data(idx, DeviceManager::Role_FriendlyName).toString();
} }

View File

@@ -88,32 +88,34 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
DeviceStateFilterModel *connected_devices_model() const { return connected_devices_model_; } DeviceStateFilterModel *connected_devices_model() const { return connected_devices_model_; }
// Get info about devices // Get info about devices
int GetDatabaseId(int row) const; int GetDatabaseId(QModelIndex idx) const;
DeviceLister *GetLister(int row) const; DeviceLister *GetLister(QModelIndex idx) const;
std::shared_ptr<ConnectedDevice> GetConnectedDevice(int row) const; DeviceInfo *GetDevice(QModelIndex idx) const;
std::shared_ptr<ConnectedDevice> GetConnectedDevice(QModelIndex idx) const;
std::shared_ptr<ConnectedDevice> GetConnectedDevice(DeviceInfo *info) const;
DeviceInfo *ItemFromRow(int row); DeviceInfo *FindDeviceById(const QString &id) const;
int FindDeviceById(const QString &id) const; DeviceInfo *FindDeviceByUrl(const QList<QUrl> &url) const;
int FindDeviceByUrl(const QList<QUrl> &url) const;
QString DeviceNameByID(QString unique_id); QString DeviceNameByID(QString unique_id);
// Actions on devices // Actions on devices
std::shared_ptr<ConnectedDevice> Connect(int row); std::shared_ptr<ConnectedDevice> Connect(DeviceInfo *info);
void Disconnect(int row); std::shared_ptr<ConnectedDevice> Connect(QModelIndex idx);
void Forget(int row); void Disconnect(QModelIndex idx);
void UnmountAsync(int row); void Forget(QModelIndex idx);
void UnmountAsync(QModelIndex idx);
void SetDeviceOptions(int row, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format); void SetDeviceOptions(QModelIndex idx, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format);
// QAbstractItemModel // QAbstractItemModel
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
public slots: public slots:
void Unmount(int row); void Unmount(QModelIndex idx);
signals: signals:
void DeviceConnected(int row); void DeviceConnected(QModelIndex idx);
void DeviceDisconnected(int row); void DeviceDisconnected(QModelIndex idx);
private slots: private slots:
void PhysicalDeviceAdded(const QString &id); void PhysicalDeviceAdded(const QString &id);
@@ -123,6 +125,7 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
void TasksChanged(); void TasksChanged();
void DeviceSongCountUpdated(int count); void DeviceSongCountUpdated(int count);
void LoadAllDevices(); void LoadAllDevices();
void DeviceConnectFinished(const QString &id, bool success);
protected: protected:
void LazyPopulate(DeviceInfo *item) { LazyPopulate(item, true); } void LazyPopulate(DeviceInfo *item) { LazyPopulate(item, true); }

View File

@@ -89,7 +89,7 @@ void DeviceProperties::SetDeviceManager(DeviceManager *manager) {
} }
void DeviceProperties::ShowDevice(int row) { void DeviceProperties::ShowDevice(QModelIndex idx) {
if (ui_->icon->count() == 0) { if (ui_->icon->count() == 0) {
// Only load the icons the first time the dialog is shown // Only load the icons the first time the dialog is shown
@@ -117,7 +117,7 @@ void DeviceProperties::ShowDevice(int row) {
#endif #endif
} }
index_ = manager_->index(row, 0, QModelIndex()); index_ = idx;
// Basic information // Basic information
ui_->name->setText(index_.data(DeviceManager::Role_FriendlyName).toString()); ui_->name->setText(index_.data(DeviceManager::Role_FriendlyName).toString());
@@ -160,7 +160,7 @@ void DeviceProperties::UpdateHardwareInfo() {
// Hardware information // Hardware information
QString id = index_.data(DeviceManager::Role_UniqueId).toString(); QString id = index_.data(DeviceManager::Role_UniqueId).toString();
if (DeviceLister *lister = manager_->GetLister(index_.row())) { if (DeviceLister *lister = manager_->GetLister(index_)) {
QVariantMap info = lister->DeviceHardwareInfo(id); QVariantMap info = lister->DeviceHardwareInfo(id);
// Remove empty items // Remove empty items
@@ -206,8 +206,8 @@ void DeviceProperties::UpdateHardwareInfo() {
void DeviceProperties::UpdateFormats() { void DeviceProperties::UpdateFormats() {
QString id = index_.data(DeviceManager::Role_UniqueId).toString(); QString id = index_.data(DeviceManager::Role_UniqueId).toString();
DeviceLister *lister = manager_->GetLister(index_.row()); DeviceLister *lister = manager_->GetLister(index_);
std::shared_ptr<ConnectedDevice> device = manager_->GetConnectedDevice(index_.row()); std::shared_ptr<ConnectedDevice> device = manager_->GetConnectedDevice(index_);
// Transcode mode // Transcode mode
MusicStorage::TranscodeMode mode = MusicStorage::TranscodeMode(index_.data(DeviceManager::Role_TranscodeMode).toInt()); MusicStorage::TranscodeMode mode = MusicStorage::TranscodeMode(index_.data(DeviceManager::Role_TranscodeMode).toInt());
@@ -277,11 +277,11 @@ void DeviceProperties::accept() {
icon_name = ui_->icon->currentItem()->data(Qt::UserRole).toString(); icon_name = ui_->icon->currentItem()->data(Qt::UserRole).toString();
} }
manager_->SetDeviceOptions(index_.row(), ui_->name->text(), icon_name, mode, format); manager_->SetDeviceOptions(index_, ui_->name->text(), icon_name, mode, format);
} }
void DeviceProperties::OpenDevice() { manager_->Connect(index_.row()); } void DeviceProperties::OpenDevice() { manager_->Connect(index_); }
void DeviceProperties::UpdateFormatsFinished(QFuture<bool> future) { void DeviceProperties::UpdateFormatsFinished(QFuture<bool> future) {

View File

@@ -48,7 +48,7 @@ class DeviceProperties : public QDialog {
~DeviceProperties(); ~DeviceProperties();
void SetDeviceManager(DeviceManager *manager); void SetDeviceManager(DeviceManager *manager);
void ShowDevice(int row); void ShowDevice(QModelIndex idx);
public slots: public slots:
void accept(); void accept();

View File

@@ -190,8 +190,8 @@ void DeviceView::SetApplication(Application *app) {
Q_ASSERT(app_ == nullptr); Q_ASSERT(app_ == nullptr);
app_ = app; app_ = app;
connect(app_->device_manager(), SIGNAL(DeviceConnected(int)), SLOT(DeviceConnected(int))); connect(app_->device_manager(), SIGNAL(DeviceConnected(QModelIndex)), SLOT(DeviceConnected(QModelIndex)));
connect(app_->device_manager(), SIGNAL(DeviceDisconnected(int)), SLOT(DeviceDisconnected(int))); connect(app_->device_manager(), SIGNAL(DeviceDisconnected(QModelIndex)), SLOT(DeviceDisconnected(QModelIndex)));
sort_model_ = new QSortFilterProxyModel(this); sort_model_ = new QSortFilterProxyModel(this);
sort_model_->setSourceModel(app_->device_manager()); sort_model_->setSourceModel(app_->device_manager());
@@ -240,8 +240,8 @@ void DeviceView::contextMenuEvent(QContextMenuEvent *e) {
const QModelIndex collection_index = MapToCollection(menu_index_); const QModelIndex collection_index = MapToCollection(menu_index_);
if (device_index.isValid()) { if (device_index.isValid()) {
const bool is_plugged_in = app_->device_manager()->GetLister(device_index.row()); const bool is_plugged_in = app_->device_manager()->GetLister(device_index);
const bool is_remembered = app_->device_manager()->GetDatabaseId(device_index.row()) != -1; const bool is_remembered = app_->device_manager()->GetDatabaseId(device_index) != -1;
forget_action_->setEnabled(is_remembered); forget_action_->setEnabled(is_remembered);
eject_action_->setEnabled(is_plugged_in); eject_action_->setEnabled(is_plugged_in);
@@ -253,7 +253,7 @@ void DeviceView::contextMenuEvent(QContextMenuEvent *e) {
bool is_filesystem_device = false; bool is_filesystem_device = false;
if (parent_device_index.isValid()) { if (parent_device_index.isValid()) {
std::shared_ptr<ConnectedDevice> device = app_->device_manager()->GetConnectedDevice(parent_device_index.row()); std::shared_ptr<ConnectedDevice> device = app_->device_manager()->GetConnectedDevice(parent_device_index);
if (device && !device->LocalPath().isEmpty()) is_filesystem_device = true; if (device && !device->LocalPath().isEmpty()) is_filesystem_device = true;
} }
@@ -295,16 +295,14 @@ void DeviceView::Connect() {
app_->device_manager()->data(device_idx, MusicStorage::Role_StorageForceConnect); app_->device_manager()->data(device_idx, MusicStorage::Role_StorageForceConnect);
} }
void DeviceView::DeviceConnected(int row) { void DeviceView::DeviceConnected(QModelIndex idx) {
std::shared_ptr<ConnectedDevice> device = app_->device_manager()->GetConnectedDevice(row); if (!idx.isValid()) return;
std::shared_ptr<ConnectedDevice> device = app_->device_manager()->GetConnectedDevice(idx);
if (!device) return; if (!device) return;
DeviceInfo *info = app_->device_manager()->ItemFromRow(row); QModelIndex sort_idx = sort_model_->mapFromSource(idx);
if (!info) return;
QModelIndex index = app_->device_manager()->ItemToIndex(info);
if (!index.isValid()) return;
QModelIndex sort_idx = sort_model_->mapFromSource(index);
if (!sort_idx.isValid()) return; if (!sort_idx.isValid()) return;
QSortFilterProxyModel *sort_model = new QSortFilterProxyModel(device->model()); QSortFilterProxyModel *sort_model = new QSortFilterProxyModel(device->model());
@@ -318,19 +316,16 @@ void DeviceView::DeviceConnected(int row) {
} }
void DeviceView::DeviceDisconnected(int row) { void DeviceView::DeviceDisconnected(QModelIndex idx) {
DeviceInfo *info = app_->device_manager()->ItemFromRow(row); if (!idx.isValid()) return;
if (!info) return; merged_model_->RemoveSubModel(sort_model_->mapFromSource(idx));
QModelIndex index = app_->device_manager()->ItemToIndex(info);
if (!index.isValid()) return;
merged_model_->RemoveSubModel(sort_model_->mapFromSource(index));
} }
void DeviceView::Forget() { void DeviceView::Forget() {
QModelIndex device_idx = MapToDevice(menu_index_); QModelIndex device_idx = MapToDevice(menu_index_);
QString unique_id = app_->device_manager()->data(device_idx, DeviceManager::Role_UniqueId).toString(); QString unique_id = app_->device_manager()->data(device_idx, DeviceManager::Role_UniqueId).toString();
if (app_->device_manager()->GetLister(device_idx.row()) && app_->device_manager()->GetLister(device_idx.row())->AskForScan(unique_id)) { if (app_->device_manager()->GetLister(device_idx) && app_->device_manager()->GetLister(device_idx)->AskForScan(unique_id)) {
std::unique_ptr<QMessageBox> dialog(new QMessageBox( std::unique_ptr<QMessageBox> dialog(new QMessageBox(
QMessageBox::Question, tr("Forget device"), QMessageBox::Question, tr("Forget device"),
tr("Forgetting a device will remove it from this list and Strawberry will have to rescan all the songs again next time you connect it."), tr("Forgetting a device will remove it from this list and Strawberry will have to rescan all the songs again next time you connect it."),
@@ -341,12 +336,12 @@ void DeviceView::Forget() {
if (dialog->clickedButton() != forget) return; if (dialog->clickedButton() != forget) return;
} }
app_->device_manager()->Forget(device_idx.row()); app_->device_manager()->Forget(device_idx);
} }
void DeviceView::Properties() { void DeviceView::Properties() {
properties_dialog_->ShowDevice(MapToDevice(menu_index_).row()); properties_dialog_->ShowDevice(MapToDevice(menu_index_));
} }
void DeviceView::mouseDoubleClickEvent(QMouseEvent *event) { void DeviceView::mouseDoubleClickEvent(QMouseEvent *event) {
@@ -356,7 +351,7 @@ void DeviceView::mouseDoubleClickEvent(QMouseEvent *event) {
QModelIndex merged_index = indexAt(event->pos()); QModelIndex merged_index = indexAt(event->pos());
QModelIndex device_index = MapToDevice(merged_index); QModelIndex device_index = MapToDevice(merged_index);
if (device_index.isValid()) { if (device_index.isValid()) {
if (!app_->device_manager()->GetConnectedDevice(device_index.row())) { if (!app_->device_manager()->GetConnectedDevice(device_index)) {
menu_index_ = merged_index; menu_index_ = merged_index;
Connect(); Connect();
} }
@@ -439,7 +434,7 @@ void DeviceView::Organise() {
void DeviceView::Unmount() { void DeviceView::Unmount() {
QModelIndex device_idx = MapToDevice(menu_index_); QModelIndex device_idx = MapToDevice(menu_index_);
app_->device_manager()->Unmount(device_idx.row()); app_->device_manager()->Unmount(device_idx);
} }
void DeviceView::DeleteFinished(const SongList &songs_with_errors) { void DeviceView::DeleteFinished(const SongList &songs_with_errors) {

View File

@@ -88,13 +88,13 @@ class DeviceView : public AutoExpandingTreeView {
void Organise(); void Organise();
void Delete(); void Delete();
void DeviceConnected(int row); void DeviceConnected(QModelIndex idx);
void DeviceDisconnected(int row); void DeviceDisconnected(QModelIndex idx);
void DeleteFinished(const SongList &songs_with_errors); void DeleteFinished(const SongList &songs_with_errors);
// AutoExpandingTreeView // AutoExpandingTreeView
bool CanRecursivelyExpand(const QModelIndex &index) const; bool CanRecursivelyExpand(const QModelIndex &idx) const;
private: private:
QModelIndex MapToDevice(const QModelIndex &merged_model_index) const; QModelIndex MapToDevice(const QModelIndex &merged_model_index) const;

View File

@@ -44,7 +44,6 @@ FilesystemDevice::FilesystemDevice(const QUrl &url, DeviceLister *lister, const
watcher_->moveToThread(watcher_thread_); watcher_->moveToThread(watcher_thread_);
watcher_thread_->start(QThread::IdlePriority); watcher_thread_->start(QThread::IdlePriority);
qLog(Debug) << __PRETTY_FUNCTION__ << unique_id;
watcher_->set_device_name(manager->DeviceNameByID(unique_id)); watcher_->set_device_name(manager->DeviceNameByID(unique_id));
watcher_->set_backend(backend_); watcher_->set_backend(backend_);
watcher_->set_task_manager(app_->task_manager()); watcher_->set_task_manager(app_->task_manager());

View File

@@ -59,11 +59,10 @@ bool GPodDevice::Init() {
loader_ = new GPodLoader(url_.path(), app_->task_manager(), backend_, shared_from_this()); loader_ = new GPodLoader(url_.path(), app_->task_manager(), backend_, shared_from_this());
loader_->moveToThread(loader_thread_); loader_->moveToThread(loader_thread_);
connect(loader_, SIGNAL(Error(QString)), SIGNAL(Error(QString))); connect(loader_, SIGNAL(Error(QString)), SLOT(LoaderError(QString)));
connect(loader_, SIGNAL(TaskStarted(int)), SIGNAL(TaskStarted(int))); connect(loader_, SIGNAL(TaskStarted(int)), SIGNAL(TaskStarted(int)));
connect(loader_, SIGNAL(LoadFinished(Itdb_iTunesDB*)), SLOT(LoadFinished(Itdb_iTunesDB*))); connect(loader_, SIGNAL(LoadFinished(Itdb_iTunesDB*, bool)), SLOT(LoadFinished(Itdb_iTunesDB*, bool)));
connect(loader_thread_, SIGNAL(started()), loader_, SLOT(LoadDatabase())); connect(loader_thread_, SIGNAL(started()), loader_, SLOT(LoadDatabase()));
loader_thread_->start();
return true; return true;
@@ -71,7 +70,13 @@ bool GPodDevice::Init() {
GPodDevice::~GPodDevice() {} GPodDevice::~GPodDevice() {}
void GPodDevice::LoadFinished(Itdb_iTunesDB *db) { void GPodDevice::ConnectAsync() {
loader_thread_->start();
}
void GPodDevice::LoadFinished(Itdb_iTunesDB *db, bool success) {
QMutexLocker l(&db_mutex_); QMutexLocker l(&db_mutex_);
db_ = db; db_ = db;
@@ -87,8 +92,12 @@ void GPodDevice::LoadFinished(Itdb_iTunesDB *db) {
loader_->deleteLater(); loader_->deleteLater();
loader_ = nullptr; loader_ = nullptr;
emit ConnectFinished(unique_id_, success);
} }
void GPodDevice::LoaderError(const QString &message) { app_->AddError(message); }
bool GPodDevice::StartCopy(QList<Song::FileType> *supported_filetypes) { bool GPodDevice::StartCopy(QList<Song::FileType> *supported_filetypes) {
{ {
@@ -137,6 +146,29 @@ bool GPodDevice::CopyToStorage(const CopyJob &job) {
Itdb_Track *track = AddTrackToITunesDb(job.metadata_); Itdb_Track *track = AddTrackToITunesDb(job.metadata_);
if (job.albumcover_) {
bool result = false;
if (!job.metadata_.image().isNull()) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
result = itdb_track_set_thumbnails_from_data(track, job.metadata_.image().constBits(), job.metadata_.image().sizeInBytes());
track->has_artwork = 1;
#else
result = itdb_track_set_thumbnails_from_data(track, job.metadata_.image().constBits(), job.metadata_.image().byteCount());
track->has_artwork = 1;
#endif
}
else if (!job.cover_source_.isEmpty()) {
result = itdb_track_set_thumbnails(track, job.cover_source_.toLocal8Bit().constData());
track->has_artwork = 1;
}
else {
result = true;
}
if (!result) {
qLog(Error) << "failed to set album cover image";
}
}
// Copy the file // Copy the file
GError *error = nullptr; GError *error = nullptr;
itdb_cp_track_to_ipod(track, QDir::toNativeSeparators(job.source_).toLocal8Bit().constData(), &error); itdb_cp_track_to_ipod(track, QDir::toNativeSeparators(job.source_).toLocal8Bit().constData(), &error);

View File

@@ -47,18 +47,18 @@ class GPodLoader;
class GPodDevice : public ConnectedDevice, public virtual MusicStorage { class GPodDevice : public ConnectedDevice, public virtual MusicStorage {
Q_OBJECT Q_OBJECT
signals:
void Error(const QString &message);
public: public:
Q_INVOKABLE GPodDevice( Q_INVOKABLE GPodDevice(
const QUrl &url, DeviceLister *lister, const QUrl &url, DeviceLister *lister,
const QString &unique_id, DeviceManager *manager, const QString &unique_id,
DeviceManager *manager,
Application *app, Application *app,
int database_id, bool first_time); int database_id,
bool first_time);
~GPodDevice(); ~GPodDevice();
bool Init(); bool Init();
void ConnectAsync();
static QStringList url_schemes() { return QStringList() << "ipod"; } static QStringList url_schemes() { return QStringList() << "ipod"; }
@@ -73,7 +73,8 @@ class GPodDevice : public ConnectedDevice, public virtual MusicStorage {
void FinishDelete(bool success); void FinishDelete(bool success);
protected slots: protected slots:
void LoadFinished(Itdb_iTunesDB *db); void LoadFinished(Itdb_iTunesDB *db, bool success);
void LoaderError(const QString& message);
protected: protected:
Itdb_Track *AddTrackToITunesDb(const Song &metadata); Itdb_Track *AddTrackToITunesDb(const Song &metadata);

View File

@@ -52,6 +52,17 @@ void GPodLoader::LoadDatabase() {
int task_id = task_manager_->StartTask(tr("Loading iPod database")); int task_id = task_manager_->StartTask(tr("Loading iPod database"));
emit TaskStarted(task_id); emit TaskStarted(task_id);
Itdb_iTunesDB *db = TryLoad();
moveToThread(original_thread_);
task_manager_->SetTaskFinished(task_id);
emit LoadFinished(db, db);
}
Itdb_iTunesDB *GPodLoader::TryLoad() {
// Load the iTunes database // Load the iTunes database
GError *error = nullptr; GError *error = nullptr;
Itdb_iTunesDB *db = itdb_parse(QDir::toNativeSeparators(mount_point_).toLocal8Bit(), &error); Itdb_iTunesDB *db = itdb_parse(QDir::toNativeSeparators(mount_point_).toLocal8Bit(), &error);
@@ -62,12 +73,12 @@ void GPodLoader::LoadDatabase() {
qLog(Error) << "loading database failed:" << error->message; qLog(Error) << "loading database failed:" << error->message;
emit Error(QString::fromUtf8(error->message)); emit Error(QString::fromUtf8(error->message));
g_error_free(error); g_error_free(error);
} else { }
else {
emit Error(tr("An error occurred loading the iTunes database")); emit Error(tr("An error occurred loading the iTunes database"));
} }
task_manager_->SetTaskFinished(task_id); return db;
return;
} }
// Convert all the tracks from libgpod structs into Song classes // Convert all the tracks from libgpod structs into Song classes
@@ -91,10 +102,6 @@ void GPodLoader::LoadDatabase() {
// Add the songs we've just loaded // Add the songs we've just loaded
backend_->AddOrUpdateSongs(songs); backend_->AddOrUpdateSongs(songs);
moveToThread(original_thread_); return db;
task_manager_->SetTaskFinished(task_id);
emit LoadFinished(db);
} }

View File

@@ -52,7 +52,10 @@ class GPodLoader : public QObject {
signals: signals:
void Error(const QString &message); void Error(const QString &message);
void TaskStarted(int task_id); void TaskStarted(int task_id);
void LoadFinished(Itdb_iTunesDB *db); void LoadFinished(Itdb_iTunesDB *db, bool success);
private:
Itdb_iTunesDB *TryLoad();
private: private:
std::shared_ptr<ConnectedDevice> device_; std::shared_ptr<ConnectedDevice> device_;

View File

@@ -64,36 +64,39 @@ MtpDevice::~MtpDevice() {}
bool MtpDevice::Init() { bool MtpDevice::Init() {
InitBackendDirectory("/", first_time_, false); InitBackendDirectory("/", first_time_, false);
model_->Init();
loader_ = new MtpLoader(url_, app_->task_manager(), backend_, shared_from_this()); loader_ = new MtpLoader(url_, app_->task_manager(), backend_, shared_from_this());
if (!loader_->Init()) {
delete loader_;
loader_ = nullptr;
return false;
}
model_->Init();
loader_->moveToThread(loader_thread_); loader_->moveToThread(loader_thread_);
connect(loader_, SIGNAL(Error(QString)), SIGNAL(Error(QString))); connect(loader_, SIGNAL(Error(QString)), SLOT(LoaderError(QString)));
connect(loader_, SIGNAL(TaskStarted(int)), SIGNAL(TaskStarted(int))); connect(loader_, SIGNAL(TaskStarted(int)), SIGNAL(TaskStarted(int)));
connect(loader_, SIGNAL(LoadFinished()), SLOT(LoadFinished())); connect(loader_, SIGNAL(LoadFinished(bool)), SLOT(LoadFinished(bool)));
connect(loader_thread_, SIGNAL(started()), loader_, SLOT(LoadDatabase())); connect(loader_thread_, SIGNAL(started()), loader_, SLOT(LoadDatabase()));
db_busy_.lock();
loader_thread_->start();
return true; return true;
} }
void MtpDevice::LoadFinished() { void MtpDevice::ConnectAsync() {
db_busy_.lock();
loader_thread_->start();
}
void MtpDevice::LoadFinished(bool success) {
loader_->deleteLater(); loader_->deleteLater();
loader_ = nullptr; loader_ = nullptr;
db_busy_.unlock(); db_busy_.unlock();
emit ConnectFinished(unique_id_, success);
} }
void MtpDevice::LoaderError(const QString& message) { app_->AddError(message); }
bool MtpDevice::StartCopy(QList<Song::FileType> *supported_types) { bool MtpDevice::StartCopy(QList<Song::FileType> *supported_types) {
// Ensure only one "organise files" can be active at any one time // Ensure only one "organise files" can be active at any one time

View File

@@ -54,6 +54,7 @@ class MtpDevice : public ConnectedDevice {
static QStringList url_schemes() { return QStringList() << "mtp" << "gphoto2"; } static QStringList url_schemes() { return QStringList() << "mtp" << "gphoto2"; }
bool Init(); bool Init();
void ConnectAsync();
bool GetSupportedFiletypes(QList<Song::FileType>* ret); bool GetSupportedFiletypes(QList<Song::FileType>* ret);
int GetFreeSpace(); int GetFreeSpace();
@@ -68,7 +69,8 @@ class MtpDevice : public ConnectedDevice {
void FinishDelete(bool success); void FinishDelete(bool success);
private slots: private slots:
void LoadFinished(); void LoadFinished(bool success);
void LoaderError(const QString& message);
private: private:
bool GetSupportedFiletypes(QList<Song::FileType> *ret, LIBMTP_mtpdevice_struct *device); bool GetSupportedFiletypes(QList<Song::FileType> *ret, LIBMTP_mtpdevice_struct *device);

View File

@@ -40,34 +40,35 @@ MtpLoader::MtpLoader(const QUrl &url, TaskManager *task_manager, CollectionBacke
original_thread_ = thread(); original_thread_ = thread();
} }
MtpLoader::~MtpLoader() { MtpLoader::~MtpLoader() {}
delete connection_;
}
bool MtpLoader::Init() { bool MtpLoader::Init() { return true; }
connection_ = new MtpConnection(url_);
return connection_->is_valid();
}
void MtpLoader::LoadDatabase() { void MtpLoader::LoadDatabase() {
int task_id = task_manager_->StartTask(tr("Loading MTP device")); int task_id = task_manager_->StartTask(tr("Loading MTP device"));
emit TaskStarted(task_id); emit TaskStarted(task_id);
TryLoad(); bool success = TryLoad();
moveToThread(original_thread_); moveToThread(original_thread_);
task_manager_->SetTaskFinished(task_id); task_manager_->SetTaskFinished(task_id);
emit LoadFinished(); emit LoadFinished(success);
} }
bool MtpLoader::TryLoad() { bool MtpLoader::TryLoad() {
MtpConnection dev(url_);
if (!dev.is_valid()) {
emit Error(tr("Error connecting MTP device"));
return false;
}
// Load the list of songs on the device // Load the list of songs on the device
SongList songs; SongList songs;
LIBMTP_track_t* tracks = LIBMTP_Get_Tracklisting_With_Callback(connection_->device(), nullptr, nullptr); LIBMTP_track_t* tracks = LIBMTP_Get_Tracklisting_With_Callback(dev.device(), nullptr, nullptr);
while (tracks) { while (tracks) {
LIBMTP_track_t *track = tracks; LIBMTP_track_t *track = tracks;

View File

@@ -51,7 +51,7 @@ class MtpLoader : public QObject {
signals: signals:
void Error(const QString &message); void Error(const QString &message);
void TaskStarted(int task_id); void TaskStarted(int task_id);
void LoadFinished(); void LoadFinished(bool success);
private: private:
bool TryLoad(); bool TryLoad();

View File

@@ -148,7 +148,7 @@ QString About::ContributorsHtml() const {
} }
ret += QString("</p>"); ret += QString("</p>");
ret += QString("<p>... and all the Amarok and Clementine contributors</p>"); ret += QString("<p>Thanks to all the Amarok and Clementine contributors.</p>");
return ret; return ret;
} }

View File

@@ -35,20 +35,20 @@
#include "core/logging.h" #include "core/logging.h"
#include "globalshortcuts.h" #include "globalshortcuts.h"
#include "globalshortcutbackend.h" #include "globalshortcutbackend.h"
#include "globalshortcutbackend-dbus.h" #include "globalshortcutbackend-gsd.h"
const char *GlobalShortcutBackendDBus::kGsdService = "org.gnome.SettingsDaemon.MediaKeys"; const char *GlobalShortcutBackendGSD::kGsdService = "org.gnome.SettingsDaemon.MediaKeys";
const char *GlobalShortcutBackendDBus::kGsdService2 = "org.gnome.SettingsDaemon"; const char *GlobalShortcutBackendGSD::kGsdService2 = "org.gnome.SettingsDaemon";
const char *GlobalShortcutBackendDBus::kGsdPath = "/org/gnome/SettingsDaemon/MediaKeys"; const char *GlobalShortcutBackendGSD::kGsdPath = "/org/gnome/SettingsDaemon/MediaKeys";
GlobalShortcutBackendDBus::GlobalShortcutBackendDBus(GlobalShortcuts *parent) GlobalShortcutBackendGSD::GlobalShortcutBackendGSD(GlobalShortcuts *parent)
: GlobalShortcutBackend(parent), : GlobalShortcutBackend(parent),
interface_(nullptr), interface_(nullptr),
is_connected_(false) {} is_connected_(false) {}
GlobalShortcutBackendDBus::~GlobalShortcutBackendDBus(){} GlobalShortcutBackendGSD::~GlobalShortcutBackendGSD(){}
bool GlobalShortcutBackendDBus::DoRegister() { bool GlobalShortcutBackendGSD::DoRegister() {
qLog(Debug) << "Registering"; qLog(Debug) << "Registering";
@@ -75,7 +75,7 @@ bool GlobalShortcutBackendDBus::DoRegister() {
} }
void GlobalShortcutBackendDBus::RegisterFinished(QDBusPendingCallWatcher *watcher) { void GlobalShortcutBackendGSD::RegisterFinished(QDBusPendingCallWatcher *watcher) {
QDBusMessage reply = watcher->reply(); QDBusMessage reply = watcher->reply();
watcher->deleteLater(); watcher->deleteLater();
@@ -92,7 +92,7 @@ void GlobalShortcutBackendDBus::RegisterFinished(QDBusPendingCallWatcher *watche
} }
void GlobalShortcutBackendDBus::DoUnregister() { void GlobalShortcutBackendGSD::DoUnregister() {
qLog(Debug) << "Unregister"; qLog(Debug) << "Unregister";
@@ -108,7 +108,7 @@ void GlobalShortcutBackendDBus::DoUnregister() {
} }
void GlobalShortcutBackendDBus::GnomeMediaKeyPressed(const QString&, const QString& key) { void GlobalShortcutBackendGSD::GnomeMediaKeyPressed(const QString&, const QString& key) {
if (key == "Play") manager_->shortcuts()["play_pause"].action->trigger(); if (key == "Play") manager_->shortcuts()["play_pause"].action->trigger();
if (key == "Stop") manager_->shortcuts()["stop"].action->trigger(); if (key == "Stop") manager_->shortcuts()["stop"].action->trigger();
if (key == "Next") manager_->shortcuts()["next_track"].action->trigger(); if (key == "Next") manager_->shortcuts()["next_track"].action->trigger();

View File

@@ -18,8 +18,8 @@
* *
*/ */
#ifndef GLOBALSHORTCUTBACKEND_DBUS_H #ifndef GLOBALSHORTCUTBACKEND_GSD_H
#define GLOBALSHORTCUTBACKEND_DBUS_H #define GLOBALSHORTCUTBACKEND_GSD_H
#include "config.h" #include "config.h"
@@ -38,12 +38,12 @@
class GlobalShortcuts; class GlobalShortcuts;
class OrgGnomeSettingsDaemonMediaKeysInterface; class OrgGnomeSettingsDaemonMediaKeysInterface;
class GlobalShortcutBackendDBus : public GlobalShortcutBackend { class GlobalShortcutBackendGSD : public GlobalShortcutBackend {
Q_OBJECT Q_OBJECT
public: public:
explicit GlobalShortcutBackendDBus(GlobalShortcuts *parent); explicit GlobalShortcutBackendGSD(GlobalShortcuts *parent);
~GlobalShortcutBackendDBus(); ~GlobalShortcutBackendGSD();
static const char *kGsdService; static const char *kGsdService;
static const char *kGsdService2; static const char *kGsdService2;
@@ -65,4 +65,4 @@ class GlobalShortcutBackendDBus : public GlobalShortcutBackend {
}; };
#endif // GLOBALSHORTCUTBACKEND_DBUS_H #endif // GLOBALSHORTCUTBACKEND_GSD_H

View File

@@ -37,7 +37,7 @@
#include "globalshortcutbackend.h" #include "globalshortcutbackend.h"
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
# include "globalshortcutbackend-dbus.h" # include "globalshortcutbackend-gsd.h"
#endif #endif
#if defined(HAVE_X11) || defined(Q_OS_WIN) #if defined(HAVE_X11) || defined(Q_OS_WIN)
# include "globalshortcutbackend-system.h" # include "globalshortcutbackend-system.h"
@@ -52,7 +52,7 @@ GlobalShortcuts::GlobalShortcuts(QWidget *parent)
: QWidget(parent), : QWidget(parent),
dbus_backend_(nullptr), dbus_backend_(nullptr),
system_backend_(nullptr), system_backend_(nullptr),
use_dbus_(true), use_gsd_(true),
use_x11_(false) use_x11_(false)
{ {
@@ -80,7 +80,7 @@ GlobalShortcuts::GlobalShortcuts(QWidget *parent)
// Create backends - these do the actual shortcut registration // Create backends - these do the actual shortcut registration
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
dbus_backend_ = new GlobalShortcutBackendDBus(this); dbus_backend_ = new GlobalShortcutBackendGSD(this);
#endif #endif
#if defined(HAVE_X11) || defined(Q_OS_WIN) #if defined(HAVE_X11) || defined(Q_OS_WIN)
@@ -97,7 +97,7 @@ GlobalShortcuts::GlobalShortcuts(QWidget *parent)
void GlobalShortcuts::ReloadSettings() { void GlobalShortcuts::ReloadSettings() {
// The actual shortcuts have been set in our actions for us by the config dialog already - we just need to reread the gnome settings. // The actual shortcuts have been set in our actions for us by the config dialog already - we just need to reread the gnome settings.
use_dbus_ = settings_.value("use_dbus", true).toBool(); use_gsd_ = settings_.value("use_gsd", true).toBool();
use_x11_ = settings_.value("use_x11", true).toBool(); use_x11_ = settings_.value("use_x11", true).toBool();
Unregister(); Unregister();
@@ -134,7 +134,7 @@ GlobalShortcuts::Shortcut GlobalShortcuts::AddShortcut(const QString &id, const
bool GlobalShortcuts::IsGsdAvailable() const { bool GlobalShortcuts::IsGsdAvailable() const {
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
return QDBusConnection::sessionBus().interface()->isServiceRegistered(GlobalShortcutBackendDBus::kGsdService) || QDBusConnection::sessionBus().interface()->isServiceRegistered(GlobalShortcutBackendDBus::kGsdService2); return QDBusConnection::sessionBus().interface()->isServiceRegistered(GlobalShortcutBackendGSD::kGsdService) || QDBusConnection::sessionBus().interface()->isServiceRegistered(GlobalShortcutBackendGSD::kGsdService2);
#else #else
return false; return false;
#endif #endif
@@ -152,7 +152,7 @@ bool GlobalShortcuts::IsX11Available() const {
} }
void GlobalShortcuts::Register() { void GlobalShortcuts::Register() {
if (use_dbus_ && dbus_backend_ && dbus_backend_->Register()) return; if (use_gsd_ && dbus_backend_ && dbus_backend_->Register()) return;
#ifdef HAVE_X11 // If this system has X11, only use the system backend if X11 is enabled in the global shortcut settings #ifdef HAVE_X11 // If this system has X11, only use the system backend if X11 is enabled in the global shortcut settings
if (use_x11_) if (use_x11_)
#endif #endif

View File

@@ -97,7 +97,7 @@ class GlobalShortcuts : public QWidget {
QMap<QString, Shortcut> shortcuts_; QMap<QString, Shortcut> shortcuts_;
QSettings settings_; QSettings settings_;
bool use_dbus_; bool use_gsd_;
bool use_x11_; bool use_x11_;
}; };

View File

@@ -33,8 +33,6 @@
#endif #endif
#ifdef HAVE_XF86KEYSYM_H #ifdef HAVE_XF86KEYSYM_H
# include <X11/XF86keysym.h> # include <X11/XF86keysym.h>
#else
# warning "Missing X11/XF86keysym.h"
#endif #endif
namespace KeyMapperX11 { namespace KeyMapperX11 {

View File

@@ -25,12 +25,12 @@
#include <QString> #include <QString>
#include "core/logging.h" #include "core/logging.h"
#include "core/song.h"
#include "lyricsfetcher.h" #include "lyricsfetcher.h"
#include "lyricsfetchersearch.h" #include "lyricsfetchersearch.h"
const int LyricsFetcher::kMaxConcurrentRequests = 5; const int LyricsFetcher::kMaxConcurrentRequests = 5;
const QRegExp LyricsFetcher::kRemoveNonAlpha("[^a-zA-Z0-9\\d\\s]"); const QRegExp LyricsFetcher::kRemoveNonAlpha("[^a-zA-Z0-9\\d\\s]");
const QRegExp LyricsFetcher::kRemoveFromTitle(" ?-? ((\\(|\\[)?)(Remastered|Live) ?((\\)|\\])?)$");
LyricsFetcher::LyricsFetcher(LyricsProviders *lyrics_providers, QObject *parent) LyricsFetcher::LyricsFetcher(LyricsProviders *lyrics_providers, QObject *parent)
: QObject(parent), : QObject(parent),
@@ -50,10 +50,10 @@ quint64 LyricsFetcher::Search(const QString &artist, const QString &album, const
request.artist = artist; request.artist = artist;
request.album = album; request.album = album;
request.album.remove(kRemoveNonAlpha); request.album.remove(kRemoveNonAlpha);
request.album.remove(kRemoveFromTitle); request.album.remove(Song::kAlbumRemoveMisc);
request.title = title; request.title = title;
request.title.remove(kRemoveNonAlpha); request.title.remove(kRemoveNonAlpha);
request.title.remove(kRemoveFromTitle); request.title.remove(Song::kTitleRemoveMisc);
request.id = next_id_++; request.id = next_id_++;
AddRequest(request); AddRequest(request);

View File

@@ -65,7 +65,6 @@ class LyricsFetcher : public QObject {
static const int kMaxConcurrentRequests; static const int kMaxConcurrentRequests;
static const QRegExp kRemoveNonAlpha; static const QRegExp kRemoveNonAlpha;
static const QRegExp kRemoveFromTitle;
quint64 Search(const QString &artist, const QString &album, const QString &title); quint64 Search(const QString &artist, const QString &album, const QString &title);
void Clear(); void Clear();

View File

@@ -124,12 +124,23 @@ int main(int argc, char* argv[]) {
CommandlineOptions options(argc, argv); CommandlineOptions options(argc, argv);
{ {
// Only start a core application now so we can check if there's another instance without requiring an X server.
// This MUST be done before parsing the commandline options so QTextCodec gets the right system locale for filenames. // This MUST be done before parsing the commandline options so QTextCodec gets the right system locale for filenames.
SingleCoreApplication a(argc, argv, true, SingleCoreApplication::Mode::User); SingleCoreApplication core_app(argc, argv, true, SingleCoreApplication::Mode::User);
Utilities::CheckPortable(); Utilities::CheckPortable();
// Parse commandline options - need to do this before starting the full QApplication so it works without an X server // Parse commandline options - need to do this before starting the full QApplication so it works without an X server
if (!options.Parse()) return 1; if (!options.Parse()) return 1;
logging::SetLevels(options.log_levels()); logging::SetLevels(options.log_levels());
if (core_app.isSecondary()) {
if (options.is_empty()) {
qLog(Info) << "Strawberry is already running - activating existing window (1)";
}
if (core_app.sendMessage(options.Serialize(), 5000)) {
main_exit_safe(0);
return 0;
}
// Couldn't send the message so start anyway
}
} }
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
@@ -147,10 +158,12 @@ int main(int argc, char* argv[]) {
Utilities::IncreaseFDLimit(); Utilities::IncreaseFDLimit();
// important: Do not remove this.
// This must also be done as a SingleApplication, in case SingleCoreApplication was compiled with a different appdata.
SingleApplication a(argc, argv, true, SingleApplication::Mode::User); SingleApplication a(argc, argv, true, SingleApplication::Mode::User);
if (a.isSecondary()) { if (a.isSecondary()) {
if (options.is_empty()) { if (options.is_empty()) {
qLog(Info) << "Strawberry is already running - activating existing window"; qLog(Info) << "Strawberry is already running - activating existing window (2)";
} }
if (a.sendMessage(options.Serialize(), 5000)) { if (a.sendMessage(options.Serialize(), 5000)) {
main_exit_safe(0); main_exit_safe(0);

View File

@@ -2,6 +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-2019, 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
@@ -28,12 +29,14 @@
#include <QString> #include <QString>
#include <QStringBuilder> #include <QStringBuilder>
#include <QUrl> #include <QUrl>
#include <QStandardPaths>
#include <QtDebug> #include <QtDebug>
#include "core/logging.h" #include "core/logging.h"
#include "core/utilities.h" #include "core/utilities.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "core/musicstorage.h" #include "core/musicstorage.h"
#include "core/tagreaderclient.h"
#include "organise.h" #include "organise.h"
#ifdef HAVE_GSTREAMER #ifdef HAVE_GSTREAMER
# include "transcoder/transcoder.h" # include "transcoder/transcoder.h"
@@ -48,7 +51,7 @@ const int Organise::kBatchSize = 10;
const int Organise::kTranscodeProgressInterval = 500; const int Organise::kTranscodeProgressInterval = 500;
#endif #endif
Organise::Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> destination, const OrganiseFormat &format, bool copy, bool overwrite, bool mark_as_listened, const NewSongInfoList &songs_info, bool eject_after) Organise::Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> destination, const OrganiseFormat &format, bool copy, bool overwrite, bool mark_as_listened, bool albumcover, const NewSongInfoList &songs_info, bool eject_after)
: thread_(nullptr), : thread_(nullptr),
task_manager_(task_manager), task_manager_(task_manager),
#ifdef HAVE_GSTREAMER #ifdef HAVE_GSTREAMER
@@ -59,11 +62,9 @@ Organise::Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> dest
copy_(copy), copy_(copy),
overwrite_(overwrite), overwrite_(overwrite),
mark_as_listened_(mark_as_listened), mark_as_listened_(mark_as_listened),
albumcover_(albumcover),
eject_after_(eject_after), eject_after_(eject_after),
task_count_(songs_info.count()), task_count_(songs_info.count()),
#ifdef HAVE_GSTREAMER
transcode_suffix_(1),
#endif
tasks_complete_(0), tasks_complete_(0),
started_(false), started_(false),
task_id_(0), task_id_(0),
@@ -88,6 +89,7 @@ void Organise::Start() {
connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles())); connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles()));
#ifdef HAVE_GSTREAMER #ifdef HAVE_GSTREAMER
connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), SLOT(FileTranscoded(QString, QString, bool))); connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), SLOT(FileTranscoded(QString, QString, bool)));
connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString)));
#endif #endif
moveToThread(thread_); moveToThread(thread_);
@@ -97,10 +99,6 @@ void Organise::Start() {
void Organise::ProcessSomeFiles() { void Organise::ProcessSomeFiles() {
if (!started_) { if (!started_) {
#ifdef HAVE_GSTREAMER
transcode_temp_name_.open();
#endif
if (!destination_->StartCopy(&supported_filetypes_)) { if (!destination_->StartCopy(&supported_filetypes_)) {
// Failed to start - mark everything as failed :( // Failed to start - mark everything as failed :(
for (const Task &task : tasks_pending_) files_with_errors_ << task.song_info_.song_.url().toLocalFile(); for (const Task &task : tasks_pending_) files_with_errors_ << task.song_info_.song_.url().toLocalFile();
@@ -127,7 +125,7 @@ void Organise::ProcessSomeFiles() {
task_manager_->SetTaskFinished(task_id_); task_manager_->SetTaskFinished(task_id_);
emit Finished(files_with_errors_); emit Finished(files_with_errors_, log_);
// Move back to the original thread so deleteLater() can get called in the main thread's event loop // Move back to the original thread so deleteLater() can get called in the main thread's event loop
moveToThread(original_thread_); moveToThread(original_thread_);
@@ -151,6 +149,10 @@ void Organise::ProcessSomeFiles() {
Song song = task.song_info_.song_; Song song = task.song_info_.song_;
if (!song.is_valid()) continue; if (!song.is_valid()) continue;
// Get embedded album cover
QImage cover = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task.song_info_.song_.url().toLocalFile());
if (!cover.isNull()) song.set_image(cover);
#ifdef HAVE_GSTREAMER #ifdef HAVE_GSTREAMER
// Maybe this file is one that's been transcoded already? // Maybe this file is one that's been transcoded already?
if (!task.transcoded_filename_.isEmpty()) { if (!task.transcoded_filename_.isEmpty()) {
@@ -174,12 +176,10 @@ void Organise::ProcessSomeFiles() {
TranscoderPreset preset = Transcoder::PresetForFileType(dest_type); TranscoderPreset preset = Transcoder::PresetForFileType(dest_type);
qLog(Debug) << "Transcoding with" << preset.name_; qLog(Debug) << "Transcoding with" << preset.name_;
// Get a temporary name for the transcoded file task.transcoded_filename_ = transcoder_->GetFile(task.song_info_.song_.url().toLocalFile(), preset);
task.transcoded_filename_ = transcode_temp_name_.fileName() + "-" + QString::number(transcode_suffix_++);
task.new_extension_ = preset.extension_; task.new_extension_ = preset.extension_;
task.new_filetype_ = dest_type; task.new_filetype_ = dest_type;
tasks_transcoding_[task.song_info_.song_.url().toLocalFile()] = task; tasks_transcoding_[task.song_info_.song_.url().toLocalFile()] = task;
qLog(Debug) << "Transcoding to" << task.transcoded_filename_; qLog(Debug) << "Transcoding to" << task.transcoded_filename_;
// Start the transcoding - this will happen in the background and FileTranscoded() will get called when it's done. // Start the transcoding - this will happen in the background and FileTranscoded() will get called when it's done.
@@ -197,12 +197,25 @@ void Organise::ProcessSomeFiles() {
job.metadata_ = song; job.metadata_ = song;
job.overwrite_ = overwrite_; job.overwrite_ = overwrite_;
job.mark_as_listened_ = mark_as_listened_; job.mark_as_listened_ = mark_as_listened_;
job.albumcover_ = albumcover_;
job.remove_original_ = !copy_; job.remove_original_ = !copy_;
if (!task.song_info_.song_.art_manual().isEmpty()) {
job.cover_source_ = task.song_info_.song_.art_manual();
}
else if (!task.song_info_.song_.art_automatic().isEmpty()) {
job.cover_source_ = task.song_info_.song_.art_automatic();
}
if (!job.cover_source_.isEmpty()) {
job.cover_dest_ = QFileInfo(job.destination_).path() + "/" + QFileInfo(job.cover_source_).fileName();
}
job.progress_ = std::bind(&Organise::SetSongProgress, this, _1, !task.transcoded_filename_.isEmpty()); job.progress_ = std::bind(&Organise::SetSongProgress, this, _1, !task.transcoded_filename_.isEmpty());
if (!destination_->CopyToStorage(job)) { if (!destination_->CopyToStorage(job)) {
files_with_errors_ << task.song_info_.song_.basefilename(); files_with_errors_ << task.song_info_.song_.basefilename();
} else { }
else {
if (job.mark_as_listened_) { if (job.mark_as_listened_) {
emit FileCopied(job.metadata_.id()); emit FileCopied(job.metadata_.id());
} }
@@ -320,3 +333,9 @@ void Organise::timerEvent(QTimerEvent *e) {
} }
void Organise::LogLine(const QString message) {
QString date(QDateTime::currentDateTime().toString(Qt::TextDate));
log_.append(QString("%1: %2").arg(date, message));
}

View File

@@ -2,6 +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-2019, 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
@@ -28,14 +29,14 @@
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
#include <QTemporaryFile>
#include <QBasicTimer> #include <QBasicTimer>
#include <QList> #include <QList>
#include <QVector>
#include <QMap> #include <QMap>
#include <QSet> #include <QSet>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <QVector> #include <QTemporaryFile>
#include "core/song.h" #include "core/song.h"
#include "organiseformat.h" #include "organiseformat.h"
@@ -59,7 +60,7 @@ class Organise : public QObject {
}; };
typedef QList<NewSongInfo> NewSongInfoList; typedef QList<NewSongInfo> NewSongInfoList;
Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> destination, const OrganiseFormat &format, bool copy, bool overwrite, bool mark_as_listened, const NewSongInfoList &songs, bool eject_after); Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> destination, const OrganiseFormat &format, bool copy, bool overwrite, bool mark_as_listened, bool albumcover, const NewSongInfoList &songs, bool eject_after);
static const int kBatchSize; static const int kBatchSize;
#ifdef HAVE_GSTREAMER #ifdef HAVE_GSTREAMER
@@ -68,8 +69,8 @@ class Organise : public QObject {
void Start(); void Start();
signals: signals:
void Finished(const QStringList &files_with_errors); void Finished(const QStringList &files_with_errors, QStringList);
void FileCopied(int database_id); void FileCopied(int database_id);
protected: protected:
@@ -80,6 +81,7 @@ signals:
#ifdef HAVE_GSTREAMER #ifdef HAVE_GSTREAMER
void FileTranscoded(const QString &input, const QString &output, bool success); void FileTranscoded(const QString &input, const QString &output, bool success);
#endif #endif
void LogLine(const QString message);
private: private:
void SetSongProgress(float progress, bool transcoded = false); void SetSongProgress(float progress, bool transcoded = false);
@@ -115,13 +117,12 @@ signals:
const bool copy_; const bool copy_;
const bool overwrite_; const bool overwrite_;
const bool mark_as_listened_; const bool mark_as_listened_;
const bool albumcover_;
const bool eject_after_; const bool eject_after_;
int task_count_; int task_count_;
#ifdef HAVE_GSTREAMER #ifdef HAVE_GSTREAMER
QBasicTimer transcode_progress_timer_; QBasicTimer transcode_progress_timer_;
QTemporaryFile transcode_temp_name_;
int transcode_suffix_;
#endif #endif
QList<Task> tasks_pending_; QList<Task> tasks_pending_;
@@ -136,6 +137,7 @@ signals:
int current_copy_progress_; int current_copy_progress_;
QStringList files_with_errors_; QStringList files_with_errors_;
QStringList log_;
}; };
#endif // ORGANISE_H #endif // ORGANISE_H

View File

@@ -2,6 +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-2019, 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
@@ -335,6 +336,7 @@ void OrganiseDialog::Reset() {
ui_->replace_spaces->setChecked(true); ui_->replace_spaces->setChecked(true);
ui_->overwrite->setChecked(false); ui_->overwrite->setChecked(false);
ui_->mark_as_listened->setChecked(false); ui_->mark_as_listened->setChecked(false);
ui_->albumcover->setChecked(true);
ui_->eject_after->setChecked(false); ui_->eject_after->setChecked(false);
} }
@@ -350,6 +352,7 @@ void OrganiseDialog::showEvent(QShowEvent*) {
ui_->remove_non_ascii->setChecked(s.value("remove_non_ascii", false).toBool()); ui_->remove_non_ascii->setChecked(s.value("remove_non_ascii", false).toBool());
ui_->replace_spaces->setChecked(s.value("replace_spaces", true).toBool()); ui_->replace_spaces->setChecked(s.value("replace_spaces", true).toBool());
ui_->overwrite->setChecked(s.value("overwrite", false).toBool()); ui_->overwrite->setChecked(s.value("overwrite", false).toBool());
ui_->albumcover->setChecked(s.value("albumcover", true).toBool());
ui_->mark_as_listened->setChecked(s.value("mark_as_listened", false).toBool()); ui_->mark_as_listened->setChecked(s.value("mark_as_listened", false).toBool());
ui_->eject_after->setChecked(s.value("eject_after", false).toBool()); ui_->eject_after->setChecked(s.value("eject_after", false).toBool());
@@ -372,6 +375,7 @@ void OrganiseDialog::accept() {
s.setValue("replace_spaces", ui_->replace_spaces->isChecked()); s.setValue("replace_spaces", ui_->replace_spaces->isChecked());
s.setValue("overwrite", ui_->overwrite->isChecked()); s.setValue("overwrite", ui_->overwrite->isChecked());
s.setValue("mark_as_listened", ui_->overwrite->isChecked()); s.setValue("mark_as_listened", ui_->overwrite->isChecked());
s.setValue("albumcover", ui_->albumcover->isChecked());
s.setValue("destination", ui_->destination->currentText()); s.setValue("destination", ui_->destination->currentText());
s.setValue("eject_after", ui_->eject_after->isChecked()); s.setValue("eject_after", ui_->eject_after->isChecked());
@@ -382,19 +386,19 @@ void OrganiseDialog::accept() {
// It deletes itself when it's finished. // It deletes itself when it's finished.
const bool copy = ui_->aftercopying->currentIndex() == 0; const bool copy = ui_->aftercopying->currentIndex() == 0;
Organise *organise = new Organise(task_manager_, storage, format_, copy, ui_->overwrite->isChecked(), ui_->mark_as_listened->isChecked(), new_songs_info_, ui_->eject_after->isChecked()); Organise *organise = new Organise(task_manager_, storage, format_, copy, ui_->overwrite->isChecked(), ui_->mark_as_listened->isChecked(), ui_->albumcover->isChecked(), new_songs_info_, ui_->eject_after->isChecked());
connect(organise, SIGNAL(Finished(QStringList)), SLOT(OrganiseFinished(QStringList))); connect(organise, SIGNAL(Finished(QStringList, QStringList)), SLOT(OrganiseFinished(QStringList, QStringList)));
connect(organise, SIGNAL(FileCopied(int)), this, SIGNAL(FileCopied(int))); connect(organise, SIGNAL(FileCopied(int)), this, SIGNAL(FileCopied(int)));
organise->Start(); organise->Start();
QDialog::accept(); QDialog::accept();
} }
void OrganiseDialog::OrganiseFinished(const QStringList &files_with_errors) { void OrganiseDialog::OrganiseFinished(const QStringList files_with_errors, const QStringList log) {
if (files_with_errors.isEmpty()) return; if (files_with_errors.isEmpty()) return;
error_dialog_.reset(new OrganiseErrorDialog); error_dialog_.reset(new OrganiseErrorDialog);
error_dialog_->Show(OrganiseErrorDialog::Type_Copy, files_with_errors); error_dialog_->Show(OrganiseErrorDialog::Type_Copy, files_with_errors, log);
} }
void OrganiseDialog::resizeEvent(QResizeEvent *e) { void OrganiseDialog::resizeEvent(QResizeEvent *e) {

View File

@@ -2,6 +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-2019, 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
@@ -88,7 +89,7 @@ class OrganiseDialog : public QDialog {
void InsertTag(const QString &tag); void InsertTag(const QString &tag);
void UpdatePreviews(); void UpdatePreviews();
void OrganiseFinished(const QStringList &files_with_errors); void OrganiseFinished(const QStringList files_with_errors, const QStringList log);
private: private:
SongList LoadSongsBlocking(const QStringList &filenames); SongList LoadSongsBlocking(const QStringList &filenames);

View File

@@ -126,6 +126,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="albumcover">
<property name="text">
<string>Copy album cover artwork</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@@ -2,6 +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-2019, 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
@@ -52,17 +53,17 @@ OrganiseErrorDialog::~OrganiseErrorDialog() {
delete ui_; delete ui_;
} }
void OrganiseErrorDialog::Show(OperationType type, const SongList &songs_with_errors) { void OrganiseErrorDialog::Show(OperationType type, const SongList &songs_with_errors, const QStringList &log) {
QStringList files; QStringList files;
for (const Song &song : songs_with_errors) { for (const Song &song : songs_with_errors) {
files << song.url().toLocalFile(); files << song.url().toLocalFile();
} }
Show(type, files); Show(type, files, log);
} }
void OrganiseErrorDialog::Show(OperationType type, const QStringList &files_with_errors) { void OrganiseErrorDialog::Show(OperationType type, const QStringList &files_with_errors, const QStringList &log) {
QStringList sorted_files = files_with_errors; QStringList sorted_files = files_with_errors;
std::stable_sort(sorted_files.begin(), sorted_files.end()); std::stable_sort(sorted_files.begin(), sorted_files.end());
@@ -79,7 +80,8 @@ void OrganiseErrorDialog::Show(OperationType type, const QStringList &files_with
break; break;
} }
ui_->list->addItems(sorted_files); ui_->files->addItems(sorted_files);
ui_->log->addItems(log);
show(); show();
} }

View File

@@ -2,6 +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-2019, 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
@@ -45,8 +46,8 @@ class OrganiseErrorDialog : public QDialog {
Type_Delete, Type_Delete,
}; };
void Show(OperationType type, const SongList& songs_with_errors); void Show(OperationType type, const SongList &songs_with_errors, const QStringList &log = QStringList());
void Show(OperationType type, const QStringList &files_with_errors); void Show(OperationType type, const QStringList &files_with_errors, const QStringList &log = QStringList());
private: private:
Ui_OrganiseErrorDialog *ui_; Ui_OrganiseErrorDialog *ui_;

View File

@@ -12,12 +12,12 @@
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="layout_left">
<item> <item>
<widget class="QLabel" name="icon"/> <widget class="QLabel" name="icon"/>
</item> </item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="spacer_left">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
@@ -32,12 +32,28 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="layout_centre">
<item> <item>
<widget class="QLabel" name="label"/> <widget class="QLabel" name="label"/>
</item> </item>
<item> <item>
<widget class="QListWidget" name="list"/> <widget class="QListWidget" name="files"/>
</item>
<item>
<widget class="QListWidget" name="log"/>
</item>
<item>
<spacer name="spacer_bottom">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">

View File

@@ -334,7 +334,8 @@ void ListenBrainzScrobbler::UpdateNowPlaying(const Song &song) {
if (!song.is_metadata_good()) return; if (!song.is_metadata_good()) return;
QString album = song.album(); QString album = song.album();
album = album.remove(Song::kCoverRemoveDisc); album = album.remove(Song::kAlbumRemoveDisc);
album = album.remove(Song::kAlbumRemoveMisc);
QJsonObject object_track_metadata; QJsonObject object_track_metadata;
object_track_metadata.insert("artist_name", QJsonValue::fromVariant(song.artist())); object_track_metadata.insert("artist_name", QJsonValue::fromVariant(song.artist()));

View File

@@ -182,9 +182,13 @@ ScrobblerCacheItem *ScrobblerCache::Add(const Song &song, const quint64 &timesta
if (scrobbler_cache_.contains(timestamp)) return nullptr; if (scrobbler_cache_.contains(timestamp)) return nullptr;
QString album = song.album(); QString album = song.album();
album = album.remove(Song::kCoverRemoveDisc); QString title = song.title();
ScrobblerCacheItem *item = new ScrobblerCacheItem(song.artist(), album, song.title(), song.albumartist(), song.track(), song.length_nanosec(), timestamp); album.remove(Song::kAlbumRemoveDisc);
album.remove(Song::kAlbumRemoveMisc);
title.remove(Song::kTitleRemoveMisc);
ScrobblerCacheItem *item = new ScrobblerCacheItem(song.artist(), album, title, song.albumartist(), song.track(), song.length_nanosec(), timestamp);
scrobbler_cache_.insert(timestamp, item); scrobbler_cache_.insert(timestamp, item);
if (loaded_) DoInAMinuteOrSo(this, SLOT(WriteCache())); if (loaded_) DoInAMinuteOrSo(this, SLOT(WriteCache()));

View File

@@ -384,7 +384,8 @@ void ScrobblingAPI20::UpdateNowPlaying(const Song &song) {
if (!IsAuthenticated() || !song.is_metadata_good()) return; if (!IsAuthenticated() || !song.is_metadata_good()) return;
QString album = song.album(); QString album = song.album();
album = album.remove(Song::kCoverRemoveDisc); album = album.remove(Song::kAlbumRemoveDisc);
album = album.remove(Song::kAlbumRemoveMisc);
ParamList params = ParamList() ParamList params = ParamList()
<< Param("method", "track.updateNowPlaying") << Param("method", "track.updateNowPlaying")

View File

@@ -70,8 +70,8 @@ GlobalShortcutsSettingsPage::GlobalShortcutsSettingsPage(SettingsDialog *dialog)
#if !defined(Q_OS_WIN) && !defined(Q_OS_MACOS) #if !defined(Q_OS_WIN) && !defined(Q_OS_MACOS)
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
connect(ui_->checkbox_dbus, SIGNAL(clicked(bool)), SLOT(DBusChanged(bool))); connect(ui_->checkbox_gsd, SIGNAL(clicked(bool)), SLOT(GSDChanged(bool)));
connect(ui_->button_dbus_open, SIGNAL(clicked()), SLOT(OpenGnomeKeybindingProperties())); connect(ui_->button_gsd_open, SIGNAL(clicked()), SLOT(OpenGnomeKeybindingProperties()));
#endif #endif
#ifdef HAVE_X11 #ifdef HAVE_X11
connect(ui_->checkbox_x11, SIGNAL(clicked(bool)), SLOT(X11Changed(bool))); connect(ui_->checkbox_x11, SIGNAL(clicked(bool)), SLOT(X11Changed(bool)));
@@ -102,15 +102,18 @@ void GlobalShortcutsSettingsPage::Load() {
if (!initialised_) { if (!initialised_) {
initialised_ = true; initialised_ = true;
de_ = Utilities::DesktopEnvironment();
ui_->widget_warning->hide();
connect(ui_->button_macos_open, SIGNAL(clicked()), manager, SLOT(ShowMacAccessibilityDialog())); connect(ui_->button_macos_open, SIGNAL(clicked()), manager, SLOT(ShowMacAccessibilityDialog()));
if (manager->IsGsdAvailable()) { if (manager->IsGsdAvailable()) {
qLog(Debug) << "Gnome D-Bus backend is available."; qLog(Debug) << "Gnome (GSD) D-Bus backend is available.";
ui_->widget_dbus->show(); ui_->widget_gsd->show();
} }
else { else {
qLog(Debug) << "Gnome D-Bus backend is unavailable."; qLog(Debug) << "Gnome (GSD) D-Bus backend is unavailable.";
ui_->widget_dbus->hide(); ui_->widget_gsd->hide();
} }
if (manager->IsX11Available()) { if (manager->IsX11Available()) {
@@ -140,9 +143,9 @@ void GlobalShortcutsSettingsPage::Load() {
SetShortcut(s.s.id, s.s.action->shortcut()); SetShortcut(s.s.id, s.s.action->shortcut());
} }
bool use_dbus = settings_.value("use_dbus", true).toBool(); bool use_gsd = settings_.value("use_gsd", true).toBool();
if (ui_->widget_dbus->isVisibleTo(this)) { if (ui_->widget_gsd->isVisibleTo(this)) {
ui_->checkbox_dbus->setChecked(use_dbus); ui_->checkbox_gsd->setChecked(use_gsd);
} }
bool use_x11 = settings_.value("use_x11", false).toBool(); bool use_x11 = settings_.value("use_x11", false).toBool();
@@ -152,7 +155,7 @@ void GlobalShortcutsSettingsPage::Load() {
#if !defined(Q_OS_WIN) && !defined(Q_OS_MACOS) #if !defined(Q_OS_WIN) && !defined(Q_OS_MACOS)
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
DBusChanged(true); GSDChanged(true);
#endif #endif
#ifdef HAVE_X11 #ifdef HAVE_X11
X11Changed(true); X11Changed(true);
@@ -176,7 +179,7 @@ void GlobalShortcutsSettingsPage::Save() {
settings_.setValue(s.s.id, s.key.toString()); settings_.setValue(s.s.id, s.key.toString());
} }
settings_.setValue("use_dbus", ui_->checkbox_dbus->isChecked()); settings_.setValue("use_gsd", ui_->checkbox_gsd->isChecked());
settings_.setValue("use_x11", ui_->checkbox_x11->isChecked()); settings_.setValue("use_x11", ui_->checkbox_x11->isChecked());
dialog()->global_shortcuts_manager()->ReloadSettings(); dialog()->global_shortcuts_manager()->ReloadSettings();
@@ -192,31 +195,31 @@ void GlobalShortcutsSettingsPage::X11Changed(bool) {
if (ui_->checkbox_x11->isChecked()) { if (ui_->checkbox_x11->isChecked()) {
ui_->list->setEnabled(true); ui_->list->setEnabled(true);
ui_->shortcut_options->setEnabled(true); ui_->shortcut_options->setEnabled(true);
X11Warning();
} }
else { else {
ui_->list->setEnabled(false); ui_->list->setEnabled(false);
ui_->shortcut_options->setEnabled(false); ui_->shortcut_options->setEnabled(false);
ui_->widget_warning->hide();
} }
} }
#endif // HAVE_X11 #endif // HAVE_X11
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
void GlobalShortcutsSettingsPage::DBusChanged(bool) { void GlobalShortcutsSettingsPage::GSDChanged(bool) {
if (!ui_->widget_dbus->isVisibleTo(this)) return; if (!ui_->widget_gsd->isVisibleTo(this)) return;
if (ui_->checkbox_dbus->isChecked()) { if (ui_->checkbox_gsd->isChecked()) {
if (ui_->checkbox_x11->isEnabled()) { if (ui_->checkbox_x11->isEnabled()) {
ui_->checkbox_x11->setEnabled(false); ui_->checkbox_x11->setEnabled(false);
ui_->list->setEnabled(false);
ui_->shortcut_options->setEnabled(false);
} }
if (ui_->checkbox_x11->isChecked()) { if (ui_->checkbox_x11->isChecked()) {
ui_->checkbox_x11->setChecked(false); ui_->checkbox_x11->setChecked(false);
ui_->list->setEnabled(false);
ui_->shortcut_options->setEnabled(false);
} }
ui_->list->setEnabled(false);
ui_->shortcut_options->setEnabled(false);
ui_->widget_warning->hide();
} }
else { else {
if (!ui_->checkbox_x11->isEnabled()) { if (!ui_->checkbox_x11->isEnabled()) {
@@ -224,6 +227,7 @@ void GlobalShortcutsSettingsPage::DBusChanged(bool) {
if (ui_->checkbox_x11->isChecked()) { if (ui_->checkbox_x11->isChecked()) {
ui_->list->setEnabled(true); ui_->list->setEnabled(true);
ui_->shortcut_options->setEnabled(true); ui_->shortcut_options->setEnabled(true);
X11Warning();
} }
} }
} }
@@ -296,3 +300,23 @@ void GlobalShortcutsSettingsPage::ChangeClicked() {
} }
void GlobalShortcutsSettingsPage::X11Warning() {
if (de_.toLower() == "kde" || de_.toLower() == "gnome" || de_.toLower() == "x-cinnamon") {
QString text(tr("Using X11 shortcuts on %1 is not recommended and can cause keyboard to become unresponsive!").arg(de_));
if (de_.toLower() == "kde")
text += tr(" Shortcuts on %1 are usually used through MPRIS D-Bus and should be configured in %1 settings instead.").arg(de_);
else if (de_.toLower() == "gnome")
text += tr(" Shortcuts on %1 are usually used through GSD D-Bus and should be configured in gnome-settings-daemon instead.").arg(de_);
else if (de_.toLower() == "x-cinnamon")
text += tr(" Shortcuts on %1 are usually used through GSD D-Bus and should be configured in cinnamon-settings-daemon instead.").arg(de_);
else
text += tr(" Shortcuts should be configured in %1 settings instead.").arg(de_);
ui_->label_warn_text->setText(text);
ui_->widget_warning->show();
}
else {
ui_->widget_warning->hide();
}
}

View File

@@ -62,7 +62,7 @@ class GlobalShortcutsSettingsPage : public SettingsPage {
void X11Changed(bool); void X11Changed(bool);
#endif #endif
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
void DBusChanged(bool); void GSDChanged(bool);
void OpenGnomeKeybindingProperties(); void OpenGnomeKeybindingProperties();
#endif #endif
#endif #endif
@@ -81,6 +81,8 @@ class GlobalShortcutsSettingsPage : public SettingsPage {
void SetShortcut(const QString &id, const QKeySequence &key); void SetShortcut(const QString &id, const QKeySequence &key);
void X11Warning();
private: private:
Ui_GlobalShortcutsSettingsPage *ui_; Ui_GlobalShortcutsSettingsPage *ui_;
@@ -91,6 +93,8 @@ class GlobalShortcutsSettingsPage : public SettingsPage {
QMap<QString, Shortcut> shortcuts_; QMap<QString, Shortcut> shortcuts_;
QString current_id_; QString current_id_;
QString de_;
}; };
#endif // GLOBALSHORTCUTSSETTINGSPAGE_H #endif // GLOBALSHORTCUTSSETTINGSPAGE_H

View File

@@ -19,7 +19,7 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QWidget" name="widget_dbus" native="true"> <widget class="QWidget" name="widget_gsd" native="true">
<layout class="QHBoxLayout" name="layout_gnome"> <layout class="QHBoxLayout" name="layout_gnome">
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>
@@ -34,7 +34,7 @@
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QCheckBox" name="checkbox_dbus"> <widget class="QCheckBox" name="checkbox_gsd">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@@ -42,12 +42,12 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>Use Gnome D-Bus shortcut keys</string> <string>Use Gnome (GSD) D-Bus shortcut keys</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="button_dbus_open"> <widget class="QPushButton" name="button_gsd_open">
<property name="text"> <property name="text">
<string>Open...</string> <string>Open...</string>
</property> </property>
@@ -90,6 +90,62 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QWidget" name="widget_warning" native="true">
<layout class="QHBoxLayout" name="layout_warning">
<item>
<widget class="QLabel" name="label_warn_icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="baseSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../../data/icons.qrc">:/icons/48x48/dialog-warning.png</pixmap>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_warn_text">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QWidget" name="widget_macos" native="true"> <widget class="QWidget" name="widget_macos" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">

View File

@@ -792,6 +792,9 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val
//qLog(Debug) << "id" << id << "track" << track << "disc" << disc << "title" << title << "album" << album << "artist" << artist << cover << allow_streaming << url; //qLog(Debug) << "id" << id << "track" << track << "disc" << disc << "title" << title << "album" << album << "artist" << artist << cover << allow_streaming << url;
title.remove(Song::kTitleRemoveMisc);
album.remove(Song::kAlbumRemoveMisc);
song.set_source(Song::Source_Tidal); song.set_source(Song::Source_Tidal);
song.set_id(song_id); song.set_id(song_id);
song.set_album_id(album_id); song.set_album_id(album_id);
@@ -911,7 +914,7 @@ void TidalService::Error(QString error, QVariant debug) {
CheckFinish(); CheckFinish();
} }
if (!stream_request_url_.isEmpty() && !login_sent_) { if (!stream_request_url_.isEmpty() && !login_sent_) {
emit StreamURLFinished(stream_request_url_, Song::FileType_Stream); emit StreamURLFinished(stream_request_url_, Song::FileType_Stream, error);
stream_request_url_ = QUrl(); stream_request_url_ = QUrl();
} }
} }

View File

@@ -73,7 +73,7 @@ class TidalService : public InternetService {
void ProgressSetMaximum(int max); void ProgressSetMaximum(int max);
void UpdateProgress(int max); void UpdateProgress(int max);
void GetStreamURLFinished(QNetworkReply *reply, const QUrl url); void GetStreamURLFinished(QNetworkReply *reply, const QUrl url);
void StreamURLFinished(const QUrl url, const Song::FileType); void StreamURLFinished(const QUrl url, const Song::FileType, QString error = QString());
public slots: public slots:
void ShowConfig(); void ShowConfig();

View File

@@ -33,7 +33,7 @@ TidalUrlHandler::TidalUrlHandler(
Application *app, TidalService *service) Application *app, TidalService *service)
: UrlHandler(service), app_(app), service_(service), task_id_(-1) { : UrlHandler(service), app_(app), service_(service), task_id_(-1) {
connect(service, SIGNAL(StreamURLFinished(QUrl, Song::FileType)), this, SLOT(GetStreamURLFinished(QUrl, Song::FileType))); connect(service, SIGNAL(StreamURLFinished(QUrl, Song::FileType, QString)), this, SLOT(GetStreamURLFinished(QUrl, Song::FileType, QString)));
} }
@@ -49,11 +49,14 @@ UrlHandler::LoadResult TidalUrlHandler::StartLoading(const QUrl &url) {
} }
void TidalUrlHandler::GetStreamURLFinished(QUrl url, Song::FileType filetype) { void TidalUrlHandler::GetStreamURLFinished(QUrl url, Song::FileType filetype, QString error) {
if (task_id_ == -1) return; if (task_id_ == -1) return;
CancelTask(); CancelTask();
emit AsyncLoadComplete(LoadResult(last_original_url_, LoadResult::TrackAvailable, url, filetype)); if (error.isEmpty())
emit AsyncLoadComplete(LoadResult(last_original_url_, LoadResult::TrackAvailable, url, filetype));
else
emit AsyncLoadComplete(LoadResult(last_original_url_, LoadResult::Error, url, filetype, -1, error));
} }

View File

@@ -43,7 +43,7 @@ class TidalUrlHandler : public UrlHandler {
void CancelTask(); void CancelTask();
private slots: private slots:
void GetStreamURLFinished(QUrl url, Song::FileType filetype); void GetStreamURLFinished(QUrl url, Song::FileType filetype, QString error = QString());
private: private:
Application *app_; Application *app_;

View File

@@ -303,7 +303,7 @@ void TranscodeDialog::SetFilenames(const QStringList &filenames) {
void TranscodeDialog::Remove() { qDeleteAll(ui_->files->selectedItems()); } void TranscodeDialog::Remove() { qDeleteAll(ui_->files->selectedItems()); }
void TranscodeDialog::LogLine(const QString &message) { void TranscodeDialog::LogLine(const QString message) {
QString date(QDateTime::currentDateTime().toString(Qt::TextDate)); QString date(QDateTime::currentDateTime().toString(Qt::TextDate));
log_ui_->log->appendPlainText(QString("%1: %2").arg(date, message)); log_ui_->log->appendPlainText(QString("%1: %2").arg(date, message));

View File

@@ -62,8 +62,8 @@ class TranscodeDialog : public QDialog {
void Start(); void Start();
void Cancel(); void Cancel();
void JobComplete(const QString &input, const QString &output, bool success); void JobComplete(const QString &input, const QString &output, bool success);
void LogLine(const QString &message);
void AllJobsComplete(); void AllJobsComplete();
void LogLine(const QString message);
void Options(); void Options();
void AddDestination(); void AddDestination();

View File

@@ -2,6 +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-2019, 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
@@ -30,9 +31,11 @@
#include <QtGlobal> #include <QtGlobal>
#include <QThread> #include <QThread>
#include <QCoreApplication> #include <QCoreApplication>
#include <QStandardPaths>
#include <QByteArray> #include <QByteArray>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QFileInfo>
#include <QList> #include <QList>
#include <QMap> #include <QMap>
#include <QVariant> #include <QVariant>
@@ -94,10 +97,10 @@ GstElement *Transcoder::CreateElementForMimeType(const QString &element_type, co
if (mime_type.isEmpty()) return nullptr; if (mime_type.isEmpty()) return nullptr;
// HACK: Force ffmux_mp4 because it doesn't set any useful src caps // HACK: Force mp4mux because it doesn't set any useful src caps
if (mime_type == "audio/mp4") { if (mime_type == "audio/mp4") {
LogLine(QString("Using '%1' (rank %2)").arg("ffmux_mp4").arg(-1)); emit LogLine(QString("Using '%1' (rank %2)").arg("mp4mux").arg(-1));
return CreateElement("ffmux_mp4", bin); return CreateElement("mp4mux", bin);
} }
// Keep track of all the suitable elements we find and figure out which is the best at the end. // Keep track of all the suitable elements we find and figure out which is the best at the end.
@@ -149,12 +152,12 @@ GstElement *Transcoder::CreateElementForMimeType(const QString &element_type, co
std::sort(suitable_elements_.begin(), suitable_elements_.end()); std::sort(suitable_elements_.begin(), suitable_elements_.end());
const SuitableElement &best = suitable_elements_.last(); const SuitableElement &best = suitable_elements_.last();
LogLine(QString("Using '%1' (rank %2)").arg(best.name_).arg(best.rank_)); emit LogLine(QString("Using '%1' (rank %2)").arg(best.name_).arg(best.rank_));
if (best.name_ == "lamemp3enc") { if (best.name_ == "lamemp3enc") {
// Special case: we need to add xingmux and id3v2mux to the pipeline when using lamemp3enc because it doesn't write the VBR or ID3v2 headers itself. // Special case: we need to add xingmux and id3v2mux to the pipeline when using lamemp3enc because it doesn't write the VBR or ID3v2 headers itself.
LogLine("Adding xingmux and id3v2mux to the pipeline"); emit LogLine("Adding xingmux and id3v2mux to the pipeline");
// Create the bin // Create the bin
GstElement *mp3bin = gst_bin_new("mp3bin"); GstElement *mp3bin = gst_bin_new("mp3bin");
@@ -217,7 +220,7 @@ Transcoder::Transcoder(QObject *parent, const QString &settings_postfix)
s.setValue("target", 1); // 1 == bitrate s.setValue("target", 1); // 1 == bitrate
} }
if (s.value("cbr").isNull()) { if (s.value("cbr").isNull()) {
s.setValue("cbr", true); s.setValue("cbr", false);
} }
} }
@@ -286,40 +289,46 @@ Song::FileType Transcoder::PickBestFormat(QList<Song::FileType> supported) {
} }
QString Transcoder::GetFile(const QString &input, const TranscoderPreset &preset, const QString output) {
QFileInfo fileinfo_output;
if (!output.isEmpty()) {
fileinfo_output.setFile(output);
}
if (!fileinfo_output.isFile() || fileinfo_output.filePath().isEmpty() || fileinfo_output.path().isEmpty() || fileinfo_output.fileName().isEmpty() || fileinfo_output.suffix().isEmpty()) {
QFileInfo fileinfo_input(input);
QString filename = fileinfo_input.baseName() + "." + preset.extension_;
fileinfo_output.setFile(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/transcoder/" + filename);
QDir dir;
dir.mkdir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/transcoder");
}
// Never overwrite existing files
if (fileinfo_output.exists()) {
QString path = fileinfo_output.path();
QString filename = fileinfo_output.baseName();
QString suffix = fileinfo_output.suffix();
for (int i = 0;; ++i) {
QString new_filename = QString("%1/%2-%3.%4").arg(path).arg(filename).arg(i).arg(suffix);
fileinfo_output.setFile(new_filename);
if (!fileinfo_output.exists()) {
break;
}
}
}
return fileinfo_output.filePath();
}
void Transcoder::AddJob(const QString &input, const TranscoderPreset &preset, const QString &output) { void Transcoder::AddJob(const QString &input, const TranscoderPreset &preset, const QString &output) {
Job job; Job job;
job.input = input; job.input = input;
job.preset = preset; job.preset = preset;
job.output = output;
// Use the supplied filename if there was one, otherwise take the file extension off the input filename and append the correct one.
if (!output.isEmpty())
job.output = output;
else
job.output = input.section('.', 0, -2) + '.' + preset.extension_;
// Never overwrite existing files
if (QFile::exists(job.output)) {
for (int i = 0;; ++i) {
QString new_filename = QString("%1.%2.%3").arg(job.output.section('.', 0, -2)).arg(i).arg( preset.extension_);
if (!QFile::exists(new_filename)) {
job.output = new_filename;
break;
}
}
}
queued_jobs_ << job;
}
void Transcoder::AddTemporaryJob(const QString &input, const TranscoderPreset &preset) {
Job job;
job.input = input;
job.output = Utilities::GetTemporaryFileName();
job.preset = preset;
queued_jobs_ << job; queued_jobs_ << job;
} }
@@ -387,6 +396,7 @@ GstBusSyncReply Transcoder::BusCallbackSync(GstBus*, GstMessage *msg, gpointer d
default: default:
break; break;
} }
return GST_BUS_PASS; return GST_BUS_PASS;
} }
@@ -429,12 +439,12 @@ bool Transcoder::StartJob(const Job &job) {
if (!src || !decode || !convert || !sink) return false; if (!src || !decode || !convert || !sink) return false;
if (!codec && !job.preset.codec_mimetype_.isEmpty()) { if (!codec && !job.preset.codec_mimetype_.isEmpty()) {
LogLine(tr("Couldn't find an encoder for %1, check you have the correct GStreamer plugins installed").arg(job.preset.codec_mimetype_)); emit LogLine(tr("Couldn't find an encoder for %1, check you have the correct GStreamer plugins installed").arg(job.preset.codec_mimetype_));
return false; return false;
} }
if (!muxer && !job.preset.muxer_mimetype_.isEmpty()) { if (!muxer && !job.preset.muxer_mimetype_.isEmpty()) {
LogLine(tr("Couldn't find a muxer for %1, check you have the correct GStreamer plugins installed").arg(job.preset.muxer_mimetype_)); emit LogLine(tr("Couldn't find a muxer for %1, check you have the correct GStreamer plugins installed").arg(job.preset.muxer_mimetype_));
return false; return false;
} }
@@ -571,7 +581,7 @@ void Transcoder::SetElementProperties(const QString &name, GObject *object) {
const QVariant value = s.value(property->name); const QVariant value = s.value(property->name);
if (value.isNull()) continue; if (value.isNull()) continue;
LogLine(QString("Setting %1 property: %2 = %3").arg(name, property->name, value.toString())); emit LogLine(QString("Setting %1 property: %2 = %3").arg(name, property->name, value.toString()));
switch (property->value_type) { switch (property->value_type) {
case G_TYPE_DOUBLE: case G_TYPE_DOUBLE:

View File

@@ -2,6 +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-2019, 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
@@ -65,8 +66,8 @@ class Transcoder : public QObject {
int max_threads() const { return max_threads_; } int max_threads() const { return max_threads_; }
void set_max_threads(int count) { max_threads_ = count; } void set_max_threads(int count) { max_threads_ = count; }
void AddJob(const QString &input, const TranscoderPreset &preset, const QString &output = QString()); QString GetFile(const QString &input, const TranscoderPreset &preset, const QString output = QString());
void AddTemporaryJob(const QString &input, const TranscoderPreset &preset); void AddJob(const QString &input, const TranscoderPreset &preset, const QString &output);
QMap<QString, float> GetProgress() const; QMap<QString, float> GetProgress() const;
int QueuedJobsCount() const { return queued_jobs_.count(); } int QueuedJobsCount() const { return queued_jobs_.count(); }
@@ -75,7 +76,7 @@ class Transcoder : public QObject {
void Start(); void Start();
void Cancel(); void Cancel();
signals: signals:
void JobComplete(const QString &input, const QString &output, bool success); void JobComplete(const QString &input, const QString &output, bool success);
void LogLine(const QString &message); void LogLine(const QString &message);
void AllJobsComplete(); void AllJobsComplete();

View File

@@ -64,7 +64,7 @@ void TranscoderOptionsMP3::Load() {
ui_->quality_spinbox->setValue(s.value("quality", 10).toFloat()); ui_->quality_spinbox->setValue(s.value("quality", 10).toFloat());
ui_->bitrate_slider->setValue(s.value("bitrate", 320).toInt()); ui_->bitrate_slider->setValue(s.value("bitrate", 320).toInt());
ui_->cbr->setChecked(s.value("cbr", true).toBool()); ui_->cbr->setChecked(s.value("cbr", false).toBool());
ui_->encoding_engine_quality->setCurrentIndex(s.value("encoding-engine-quality", 1).toInt()); ui_->encoding_engine_quality->setCurrentIndex(s.value("encoding-engine-quality", 1).toInt());
ui_->mono->setChecked(s.value("mono", false).toBool()); ui_->mono->setChecked(s.value("mono", false).toBool());