Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a8fe18b8e | ||
|
|
4599b4a9cc | ||
|
|
6ab6ab56dd | ||
|
|
59f5dc21ac | ||
|
|
8f316db49c | ||
|
|
4c2f28311c | ||
|
|
6ae022fbec | ||
|
|
cfbb0f8fc0 | ||
|
|
3ad2c2ad6a | ||
|
|
1ce553fb65 | ||
|
|
ec5ec83edc | ||
|
|
18087fd797 | ||
|
|
f90de75e3a | ||
|
|
3241815a11 | ||
|
|
db3f8d3b83 | ||
|
|
9983dc3138 | ||
|
|
ad141c165b | ||
|
|
93b252b9e7 | ||
|
|
42e245e334 | ||
|
|
cb844084e8 | ||
|
|
3483736490 | ||
|
|
119a75588e | ||
|
|
2f42242305 | ||
|
|
4990f44b10 | ||
|
|
85eba24167 | ||
|
|
ce4be75803 | ||
|
|
706529248a | ||
|
|
79f50f5343 | ||
|
|
7437c208ff | ||
|
|
b838893e88 | ||
|
|
41e2a75675 | ||
|
|
ad5e366aad | ||
|
|
b35c641df6 | ||
|
|
22c476d507 | ||
|
|
8f9b1d708b | ||
|
|
9ce8b60cd2 | ||
|
|
9401ad2ba3 | ||
|
|
128b6c7ce9 | ||
|
|
04d509f6eb | ||
|
|
c91cef3507 | ||
|
|
a9304a840f | ||
|
|
3ba500b589 |
45
.travis.yml
45
.travis.yml
@@ -12,30 +12,37 @@ compiler:
|
||||
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
|
||||
- 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 docker run --name build -itd strawberry-build /bin/bash ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build git clone https://github.com/jonaski/strawberry ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then git fetch --unshallow ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then git pull ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink python ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install glib pkgconfig protobuf protobuf-c qt ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install sqlite --with-fts ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install gstreamer gst-plugins-base ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install gst-plugins-good --with-flac ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install gst-plugins-bad gst-plugins-ugly gst-libav ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install chromaprint ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libcdio libmtp libimobiledevice libplist ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export Qt5_DIR=/usr/local/opt/qt5/lib/cmake ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
docker build -f Dockerfile -t strawberry-build .;
|
||||
docker run --name build -itd strawberry-build /bin/bash;
|
||||
docker exec build git clone https://github.com/jonaski/strawberry;
|
||||
fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
git fetch --unshallow;
|
||||
git pull;
|
||||
brew update;
|
||||
brew unlink python;
|
||||
brew install glib pkgconfig protobuf protobuf-c qt;
|
||||
brew install sqlite --with-fts;
|
||||
brew install gstreamer gst-plugins-base;
|
||||
brew install gst-plugins-good --with-flac;
|
||||
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:
|
||||
- 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
|
||||
script:
|
||||
- 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 sudo make install ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo ../dist/macos/macdeploy.py strawberry.app ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ../dist/macos/create-dmg.sh strawberry.app ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
make -j8;
|
||||
sudo make install;
|
||||
sudo ../dist/macos/macdeploy.py strawberry.app;
|
||||
../dist/macos/create-dmg.sh strawberry.app;
|
||||
fi
|
||||
after_success:
|
||||
- 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
|
||||
|
||||
@@ -55,6 +55,8 @@ SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
|
||||
{
|
||||
server = nullptr;
|
||||
socket = nullptr;
|
||||
memory = nullptr;
|
||||
instanceNumber = -1;
|
||||
}
|
||||
|
||||
SingleApplicationPrivate::~SingleApplicationPrivate()
|
||||
|
||||
@@ -55,6 +55,8 @@ SingleCoreApplicationPrivate::SingleCoreApplicationPrivate( SingleCoreApplicatio
|
||||
{
|
||||
server = nullptr;
|
||||
socket = nullptr;
|
||||
memory = nullptr;
|
||||
instanceNumber = -1;
|
||||
}
|
||||
|
||||
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate()
|
||||
|
||||
@@ -152,8 +152,8 @@ if(WIN32)
|
||||
endif()
|
||||
|
||||
if(X11_FOUND)
|
||||
find_path(KEYSYMDEF_H keysymdef.h PATHS ${X11_INCLUDE_DIR})
|
||||
find_path(XF86KEYSYM_H XF86keysym.h PATHS ${X11_INCLUDE_DIR})
|
||||
find_path(KEYSYMDEF_H NAMES "keysymdef.h" PATHS "${X11_INCLUDE_DIR}" PATH_SUFFIXES "X11")
|
||||
find_path(XF86KEYSYM_H NAMES "XF86keysym.h" PATHS "${XCB_INCLUDEDIR}" PATH_SUFFIXES "X11")
|
||||
if(KEYSYMDEF_H)
|
||||
set(HAVE_KEYSYMDEF_H ON)
|
||||
else()
|
||||
@@ -203,7 +203,13 @@ endif()
|
||||
|
||||
# SingleApplication
|
||||
pkg_check_modules(SINGLEAPPLICATION singleapplication)
|
||||
if (NOT SINGLEAPPLICATION_FOUND)
|
||||
pkg_check_modules(SINGLEAPPLICATION libsingleapplication)
|
||||
endif()
|
||||
pkg_check_modules(SINGLECOREAPPLICATION singlecoreapplication)
|
||||
if (NOT SINGLECOREAPPLICATION_FOUND)
|
||||
pkg_check_modules(SINGLECOREAPPLICATION libsinglecoreapplication)
|
||||
endif()
|
||||
if (SINGLEAPPLICATION_FOUND AND SINGLECOREAPPLICATION_FOUND)
|
||||
option(USE_SYSTEM_SINGLEAPPLICATION "Use system SingleApplication/SingleCoreApplication libraries" ON)
|
||||
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_LIBRARIES singleapplication)
|
||||
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
||||
unset(SINGLEAPPLICATION_LIBRARY_DIRS)
|
||||
unset(SINGLECOREAPPLICATION_LIBRARY_DIRS)
|
||||
endif(USE_SYSTEM_SINGLEAPPLICATION)
|
||||
|
||||
# Qocoa
|
||||
@@ -326,11 +334,11 @@ optional_component(LIBMTP ON "Devices: MTP support"
|
||||
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 "libplist" LIBPLIST_FOUND
|
||||
DEPENDS "libusbmuxd" LIBUSBMUXD_FOUND
|
||||
DEPENDS "iPod classic support" LIBGPOD_FOUND
|
||||
DEPENDS "libgpod" LIBGPOD_FOUND
|
||||
)
|
||||
|
||||
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")
|
||||
endif()
|
||||
|
||||
#if(IMOBILEDEVICE_FOUND AND PLIST_FOUND)
|
||||
#add_subdirectory(ext/gstafc)
|
||||
#endif(IMOBILEDEVICE_FOUND AND PLIST_FOUND)
|
||||
|
||||
# Set up definitions and paths
|
||||
|
||||
|
||||
17
Changelog
17
Changelog
@@ -2,6 +2,23 @@ Strawberry Music Player
|
||||
=======================
|
||||
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:
|
||||
|
||||
* Added scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||
|
||||
@@ -9,7 +9,7 @@ run zypper --non-interactive --gpg-auto-import-keys install \
|
||||
boost-devel protobuf-devel sqlite3-devel taglib-devel \
|
||||
gstreamer-devel gstreamer-plugins-base-devel libxine-devel vlc-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
|
||||
|
||||
run mkdir -p /usr/src/app
|
||||
|
||||
18
README.md
18
README.md
@@ -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/)
|
||||
* [DBus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||
* [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.
|
||||
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).
|
||||
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
|
||||
|
||||
### 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).
|
||||
|
||||
### :penguin: Packaging status
|
||||
|
||||
[](https://repology.org/metapackage/strawberry/versions)
|
||||
|
||||
### :computer: Screenshot
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set(STRAWBERRY_VERSION_MAJOR 0)
|
||||
set(STRAWBERRY_VERSION_MINOR 5)
|
||||
set(STRAWBERRY_VERSION_PATCH 1)
|
||||
set(STRAWBERRY_VERSION_PATCH 2)
|
||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||
|
||||
set(INCLUDE_GIT_REVISION OFF)
|
||||
|
||||
10
dist/debian/copyright
vendored
10
dist/debian/copyright
vendored
@@ -59,6 +59,8 @@ Files: src/core/main.h
|
||||
src/scrobbler/*
|
||||
src/tidal/*
|
||||
src/deezer/*
|
||||
transcoderoptionswavpack.cpp
|
||||
transcoderoptionswavpack.h
|
||||
Copyright: 2012-2014, 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
@@ -114,6 +116,14 @@ Files: src/core/main.cpp
|
||||
src/globalshortcuts/globalshortcuts.h
|
||||
src/settings/shortcutssettingspage.cpp
|
||||
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>
|
||||
2012-2014, 2017-2018 Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
2
dist/macos/macdeploy.py
vendored
2
dist/macos/macdeploy.py
vendored
@@ -100,6 +100,8 @@ GSTREAMER_PLUGINS = [
|
||||
'libgsttaglib.so',
|
||||
'libgstvorbis.so',
|
||||
'libgstisomp4.so',
|
||||
'libgstlame.so',
|
||||
'libgstlibav.so',
|
||||
|
||||
]
|
||||
|
||||
|
||||
1
dist/opensuse/strawberry.spec.in
vendored
1
dist/opensuse/strawberry.spec.in
vendored
@@ -18,7 +18,6 @@ BuildRequires: desktop-file-utils
|
||||
BuildRequires: appstream-glib
|
||||
BuildRequires: gcc-c++
|
||||
BuildRequires: hicolor-icon-theme
|
||||
BuildRequires: libQt5Gui-private-headers-devel
|
||||
BuildRequires: make
|
||||
BuildRequires: git
|
||||
BuildRequires: pkgconfig
|
||||
|
||||
@@ -527,8 +527,8 @@ if(HAVE_GLOBALSHORTCUTS)
|
||||
SOURCES globalshortcuts/globalshortcut-x11.cpp
|
||||
)
|
||||
optional_source(HAVE_DBUS
|
||||
SOURCES globalshortcuts/globalshortcutbackend-dbus.cpp
|
||||
HEADERS globalshortcuts/globalshortcutbackend-dbus.h
|
||||
SOURCES globalshortcuts/globalshortcutbackend-gsd.cpp
|
||||
HEADERS globalshortcuts/globalshortcutbackend-gsd.h
|
||||
)
|
||||
optional_source(WIN32
|
||||
SOURCES globalshortcuts/globalshortcut-win.cpp
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* 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 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
|
||||
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
|
||||
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
|
||||
if (job.remove_original_)
|
||||
return QFile::rename(src.absoluteFilePath(), dest.absoluteFilePath());
|
||||
else
|
||||
return QFile::copy(src.absoluteFilePath(), dest.absoluteFilePath());
|
||||
bool result(true);
|
||||
if (job.remove_original_) {
|
||||
result = QFile::rename(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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -43,4 +44,3 @@ private:
|
||||
};
|
||||
|
||||
#endif // FILESYSTEMMUSICSTORAGE_H
|
||||
|
||||
|
||||
@@ -1774,6 +1774,7 @@ void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByt
|
||||
options.Load(string_options);
|
||||
|
||||
if (options.is_empty()) {
|
||||
raise();
|
||||
show();
|
||||
activateWindow();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,4 @@
|
||||
|
||||
#include "musicstorage.h"
|
||||
|
||||
MusicStorage::MusicStorage()
|
||||
{
|
||||
}
|
||||
MusicStorage::MusicStorage() {}
|
||||
|
||||
@@ -61,6 +61,9 @@ class MusicStorage {
|
||||
bool overwrite_;
|
||||
bool mark_as_listened_;
|
||||
bool remove_original_;
|
||||
bool albumcover_;
|
||||
QString cover_source_;
|
||||
QString cover_dest_;
|
||||
ProgressFunction progress_;
|
||||
};
|
||||
|
||||
|
||||
@@ -235,6 +235,12 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
||||
if (item->Url() != result.original_url_) return;
|
||||
|
||||
switch (result.type_) {
|
||||
case UrlHandler::LoadResult::Error:
|
||||
loading_async_ = QUrl();
|
||||
EngineStateChanged(Engine::Error);
|
||||
FatalError();
|
||||
emit Error(result.error_);
|
||||
break;
|
||||
case UrlHandler::LoadResult::NoMoreTracks:
|
||||
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")) {
|
||||
UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url);
|
||||
switch (result.type_) {
|
||||
case UrlHandler::LoadResult::Error:
|
||||
loading_async_ = QUrl();
|
||||
EngineStateChanged(Engine::Error);
|
||||
FatalError();
|
||||
emit Error(result.error_);
|
||||
return;
|
||||
case UrlHandler::LoadResult::NoMoreTracks:
|
||||
return;
|
||||
|
||||
case UrlHandler::LoadResult::WillLoadAsynchronously:
|
||||
loading_async_ = url;
|
||||
return;
|
||||
|
||||
case UrlHandler::LoadResult::TrackAvailable:
|
||||
url = result.media_url_;
|
||||
break;
|
||||
|
||||
@@ -142,7 +142,9 @@ const QString Song::kFtsUpdateSpec = Utilities::Updateify(Song::kFtsColumns).joi
|
||||
const QString Song::kManuallyUnsetCover = "(unset)";
|
||||
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 {
|
||||
|
||||
@@ -820,7 +822,7 @@ void Song::InitFromFilePartial(const QString &filename) {
|
||||
void Song::InitArtManual() {
|
||||
|
||||
QString album2 = d->album_;
|
||||
album2.remove(Song::kCoverRemoveDisc);
|
||||
album2.remove(Song::kAlbumRemoveDisc);
|
||||
|
||||
//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->samplerate = d->samplerate_;
|
||||
//track->bithdepth = d->bithdepth_;
|
||||
|
||||
track->type1 = 0;
|
||||
track->type2 = d->filetype_ == FileType_MP4 ? 0 : 1;
|
||||
track->type1 = (d->filetype_ == FileType_MPEG ? 1 : 0);
|
||||
track->type2 = (d->filetype_ == FileType_MPEG ? 1 : 0);
|
||||
track->mediatype = 1; // Audio
|
||||
track->size = d->filesize_;
|
||||
track->time_modified = d->mtime_;
|
||||
|
||||
@@ -83,7 +83,9 @@ class Song {
|
||||
static const QString kManuallyUnsetCover;
|
||||
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 QString JoinSpec(const QString &table);
|
||||
|
||||
@@ -28,6 +28,6 @@
|
||||
#include "song.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) {}
|
||||
|
||||
@@ -52,9 +52,12 @@ class UrlHandler : public QObject {
|
||||
|
||||
// There was a track available. Its url is in media_url.
|
||||
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().
|
||||
// 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
|
||||
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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
QDir dir(path);
|
||||
@@ -700,6 +666,10 @@ QString FiddleFileExtension(const QString &filename, const QString &new_extensio
|
||||
return PathWithoutFilenameExtension(filename) + "." + new_extension;
|
||||
}
|
||||
|
||||
QString GetEnv(const QString &key) {
|
||||
return QString::fromLocal8Bit(qgetenv(key.toLocal8Bit()));
|
||||
}
|
||||
|
||||
void SetEnv(const char *key, const QString &value) {
|
||||
|
||||
#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
|
||||
|
||||
ScopedWCharArray::ScopedWCharArray(const QString &str)
|
||||
|
||||
@@ -61,8 +61,6 @@ quint64 FileSystemCapacity(const QString &path);
|
||||
quint64 FileSystemFreeSpace(const QString &path);
|
||||
|
||||
QString MakeTempDir(const QString template_name = QString());
|
||||
QString GetTemporaryFileName();
|
||||
QString SaveToTemporaryFile(const QByteArray &data);
|
||||
|
||||
bool RemoveRecursive(const QString &path);
|
||||
bool CopyRecursive(const QString &source, const QString &destination);
|
||||
@@ -122,6 +120,7 @@ QUrl GetRelativePathToStrawberryBin(const QUrl &url);
|
||||
QString PathWithoutFilenameExtension(const QString &filename);
|
||||
QString FiddleFileExtension(const QString &filename, const QString &new_extension);
|
||||
|
||||
QString GetEnv(const QString &key);
|
||||
void SetEnv(const char *key, const QString &value);
|
||||
void IncreaseFDLimit();
|
||||
void CheckPortable();
|
||||
@@ -153,7 +152,9 @@ QString GetRandomStringWithChars(const int len);
|
||||
QString GetRandomStringWithCharsAndNumbers(const int len);
|
||||
QString GetRandomString(const int len, const QString &UseCharacters);
|
||||
|
||||
}
|
||||
QString DesktopEnvironment();
|
||||
|
||||
} // namespace
|
||||
|
||||
class ScopedWCharArray {
|
||||
public:
|
||||
|
||||
@@ -180,7 +180,8 @@ QString AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
|
||||
QString AlbumCoverChoiceController::SearchForCover(Song *song) {
|
||||
|
||||
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
|
||||
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 album2(album);
|
||||
album2.remove(Song::kCoverRemoveDisc);
|
||||
album2.remove(Song::kAlbumRemoveDisc);
|
||||
|
||||
// Hash the artist and album into a filename for the image
|
||||
QString filename(Utilities::Sha1CoverHash(artist, album2).toHex() + ".jpg");
|
||||
|
||||
@@ -50,7 +50,8 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString
|
||||
CoverSearchRequest request;
|
||||
request.artist = artist;
|
||||
request.album = album;
|
||||
request.album.remove(Song::kCoverRemoveDisc);
|
||||
request.album.remove(Song::kAlbumRemoveDisc);
|
||||
request.album.remove(Song::kAlbumRemoveMisc);
|
||||
request.search = false;
|
||||
request.id = next_id_++;
|
||||
request.fetchall = fetchall;
|
||||
@@ -65,7 +66,8 @@ quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString
|
||||
CoverSearchRequest request;
|
||||
request.artist = artist;
|
||||
request.album = album;
|
||||
request.album.remove(Song::kCoverRemoveDisc);
|
||||
request.album.remove(Song::kAlbumRemoveDisc);
|
||||
request.album.remove(Song::kAlbumRemoveMisc);
|
||||
request.search = true;
|
||||
request.id = next_id_++;
|
||||
request.fetchall = false;
|
||||
|
||||
@@ -99,8 +99,18 @@ void ConnectedDevice::InitBackendDirectory(const QString &mount_point, bool firs
|
||||
|
||||
}
|
||||
|
||||
void ConnectedDevice::ConnectAsync() { emit ConnectFinished(unique_id_, true); }
|
||||
|
||||
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) {
|
||||
@@ -112,13 +122,27 @@ void ConnectedDevice::FinishDelete(bool) {
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
|
||||
@@ -50,6 +50,7 @@ class ConnectedDevice : public QObject,
|
||||
~ConnectedDevice();
|
||||
|
||||
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:
|
||||
// we can call this method to refresh device's state
|
||||
virtual void Refresh() {}
|
||||
@@ -71,6 +72,7 @@ class ConnectedDevice : public QObject,
|
||||
signals:
|
||||
void TaskStarted(int id);
|
||||
void SongCountUpdated(int count);
|
||||
void ConnectFinished(const QString& id, bool success);
|
||||
|
||||
protected:
|
||||
void InitBackendDirectory(const QString &mount_point, bool first_time, bool rewrite_path = true);
|
||||
|
||||
@@ -123,16 +123,11 @@ void DeviceInfo::LoadIcon(const QVariantList &icons, const QString &name_hint) {
|
||||
|
||||
QString hint = QString(icons.first().toString() + name_hint).toLower();
|
||||
|
||||
if (hint.contains("phone"))
|
||||
icon_name_ = "device-phone";
|
||||
else if (hint.contains("ipod") || hint.contains("apple"))
|
||||
icon_name_ = "device-ipod";
|
||||
else if ((hint.contains("usb")) && (hint.contains("reader")))
|
||||
icon_name_ = "device-usb-flash";
|
||||
else if (hint.contains("usb"))
|
||||
icon_name_ = "device-usb-drive";
|
||||
else
|
||||
icon_name_ = "device";
|
||||
if (hint.contains("phone")) icon_name_ = "device-phone";
|
||||
else if (hint.contains("ipod") || hint.contains("apple")) icon_name_ = "device-ipod";
|
||||
else if ((hint.contains("usb")) && (hint.contains("reader"))) icon_name_ = "device-usb-flash";
|
||||
else if (hint.contains("usb")) icon_name_ = "device-usb-drive";
|
||||
else icon_name_ = "device";
|
||||
|
||||
icon_ = IconLoader::Load(icon_name_);
|
||||
|
||||
|
||||
@@ -42,10 +42,7 @@
|
||||
#include "devicedatabasebackend.h"
|
||||
#include "devicelister.h"
|
||||
|
||||
class Application;
|
||||
class ConnectedDevice;
|
||||
class DeviceLister;
|
||||
class DeviceStateFilterModel;
|
||||
|
||||
// Devices can be in three different states:
|
||||
// 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".
|
||||
struct Backend {
|
||||
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
|
||||
QString unique_id_;
|
||||
|
||||
@@ -131,13 +131,16 @@ DeviceManager::DeviceManager(Application *app, QObject *parent)
|
||||
#if defined(Q_OS_MACOS) and defined(HAVE_LIBMTP)
|
||||
AddLister(new MacOsDeviceLister);
|
||||
#endif
|
||||
|
||||
AddDeviceClass<FilesystemDevice>();
|
||||
#ifdef HAVE_IMOBILEDEVICE
|
||||
AddLister(new iLister);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
|
||||
AddDeviceClass<CddaDevice>();
|
||||
#endif
|
||||
|
||||
AddDeviceClass<FilesystemDevice>();
|
||||
|
||||
#ifdef HAVE_LIBGPOD
|
||||
AddDeviceClass<GPodDevice>();
|
||||
#endif
|
||||
@@ -146,6 +149,10 @@ DeviceManager::DeviceManager(Application *app, QObject *parent)
|
||||
AddDeviceClass<MtpDevice>();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_IMOBILEDEVICE
|
||||
AddDeviceClass<AfcDevice>();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
DeviceManager::~DeviceManager() {
|
||||
@@ -157,6 +164,8 @@ DeviceManager::~DeviceManager() {
|
||||
|
||||
backend_->deleteLater();
|
||||
|
||||
delete root_;
|
||||
|
||||
}
|
||||
|
||||
void DeviceManager::LoadAllDevices() {
|
||||
@@ -165,20 +174,20 @@ void DeviceManager::LoadAllDevices() {
|
||||
|
||||
DeviceDatabaseBackend::DeviceList devices = backend_->GetAllDevices();
|
||||
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);
|
||||
beginInsertRows(QModelIndex(), devices_.count(), devices_.count());
|
||||
devices_ << info;
|
||||
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();
|
||||
|
||||
switch (role) {
|
||||
@@ -239,24 +248,22 @@ QVariant DeviceManager::data(const QModelIndex &index, int role) const {
|
||||
|
||||
case MusicStorage::Role_Storage:
|
||||
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();
|
||||
return QVariant::fromValue<std::shared_ptr<MusicStorage>>(info->device_);
|
||||
|
||||
case MusicStorage::Role_StorageForceConnect:
|
||||
if (!info->BestBackend()) return QVariant();
|
||||
if (!info->device_) {
|
||||
if (info->database_id_ == -1 && info->BestBackend() && !info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) {
|
||||
|
||||
if (info->BestBackend() && info->BestBackend()->lister_->AskForScan(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_)) {
|
||||
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);
|
||||
dialog->exec();
|
||||
|
||||
if (dialog->clickedButton() != connect) return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
const_cast<DeviceManager*>(this)->Connect(index.row());
|
||||
const_cast<DeviceManager*>(this)->Connect(info);
|
||||
}
|
||||
if (!info->device_) return QVariant();
|
||||
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 (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 (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_);
|
||||
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();
|
||||
|
||||
// Do we have this device already?
|
||||
int i = FindDeviceById(id);
|
||||
if (i != -1) {
|
||||
DeviceInfo *info = devices_[i];
|
||||
DeviceInfo *info = FindDeviceById(id);
|
||||
if (info) {
|
||||
for (int backend_index = 0 ; backend_index < info->backends_.count() ; ++backend_index) {
|
||||
if (info->backends_[backend_index].unique_id_ == id) {
|
||||
info->backends_[backend_index].lister_ = lister;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit dataChanged(index(i, 0), index(i, 0));
|
||||
QModelIndex idx = ItemToIndex(info);
|
||||
if (idx.isValid()) emit dataChanged(idx, idx);
|
||||
}
|
||||
else {
|
||||
// Check if we have another device with the same URL
|
||||
i = FindDeviceByUrl(lister->MakeDeviceUrls(id));
|
||||
if (i != -1) {
|
||||
info = FindDeviceByUrl(lister->MakeDeviceUrls(id));
|
||||
if (info) {
|
||||
// Add this device's lister to the existing device
|
||||
DeviceInfo *info = devices_[i];
|
||||
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.
|
||||
@@ -363,18 +368,17 @@ void DeviceManager::PhysicalDeviceAdded(const QString &id) {
|
||||
info->size_ = lister->DeviceCapacity(id);
|
||||
info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_);
|
||||
}
|
||||
|
||||
emit dataChanged(index(i, 0), index(i, 0));
|
||||
QModelIndex idx = ItemToIndex(info);
|
||||
if (idx.isValid()) emit dataChanged(idx, idx);
|
||||
}
|
||||
else {
|
||||
// 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->friendly_name_ = lister->MakeFriendlyName(id);
|
||||
info->size_ = lister->DeviceCapacity(id);
|
||||
info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_);
|
||||
|
||||
beginInsertRows(QModelIndex(), devices_.count(), devices_.count());
|
||||
devices_ << info;
|
||||
endInsertRows();
|
||||
}
|
||||
@@ -388,13 +392,11 @@ void DeviceManager::PhysicalDeviceRemoved(const QString &id) {
|
||||
|
||||
qLog(Info) << "Device removed:" << id;
|
||||
|
||||
int i = FindDeviceById(id);
|
||||
if (i == -1) {
|
||||
// Shouldn't happen
|
||||
return;
|
||||
}
|
||||
DeviceInfo *info = FindDeviceById(id);
|
||||
if (!info) return;
|
||||
|
||||
DeviceInfo *info = devices_[i];
|
||||
QModelIndex idx = ItemToIndex(info);
|
||||
if (!idx.isValid()) return;
|
||||
|
||||
if (info->database_id_ != -1) {
|
||||
// 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_) emit DeviceDisconnected(i);
|
||||
if (!info->device_) emit DeviceDisconnected(idx);
|
||||
|
||||
emit dataChanged(index(i, 0), index(i, 0));
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
else {
|
||||
// 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()) {
|
||||
beginRemoveRows(QModelIndex(), i, i);
|
||||
devices_.removeAt(i);
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
beginRemoveRows(ItemToIndex(root_), idx.row(), idx.row());
|
||||
devices_.removeAll(info);
|
||||
root_->Delete(info->row);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
@@ -442,29 +437,38 @@ void DeviceManager::PhysicalDeviceChanged(const QString &id) {
|
||||
DeviceLister *lister = qobject_cast<DeviceLister*>(sender());
|
||||
Q_UNUSED(lister);
|
||||
|
||||
int i = FindDeviceById(id);
|
||||
if (i == -1) {
|
||||
// Shouldn't happen
|
||||
return;
|
||||
}
|
||||
DeviceInfo *info = FindDeviceById(id);
|
||||
if (!info) return;
|
||||
|
||||
// TODO
|
||||
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectedDevice> DeviceManager::Connect(int row) {
|
||||
|
||||
DeviceInfo *info = devices_[row];
|
||||
if (info->device_) // Already connected
|
||||
return info->device_;
|
||||
std::shared_ptr<ConnectedDevice> DeviceManager::Connect(QModelIndex idx) {
|
||||
|
||||
std::shared_ptr<ConnectedDevice> ret;
|
||||
|
||||
if (!info->BestBackend()->lister_) // Not physically connected
|
||||
return ret;
|
||||
DeviceInfo *info = IndexToItem(idx);
|
||||
if (!info) return ret;
|
||||
|
||||
if (info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) {
|
||||
// Mount the device
|
||||
return Connect(info);
|
||||
|
||||
}
|
||||
|
||||
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_);
|
||||
return ret;
|
||||
}
|
||||
@@ -525,8 +529,10 @@ std::shared_ptr<ConnectedDevice> DeviceManager::Connect(int row) {
|
||||
Q_ARG(QUrl, device_url),
|
||||
Q_ARG(DeviceLister*, info->BestBackend()->lister_),
|
||||
Q_ARG(QString, info->BestBackend()->unique_id_),
|
||||
Q_ARG(DeviceManager*, this), Q_ARG(Application*, app_),
|
||||
Q_ARG(int, info->database_id_), Q_ARG(bool, first_time));
|
||||
Q_ARG(DeviceManager*, this),
|
||||
Q_ARG(Application*, app_),
|
||||
Q_ARG(int, info->database_id_),
|
||||
Q_ARG(bool, first_time));
|
||||
|
||||
ret.reset(static_cast<ConnectedDevice*>(instance));
|
||||
|
||||
@@ -541,91 +547,138 @@ std::shared_ptr<ConnectedDevice> DeviceManager::Connect(int row) {
|
||||
return 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(SongCountUpdated(int)), SLOT(DeviceSongCountUpdated(int)));
|
||||
|
||||
emit DeviceConnected(row);
|
||||
connect(info->device_.get(), SIGNAL(ConnectFinished(const QString&, bool)), SLOT(DeviceConnectFinished(const QString&, bool)));
|
||||
ret->ConnectAsync();
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectedDevice> DeviceManager::GetConnectedDevice(int row) const {
|
||||
return devices_[row]->device_;
|
||||
void DeviceManager::DeviceConnectFinished(const QString &id, bool success) {
|
||||
|
||||
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 {
|
||||
return devices_[row]->database_id_;
|
||||
DeviceInfo *DeviceManager::GetDevice(QModelIndex idx) const {
|
||||
|
||||
DeviceInfo *info = IndexToItem(idx);
|
||||
return info;
|
||||
|
||||
}
|
||||
|
||||
DeviceLister *DeviceManager::GetLister(int row) const {
|
||||
return devices_[row]->BestBackend()->lister_;
|
||||
std::shared_ptr<ConnectedDevice> DeviceManager::GetConnectedDevice(QModelIndex idx) const {
|
||||
|
||||
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];
|
||||
if (!info || !info->device_)
|
||||
return;
|
||||
std::shared_ptr<ConnectedDevice> ret;
|
||||
if (!info) return ret;
|
||||
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();
|
||||
emit DeviceDisconnected(row);
|
||||
|
||||
QModelIndex index = ItemToIndex(info);
|
||||
if (!index.isValid()) return;
|
||||
|
||||
emit dataChanged(index, index);
|
||||
emit DeviceDisconnected(idx);
|
||||
emit dataChanged(idx, idx);
|
||||
|
||||
}
|
||||
|
||||
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->device_) Disconnect(row);
|
||||
if (info->device_) Disconnect(idx);
|
||||
|
||||
backend_->RemoveDevice(info->database_id_);
|
||||
info->database_id_ = -1;
|
||||
|
||||
if (!info->BestBackend()->lister_) {
|
||||
// It's not attached any more so remove it from the list
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
devices_.removeAt(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()));
|
||||
}
|
||||
|
||||
if (!info->BestBackend() || (info->BestBackend() && !info->BestBackend()->lister_)) { // It's not attached any more so remove it from the list
|
||||
beginRemoveRows(ItemToIndex(root_), idx.row(), idx.row());
|
||||
devices_.removeAll(info);
|
||||
root_->Delete(info->row);
|
||||
endRemoveRows();
|
||||
}
|
||||
else {
|
||||
// It's still attached, set the name and icon back to what they were originally
|
||||
else { // It's still attached, set the name and icon back to what they were originally
|
||||
const QString id = info->BestBackend()->unique_id_;
|
||||
|
||||
info->friendly_name_ = info->BestBackend()->lister_->MakeFriendlyName(id);
|
||||
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->LoadIcon(QVariantList() << icon_name, friendly_name);
|
||||
info->transcode_mode_ = mode;
|
||||
info->transcode_format_ = format;
|
||||
|
||||
emit dataChanged(index(row, 0), index(row, 0));
|
||||
emit dataChanged(idx, idx);
|
||||
|
||||
if (info->database_id_ != -1)
|
||||
backend_->SetDeviceOptions(info->database_id_, friendly_name, icon_name, mode, format);
|
||||
@@ -659,42 +712,49 @@ void DeviceManager::TasksChanged() {
|
||||
for (const TaskManager::Task &task : tasks) {
|
||||
if (!active_tasks_.contains(task.id)) continue;
|
||||
|
||||
QPersistentModelIndex index = active_tasks_[task.id];
|
||||
if (!index.isValid()) continue;
|
||||
QPersistentModelIndex idx = active_tasks_[task.id];
|
||||
if (!idx.isValid()) continue;
|
||||
|
||||
DeviceInfo *info = IndexToItem(index);
|
||||
DeviceInfo *info = IndexToItem(idx);
|
||||
if (task.progress_max)
|
||||
info->task_percentage_ = float(task.progress) / task.progress_max * 100;
|
||||
else
|
||||
info->task_percentage_ = 0;
|
||||
|
||||
emit dataChanged(index, index);
|
||||
finished_tasks.removeAll(index);
|
||||
emit dataChanged(idx, idx);
|
||||
finished_tasks.removeAll(idx);
|
||||
|
||||
}
|
||||
|
||||
for (const QPersistentModelIndex &index : finished_tasks) {
|
||||
if (!index.isValid()) continue;
|
||||
for (const QPersistentModelIndex &idx : finished_tasks) {
|
||||
|
||||
if (!idx.isValid()) continue;
|
||||
|
||||
DeviceInfo *info = IndexToItem(idx);
|
||||
if (!info) continue;
|
||||
|
||||
DeviceInfo *info = devices_[index.row()];
|
||||
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) {
|
||||
Q_ASSERT(QMetaObject::invokeMethod(this, "Unmount", Q_ARG(int, row)));
|
||||
void DeviceManager::UnmountAsync(QModelIndex idx) {
|
||||
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->device_) Disconnect(row);
|
||||
if (info->device_) Disconnect(idx);
|
||||
|
||||
if (info->BestBackend()->lister_)
|
||||
info->BestBackend()->lister_->UnmountDevice(info->BestBackend()->unique_id_);
|
||||
@@ -706,13 +766,13 @@ void DeviceManager::DeviceSongCountUpdated(int count) {
|
||||
ConnectedDevice *device = qobject_cast<ConnectedDevice*>(sender());
|
||||
if (!device) return;
|
||||
|
||||
int row = FindDeviceById(device->unique_id());
|
||||
if (row == -1) return;
|
||||
DeviceInfo *info = FindDeviceById(device->unique_id());
|
||||
if (!info) return;
|
||||
|
||||
QModelIndex index = ItemToIndex(devices_[row]);
|
||||
if (!index.isValid()) return;
|
||||
QModelIndex idx = ItemToIndex(info);
|
||||
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;
|
||||
}
|
||||
|
||||
DeviceInfo *DeviceManager::ItemFromRow(int row) {
|
||||
return devices_[row];
|
||||
}
|
||||
|
||||
QString DeviceManager::DeviceNameByID(QString unique_id) {
|
||||
|
||||
int row = FindDeviceById(unique_id);
|
||||
if (row == -1) return QString();
|
||||
|
||||
DeviceInfo *info = devices_[row];
|
||||
DeviceInfo *info = FindDeviceById(unique_id);
|
||||
if (!info) return QString();
|
||||
|
||||
QModelIndex index = ItemToIndex(info);
|
||||
if (!index.isValid()) return QString();
|
||||
QModelIndex idx = ItemToIndex(info);
|
||||
if (!idx.isValid()) return QString();
|
||||
|
||||
return data(index, DeviceManager::Role_FriendlyName).toString();
|
||||
return data(idx, DeviceManager::Role_FriendlyName).toString();
|
||||
|
||||
}
|
||||
|
||||
@@ -88,32 +88,34 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
|
||||
DeviceStateFilterModel *connected_devices_model() const { return connected_devices_model_; }
|
||||
|
||||
// Get info about devices
|
||||
int GetDatabaseId(int row) const;
|
||||
DeviceLister *GetLister(int row) const;
|
||||
std::shared_ptr<ConnectedDevice> GetConnectedDevice(int row) const;
|
||||
int GetDatabaseId(QModelIndex idx) const;
|
||||
DeviceLister *GetLister(QModelIndex idx) 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);
|
||||
int FindDeviceById(const QString &id) const;
|
||||
int FindDeviceByUrl(const QList<QUrl> &url) const;
|
||||
DeviceInfo *FindDeviceById(const QString &id) const;
|
||||
DeviceInfo *FindDeviceByUrl(const QList<QUrl> &url) const;
|
||||
QString DeviceNameByID(QString unique_id);
|
||||
|
||||
// Actions on devices
|
||||
std::shared_ptr<ConnectedDevice> Connect(int row);
|
||||
void Disconnect(int row);
|
||||
void Forget(int row);
|
||||
void UnmountAsync(int row);
|
||||
std::shared_ptr<ConnectedDevice> Connect(DeviceInfo *info);
|
||||
std::shared_ptr<ConnectedDevice> Connect(QModelIndex idx);
|
||||
void Disconnect(QModelIndex idx);
|
||||
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
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
|
||||
public slots:
|
||||
void Unmount(int row);
|
||||
void Unmount(QModelIndex idx);
|
||||
|
||||
signals:
|
||||
void DeviceConnected(int row);
|
||||
void DeviceDisconnected(int row);
|
||||
void DeviceConnected(QModelIndex idx);
|
||||
void DeviceDisconnected(QModelIndex idx);
|
||||
|
||||
private slots:
|
||||
void PhysicalDeviceAdded(const QString &id);
|
||||
@@ -123,6 +125,7 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
|
||||
void TasksChanged();
|
||||
void DeviceSongCountUpdated(int count);
|
||||
void LoadAllDevices();
|
||||
void DeviceConnectFinished(const QString &id, bool success);
|
||||
|
||||
protected:
|
||||
void LazyPopulate(DeviceInfo *item) { LazyPopulate(item, true); }
|
||||
|
||||
@@ -89,7 +89,7 @@ void DeviceProperties::SetDeviceManager(DeviceManager *manager) {
|
||||
|
||||
}
|
||||
|
||||
void DeviceProperties::ShowDevice(int row) {
|
||||
void DeviceProperties::ShowDevice(QModelIndex idx) {
|
||||
|
||||
if (ui_->icon->count() == 0) {
|
||||
// Only load the icons the first time the dialog is shown
|
||||
@@ -117,7 +117,7 @@ void DeviceProperties::ShowDevice(int row) {
|
||||
#endif
|
||||
}
|
||||
|
||||
index_ = manager_->index(row, 0, QModelIndex());
|
||||
index_ = idx;
|
||||
|
||||
// Basic information
|
||||
ui_->name->setText(index_.data(DeviceManager::Role_FriendlyName).toString());
|
||||
@@ -160,7 +160,7 @@ void DeviceProperties::UpdateHardwareInfo() {
|
||||
|
||||
// Hardware information
|
||||
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);
|
||||
|
||||
// Remove empty items
|
||||
@@ -206,8 +206,8 @@ void DeviceProperties::UpdateHardwareInfo() {
|
||||
void DeviceProperties::UpdateFormats() {
|
||||
|
||||
QString id = index_.data(DeviceManager::Role_UniqueId).toString();
|
||||
DeviceLister *lister = manager_->GetLister(index_.row());
|
||||
std::shared_ptr<ConnectedDevice> device = manager_->GetConnectedDevice(index_.row());
|
||||
DeviceLister *lister = manager_->GetLister(index_);
|
||||
std::shared_ptr<ConnectedDevice> device = manager_->GetConnectedDevice(index_);
|
||||
|
||||
// Transcode mode
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class DeviceProperties : public QDialog {
|
||||
~DeviceProperties();
|
||||
|
||||
void SetDeviceManager(DeviceManager *manager);
|
||||
void ShowDevice(int row);
|
||||
void ShowDevice(QModelIndex idx);
|
||||
|
||||
public slots:
|
||||
void accept();
|
||||
|
||||
@@ -190,8 +190,8 @@ void DeviceView::SetApplication(Application *app) {
|
||||
Q_ASSERT(app_ == nullptr);
|
||||
app_ = app;
|
||||
|
||||
connect(app_->device_manager(), SIGNAL(DeviceConnected(int)), SLOT(DeviceConnected(int)));
|
||||
connect(app_->device_manager(), SIGNAL(DeviceDisconnected(int)), SLOT(DeviceDisconnected(int)));
|
||||
connect(app_->device_manager(), SIGNAL(DeviceConnected(QModelIndex)), SLOT(DeviceConnected(QModelIndex)));
|
||||
connect(app_->device_manager(), SIGNAL(DeviceDisconnected(QModelIndex)), SLOT(DeviceDisconnected(QModelIndex)));
|
||||
|
||||
sort_model_ = new QSortFilterProxyModel(this);
|
||||
sort_model_->setSourceModel(app_->device_manager());
|
||||
@@ -240,8 +240,8 @@ void DeviceView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
const QModelIndex collection_index = MapToCollection(menu_index_);
|
||||
|
||||
if (device_index.isValid()) {
|
||||
const bool is_plugged_in = app_->device_manager()->GetLister(device_index.row());
|
||||
const bool is_remembered = app_->device_manager()->GetDatabaseId(device_index.row()) != -1;
|
||||
const bool is_plugged_in = app_->device_manager()->GetLister(device_index);
|
||||
const bool is_remembered = app_->device_manager()->GetDatabaseId(device_index) != -1;
|
||||
|
||||
forget_action_->setEnabled(is_remembered);
|
||||
eject_action_->setEnabled(is_plugged_in);
|
||||
@@ -253,7 +253,7 @@ void DeviceView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
|
||||
bool is_filesystem_device = false;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -295,16 +295,14 @@ void DeviceView::Connect() {
|
||||
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;
|
||||
|
||||
DeviceInfo *info = app_->device_manager()->ItemFromRow(row);
|
||||
if (!info) return;
|
||||
QModelIndex index = app_->device_manager()->ItemToIndex(info);
|
||||
if (!index.isValid()) return;
|
||||
QModelIndex sort_idx = sort_model_->mapFromSource(index);
|
||||
QModelIndex sort_idx = sort_model_->mapFromSource(idx);
|
||||
if (!sort_idx.isValid()) return;
|
||||
|
||||
QSortFilterProxyModel *sort_model = new QSortFilterProxyModel(device->model());
|
||||
@@ -318,19 +316,16 @@ void DeviceView::DeviceConnected(int row) {
|
||||
|
||||
}
|
||||
|
||||
void DeviceView::DeviceDisconnected(int row) {
|
||||
DeviceInfo *info = app_->device_manager()->ItemFromRow(row);
|
||||
if (!info) return;
|
||||
QModelIndex index = app_->device_manager()->ItemToIndex(info);
|
||||
if (!index.isValid()) return;
|
||||
merged_model_->RemoveSubModel(sort_model_->mapFromSource(index));
|
||||
void DeviceView::DeviceDisconnected(QModelIndex idx) {
|
||||
if (!idx.isValid()) return;
|
||||
merged_model_->RemoveSubModel(sort_model_->mapFromSource(idx));
|
||||
}
|
||||
|
||||
void DeviceView::Forget() {
|
||||
|
||||
QModelIndex device_idx = MapToDevice(menu_index_);
|
||||
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(
|
||||
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."),
|
||||
@@ -341,12 +336,12 @@ void DeviceView::Forget() {
|
||||
if (dialog->clickedButton() != forget) return;
|
||||
}
|
||||
|
||||
app_->device_manager()->Forget(device_idx.row());
|
||||
app_->device_manager()->Forget(device_idx);
|
||||
|
||||
}
|
||||
|
||||
void DeviceView::Properties() {
|
||||
properties_dialog_->ShowDevice(MapToDevice(menu_index_).row());
|
||||
properties_dialog_->ShowDevice(MapToDevice(menu_index_));
|
||||
}
|
||||
|
||||
void DeviceView::mouseDoubleClickEvent(QMouseEvent *event) {
|
||||
@@ -356,7 +351,7 @@ void DeviceView::mouseDoubleClickEvent(QMouseEvent *event) {
|
||||
QModelIndex merged_index = indexAt(event->pos());
|
||||
QModelIndex device_index = MapToDevice(merged_index);
|
||||
if (device_index.isValid()) {
|
||||
if (!app_->device_manager()->GetConnectedDevice(device_index.row())) {
|
||||
if (!app_->device_manager()->GetConnectedDevice(device_index)) {
|
||||
menu_index_ = merged_index;
|
||||
Connect();
|
||||
}
|
||||
@@ -439,7 +434,7 @@ void DeviceView::Organise() {
|
||||
|
||||
void DeviceView::Unmount() {
|
||||
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) {
|
||||
|
||||
@@ -88,13 +88,13 @@ class DeviceView : public AutoExpandingTreeView {
|
||||
void Organise();
|
||||
void Delete();
|
||||
|
||||
void DeviceConnected(int row);
|
||||
void DeviceDisconnected(int row);
|
||||
void DeviceConnected(QModelIndex idx);
|
||||
void DeviceDisconnected(QModelIndex idx);
|
||||
|
||||
void DeleteFinished(const SongList &songs_with_errors);
|
||||
|
||||
// AutoExpandingTreeView
|
||||
bool CanRecursivelyExpand(const QModelIndex &index) const;
|
||||
bool CanRecursivelyExpand(const QModelIndex &idx) const;
|
||||
|
||||
private:
|
||||
QModelIndex MapToDevice(const QModelIndex &merged_model_index) const;
|
||||
|
||||
@@ -44,7 +44,6 @@ FilesystemDevice::FilesystemDevice(const QUrl &url, DeviceLister *lister, const
|
||||
watcher_->moveToThread(watcher_thread_);
|
||||
watcher_thread_->start(QThread::IdlePriority);
|
||||
|
||||
qLog(Debug) << __PRETTY_FUNCTION__ << unique_id;
|
||||
watcher_->set_device_name(manager->DeviceNameByID(unique_id));
|
||||
watcher_->set_backend(backend_);
|
||||
watcher_->set_task_manager(app_->task_manager());
|
||||
|
||||
@@ -59,11 +59,10 @@ bool GPodDevice::Init() {
|
||||
loader_ = new GPodLoader(url_.path(), app_->task_manager(), backend_, shared_from_this());
|
||||
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(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()));
|
||||
loader_thread_->start();
|
||||
|
||||
return true;
|
||||
|
||||
@@ -71,7 +70,13 @@ bool GPodDevice::Init() {
|
||||
|
||||
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_);
|
||||
db_ = db;
|
||||
@@ -87,8 +92,12 @@ void GPodDevice::LoadFinished(Itdb_iTunesDB *db) {
|
||||
loader_->deleteLater();
|
||||
loader_ = nullptr;
|
||||
|
||||
emit ConnectFinished(unique_id_, success);
|
||||
|
||||
}
|
||||
|
||||
void GPodDevice::LoaderError(const QString &message) { app_->AddError(message); }
|
||||
|
||||
bool GPodDevice::StartCopy(QList<Song::FileType> *supported_filetypes) {
|
||||
|
||||
{
|
||||
@@ -137,6 +146,29 @@ bool GPodDevice::CopyToStorage(const CopyJob &job) {
|
||||
|
||||
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
|
||||
GError *error = nullptr;
|
||||
itdb_cp_track_to_ipod(track, QDir::toNativeSeparators(job.source_).toLocal8Bit().constData(), &error);
|
||||
|
||||
@@ -46,19 +46,19 @@ class GPodLoader;
|
||||
|
||||
class GPodDevice : public ConnectedDevice, public virtual MusicStorage {
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void Error(const QString &message);
|
||||
|
||||
public:
|
||||
Q_INVOKABLE GPodDevice(
|
||||
const QUrl &url, DeviceLister *lister,
|
||||
const QString &unique_id, DeviceManager *manager,
|
||||
const QString &unique_id,
|
||||
DeviceManager *manager,
|
||||
Application *app,
|
||||
int database_id, bool first_time);
|
||||
int database_id,
|
||||
bool first_time);
|
||||
~GPodDevice();
|
||||
|
||||
bool Init();
|
||||
void ConnectAsync();
|
||||
|
||||
static QStringList url_schemes() { return QStringList() << "ipod"; }
|
||||
|
||||
@@ -73,7 +73,8 @@ class GPodDevice : public ConnectedDevice, public virtual MusicStorage {
|
||||
void FinishDelete(bool success);
|
||||
|
||||
protected slots:
|
||||
void LoadFinished(Itdb_iTunesDB *db);
|
||||
void LoadFinished(Itdb_iTunesDB *db, bool success);
|
||||
void LoaderError(const QString& message);
|
||||
|
||||
protected:
|
||||
Itdb_Track *AddTrackToITunesDb(const Song &metadata);
|
||||
|
||||
@@ -52,6 +52,17 @@ void GPodLoader::LoadDatabase() {
|
||||
int task_id = task_manager_->StartTask(tr("Loading iPod database"));
|
||||
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
|
||||
GError *error = nullptr;
|
||||
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;
|
||||
emit Error(QString::fromUtf8(error->message));
|
||||
g_error_free(error);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
emit Error(tr("An error occurred loading the iTunes database"));
|
||||
}
|
||||
|
||||
task_manager_->SetTaskFinished(task_id);
|
||||
return;
|
||||
return db;
|
||||
}
|
||||
|
||||
// Convert all the tracks from libgpod structs into Song classes
|
||||
@@ -91,10 +102,6 @@ void GPodLoader::LoadDatabase() {
|
||||
// Add the songs we've just loaded
|
||||
backend_->AddOrUpdateSongs(songs);
|
||||
|
||||
moveToThread(original_thread_);
|
||||
|
||||
task_manager_->SetTaskFinished(task_id);
|
||||
emit LoadFinished(db);
|
||||
return db;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,10 @@ class GPodLoader : public QObject {
|
||||
signals:
|
||||
void Error(const QString &message);
|
||||
void TaskStarted(int task_id);
|
||||
void LoadFinished(Itdb_iTunesDB *db);
|
||||
void LoadFinished(Itdb_iTunesDB *db, bool success);
|
||||
|
||||
private:
|
||||
Itdb_iTunesDB *TryLoad();
|
||||
|
||||
private:
|
||||
std::shared_ptr<ConnectedDevice> device_;
|
||||
|
||||
@@ -64,36 +64,39 @@ MtpDevice::~MtpDevice() {}
|
||||
bool MtpDevice::Init() {
|
||||
|
||||
InitBackendDirectory("/", first_time_, false);
|
||||
model_->Init();
|
||||
|
||||
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_);
|
||||
|
||||
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(LoadFinished()), SLOT(LoadFinished()));
|
||||
connect(loader_, SIGNAL(LoadFinished(bool)), SLOT(LoadFinished(bool)));
|
||||
connect(loader_thread_, SIGNAL(started()), loader_, SLOT(LoadDatabase()));
|
||||
|
||||
db_busy_.lock();
|
||||
loader_thread_->start();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void MtpDevice::LoadFinished() {
|
||||
void MtpDevice::ConnectAsync() {
|
||||
|
||||
db_busy_.lock();
|
||||
loader_thread_->start();
|
||||
|
||||
}
|
||||
|
||||
void MtpDevice::LoadFinished(bool success) {
|
||||
|
||||
loader_->deleteLater();
|
||||
loader_ = nullptr;
|
||||
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) {
|
||||
|
||||
// Ensure only one "organise files" can be active at any one time
|
||||
|
||||
@@ -54,6 +54,7 @@ class MtpDevice : public ConnectedDevice {
|
||||
static QStringList url_schemes() { return QStringList() << "mtp" << "gphoto2"; }
|
||||
|
||||
bool Init();
|
||||
void ConnectAsync();
|
||||
|
||||
bool GetSupportedFiletypes(QList<Song::FileType>* ret);
|
||||
int GetFreeSpace();
|
||||
@@ -68,7 +69,8 @@ class MtpDevice : public ConnectedDevice {
|
||||
void FinishDelete(bool success);
|
||||
|
||||
private slots:
|
||||
void LoadFinished();
|
||||
void LoadFinished(bool success);
|
||||
void LoaderError(const QString& message);
|
||||
|
||||
private:
|
||||
bool GetSupportedFiletypes(QList<Song::FileType> *ret, LIBMTP_mtpdevice_struct *device);
|
||||
|
||||
@@ -40,34 +40,35 @@ MtpLoader::MtpLoader(const QUrl &url, TaskManager *task_manager, CollectionBacke
|
||||
original_thread_ = thread();
|
||||
}
|
||||
|
||||
MtpLoader::~MtpLoader() {
|
||||
delete connection_;
|
||||
}
|
||||
MtpLoader::~MtpLoader() {}
|
||||
|
||||
bool MtpLoader::Init() {
|
||||
connection_ = new MtpConnection(url_);
|
||||
return connection_->is_valid();
|
||||
}
|
||||
bool MtpLoader::Init() { return true; }
|
||||
|
||||
void MtpLoader::LoadDatabase() {
|
||||
|
||||
int task_id = task_manager_->StartTask(tr("Loading MTP device"));
|
||||
emit TaskStarted(task_id);
|
||||
|
||||
TryLoad();
|
||||
bool success = TryLoad();
|
||||
|
||||
moveToThread(original_thread_);
|
||||
|
||||
task_manager_->SetTaskFinished(task_id);
|
||||
emit LoadFinished();
|
||||
emit LoadFinished(success);
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
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) {
|
||||
LIBMTP_track_t *track = tracks;
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class MtpLoader : public QObject {
|
||||
signals:
|
||||
void Error(const QString &message);
|
||||
void TaskStarted(int task_id);
|
||||
void LoadFinished();
|
||||
void LoadFinished(bool success);
|
||||
|
||||
private:
|
||||
bool TryLoad();
|
||||
|
||||
@@ -148,7 +148,7 @@ QString About::ContributorsHtml() const {
|
||||
}
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
@@ -35,20 +35,20 @@
|
||||
#include "core/logging.h"
|
||||
#include "globalshortcuts.h"
|
||||
#include "globalshortcutbackend.h"
|
||||
#include "globalshortcutbackend-dbus.h"
|
||||
#include "globalshortcutbackend-gsd.h"
|
||||
|
||||
const char *GlobalShortcutBackendDBus::kGsdService = "org.gnome.SettingsDaemon.MediaKeys";
|
||||
const char *GlobalShortcutBackendDBus::kGsdService2 = "org.gnome.SettingsDaemon";
|
||||
const char *GlobalShortcutBackendDBus::kGsdPath = "/org/gnome/SettingsDaemon/MediaKeys";
|
||||
const char *GlobalShortcutBackendGSD::kGsdService = "org.gnome.SettingsDaemon.MediaKeys";
|
||||
const char *GlobalShortcutBackendGSD::kGsdService2 = "org.gnome.SettingsDaemon";
|
||||
const char *GlobalShortcutBackendGSD::kGsdPath = "/org/gnome/SettingsDaemon/MediaKeys";
|
||||
|
||||
GlobalShortcutBackendDBus::GlobalShortcutBackendDBus(GlobalShortcuts *parent)
|
||||
GlobalShortcutBackendGSD::GlobalShortcutBackendGSD(GlobalShortcuts *parent)
|
||||
: GlobalShortcutBackend(parent),
|
||||
interface_(nullptr),
|
||||
is_connected_(false) {}
|
||||
|
||||
GlobalShortcutBackendDBus::~GlobalShortcutBackendDBus(){}
|
||||
GlobalShortcutBackendGSD::~GlobalShortcutBackendGSD(){}
|
||||
|
||||
bool GlobalShortcutBackendDBus::DoRegister() {
|
||||
bool GlobalShortcutBackendGSD::DoRegister() {
|
||||
|
||||
qLog(Debug) << "Registering";
|
||||
|
||||
@@ -75,7 +75,7 @@ bool GlobalShortcutBackendDBus::DoRegister() {
|
||||
|
||||
}
|
||||
|
||||
void GlobalShortcutBackendDBus::RegisterFinished(QDBusPendingCallWatcher *watcher) {
|
||||
void GlobalShortcutBackendGSD::RegisterFinished(QDBusPendingCallWatcher *watcher) {
|
||||
|
||||
QDBusMessage reply = watcher->reply();
|
||||
watcher->deleteLater();
|
||||
@@ -92,7 +92,7 @@ void GlobalShortcutBackendDBus::RegisterFinished(QDBusPendingCallWatcher *watche
|
||||
|
||||
}
|
||||
|
||||
void GlobalShortcutBackendDBus::DoUnregister() {
|
||||
void GlobalShortcutBackendGSD::DoUnregister() {
|
||||
|
||||
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 == "Stop") manager_->shortcuts()["stop"].action->trigger();
|
||||
if (key == "Next") manager_->shortcuts()["next_track"].action->trigger();
|
||||
@@ -18,8 +18,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GLOBALSHORTCUTBACKEND_DBUS_H
|
||||
#define GLOBALSHORTCUTBACKEND_DBUS_H
|
||||
#ifndef GLOBALSHORTCUTBACKEND_GSD_H
|
||||
#define GLOBALSHORTCUTBACKEND_GSD_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -38,12 +38,12 @@
|
||||
class GlobalShortcuts;
|
||||
class OrgGnomeSettingsDaemonMediaKeysInterface;
|
||||
|
||||
class GlobalShortcutBackendDBus : public GlobalShortcutBackend {
|
||||
class GlobalShortcutBackendGSD : public GlobalShortcutBackend {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GlobalShortcutBackendDBus(GlobalShortcuts *parent);
|
||||
~GlobalShortcutBackendDBus();
|
||||
explicit GlobalShortcutBackendGSD(GlobalShortcuts *parent);
|
||||
~GlobalShortcutBackendGSD();
|
||||
|
||||
static const char *kGsdService;
|
||||
static const char *kGsdService2;
|
||||
@@ -65,4 +65,4 @@ class GlobalShortcutBackendDBus : public GlobalShortcutBackend {
|
||||
|
||||
};
|
||||
|
||||
#endif // GLOBALSHORTCUTBACKEND_DBUS_H
|
||||
#endif // GLOBALSHORTCUTBACKEND_GSD_H
|
||||
@@ -37,7 +37,7 @@
|
||||
#include "globalshortcutbackend.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
# include "globalshortcutbackend-dbus.h"
|
||||
# include "globalshortcutbackend-gsd.h"
|
||||
#endif
|
||||
#if defined(HAVE_X11) || defined(Q_OS_WIN)
|
||||
# include "globalshortcutbackend-system.h"
|
||||
@@ -52,7 +52,7 @@ GlobalShortcuts::GlobalShortcuts(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
dbus_backend_(nullptr),
|
||||
system_backend_(nullptr),
|
||||
use_dbus_(true),
|
||||
use_gsd_(true),
|
||||
use_x11_(false)
|
||||
{
|
||||
|
||||
@@ -80,7 +80,7 @@ GlobalShortcuts::GlobalShortcuts(QWidget *parent)
|
||||
|
||||
// Create backends - these do the actual shortcut registration
|
||||
#ifdef HAVE_DBUS
|
||||
dbus_backend_ = new GlobalShortcutBackendDBus(this);
|
||||
dbus_backend_ = new GlobalShortcutBackendGSD(this);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_X11) || defined(Q_OS_WIN)
|
||||
@@ -97,7 +97,7 @@ GlobalShortcuts::GlobalShortcuts(QWidget *parent)
|
||||
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.
|
||||
use_dbus_ = settings_.value("use_dbus", true).toBool();
|
||||
use_gsd_ = settings_.value("use_gsd", true).toBool();
|
||||
use_x11_ = settings_.value("use_x11", true).toBool();
|
||||
|
||||
Unregister();
|
||||
@@ -134,7 +134,7 @@ GlobalShortcuts::Shortcut GlobalShortcuts::AddShortcut(const QString &id, const
|
||||
bool GlobalShortcuts::IsGsdAvailable() const {
|
||||
|
||||
#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
|
||||
return false;
|
||||
#endif
|
||||
@@ -152,7 +152,7 @@ bool GlobalShortcuts::IsX11Available() const {
|
||||
}
|
||||
|
||||
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
|
||||
if (use_x11_)
|
||||
#endif
|
||||
|
||||
@@ -97,7 +97,7 @@ class GlobalShortcuts : public QWidget {
|
||||
QMap<QString, Shortcut> shortcuts_;
|
||||
QSettings settings_;
|
||||
|
||||
bool use_dbus_;
|
||||
bool use_gsd_;
|
||||
bool use_x11_;
|
||||
};
|
||||
|
||||
|
||||
@@ -33,8 +33,6 @@
|
||||
#endif
|
||||
#ifdef HAVE_XF86KEYSYM_H
|
||||
# include <X11/XF86keysym.h>
|
||||
#else
|
||||
# warning "Missing X11/XF86keysym.h"
|
||||
#endif
|
||||
|
||||
namespace KeyMapperX11 {
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/song.h"
|
||||
#include "lyricsfetcher.h"
|
||||
#include "lyricsfetchersearch.h"
|
||||
|
||||
const int LyricsFetcher::kMaxConcurrentRequests = 5;
|
||||
const QRegExp LyricsFetcher::kRemoveNonAlpha("[^a-zA-Z0-9\\d\\s]");
|
||||
const QRegExp LyricsFetcher::kRemoveFromTitle(" ?-? ((\\(|\\[)?)(Remastered|Live) ?((\\)|\\])?)$");
|
||||
|
||||
LyricsFetcher::LyricsFetcher(LyricsProviders *lyrics_providers, QObject *parent)
|
||||
: QObject(parent),
|
||||
@@ -50,10 +50,10 @@ quint64 LyricsFetcher::Search(const QString &artist, const QString &album, const
|
||||
request.artist = artist;
|
||||
request.album = album;
|
||||
request.album.remove(kRemoveNonAlpha);
|
||||
request.album.remove(kRemoveFromTitle);
|
||||
request.album.remove(Song::kAlbumRemoveMisc);
|
||||
request.title = title;
|
||||
request.title.remove(kRemoveNonAlpha);
|
||||
request.title.remove(kRemoveFromTitle);
|
||||
request.title.remove(Song::kTitleRemoveMisc);
|
||||
request.id = next_id_++;
|
||||
AddRequest(request);
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ class LyricsFetcher : public QObject {
|
||||
|
||||
static const int kMaxConcurrentRequests;
|
||||
static const QRegExp kRemoveNonAlpha;
|
||||
static const QRegExp kRemoveFromTitle;
|
||||
|
||||
quint64 Search(const QString &artist, const QString &album, const QString &title);
|
||||
void Clear();
|
||||
|
||||
17
src/main.cpp
17
src/main.cpp
@@ -124,12 +124,23 @@ int main(int argc, char* 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.
|
||||
SingleCoreApplication a(argc, argv, true, SingleCoreApplication::Mode::User);
|
||||
SingleCoreApplication core_app(argc, argv, true, SingleCoreApplication::Mode::User);
|
||||
Utilities::CheckPortable();
|
||||
// Parse commandline options - need to do this before starting the full QApplication so it works without an X server
|
||||
if (!options.Parse()) return 1;
|
||||
logging::SetLevels(options.log_levels());
|
||||
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
|
||||
@@ -147,10 +158,12 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
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);
|
||||
if (a.isSecondary()) {
|
||||
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)) {
|
||||
main_exit_safe(0);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -28,12 +29,14 @@
|
||||
#include <QString>
|
||||
#include <QStringBuilder>
|
||||
#include <QUrl>
|
||||
#include <QStandardPaths>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/musicstorage.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "organise.h"
|
||||
#ifdef HAVE_GSTREAMER
|
||||
# include "transcoder/transcoder.h"
|
||||
@@ -48,7 +51,7 @@ const int Organise::kBatchSize = 10;
|
||||
const int Organise::kTranscodeProgressInterval = 500;
|
||||
#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),
|
||||
task_manager_(task_manager),
|
||||
#ifdef HAVE_GSTREAMER
|
||||
@@ -59,11 +62,9 @@ Organise::Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> dest
|
||||
copy_(copy),
|
||||
overwrite_(overwrite),
|
||||
mark_as_listened_(mark_as_listened),
|
||||
albumcover_(albumcover),
|
||||
eject_after_(eject_after),
|
||||
task_count_(songs_info.count()),
|
||||
#ifdef HAVE_GSTREAMER
|
||||
transcode_suffix_(1),
|
||||
#endif
|
||||
tasks_complete_(0),
|
||||
started_(false),
|
||||
task_id_(0),
|
||||
@@ -88,6 +89,7 @@ void Organise::Start() {
|
||||
connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles()));
|
||||
#ifdef HAVE_GSTREAMER
|
||||
connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), SLOT(FileTranscoded(QString, QString, bool)));
|
||||
connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString)));
|
||||
#endif
|
||||
|
||||
moveToThread(thread_);
|
||||
@@ -97,10 +99,6 @@ void Organise::Start() {
|
||||
void Organise::ProcessSomeFiles() {
|
||||
|
||||
if (!started_) {
|
||||
#ifdef HAVE_GSTREAMER
|
||||
transcode_temp_name_.open();
|
||||
#endif
|
||||
|
||||
if (!destination_->StartCopy(&supported_filetypes_)) {
|
||||
// Failed to start - mark everything as failed :(
|
||||
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_);
|
||||
|
||||
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
|
||||
moveToThread(original_thread_);
|
||||
@@ -151,6 +149,10 @@ void Organise::ProcessSomeFiles() {
|
||||
Song song = task.song_info_.song_;
|
||||
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
|
||||
// Maybe this file is one that's been transcoded already?
|
||||
if (!task.transcoded_filename_.isEmpty()) {
|
||||
@@ -174,12 +176,10 @@ void Organise::ProcessSomeFiles() {
|
||||
TranscoderPreset preset = Transcoder::PresetForFileType(dest_type);
|
||||
qLog(Debug) << "Transcoding with" << preset.name_;
|
||||
|
||||
// Get a temporary name for the transcoded file
|
||||
task.transcoded_filename_ = transcode_temp_name_.fileName() + "-" + QString::number(transcode_suffix_++);
|
||||
task.transcoded_filename_ = transcoder_->GetFile(task.song_info_.song_.url().toLocalFile(), preset);
|
||||
task.new_extension_ = preset.extension_;
|
||||
task.new_filetype_ = dest_type;
|
||||
tasks_transcoding_[task.song_info_.song_.url().toLocalFile()] = task;
|
||||
|
||||
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.
|
||||
@@ -197,12 +197,25 @@ void Organise::ProcessSomeFiles() {
|
||||
job.metadata_ = song;
|
||||
job.overwrite_ = overwrite_;
|
||||
job.mark_as_listened_ = mark_as_listened_;
|
||||
job.albumcover_ = albumcover_;
|
||||
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());
|
||||
|
||||
if (!destination_->CopyToStorage(job)) {
|
||||
files_with_errors_ << task.song_info_.song_.basefilename();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (job.mark_as_listened_) {
|
||||
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));
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -28,14 +29,14 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QTemporaryFile>
|
||||
#include <QBasicTimer>
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "organiseformat.h"
|
||||
@@ -59,7 +60,7 @@ class Organise : public QObject {
|
||||
};
|
||||
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;
|
||||
#ifdef HAVE_GSTREAMER
|
||||
@@ -68,8 +69,8 @@ class Organise : public QObject {
|
||||
|
||||
void Start();
|
||||
|
||||
signals:
|
||||
void Finished(const QStringList &files_with_errors);
|
||||
signals:
|
||||
void Finished(const QStringList &files_with_errors, QStringList);
|
||||
void FileCopied(int database_id);
|
||||
|
||||
protected:
|
||||
@@ -80,6 +81,7 @@ signals:
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void FileTranscoded(const QString &input, const QString &output, bool success);
|
||||
#endif
|
||||
void LogLine(const QString message);
|
||||
|
||||
private:
|
||||
void SetSongProgress(float progress, bool transcoded = false);
|
||||
@@ -115,13 +117,12 @@ signals:
|
||||
const bool copy_;
|
||||
const bool overwrite_;
|
||||
const bool mark_as_listened_;
|
||||
const bool albumcover_;
|
||||
const bool eject_after_;
|
||||
int task_count_;
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
QBasicTimer transcode_progress_timer_;
|
||||
QTemporaryFile transcode_temp_name_;
|
||||
int transcode_suffix_;
|
||||
#endif
|
||||
|
||||
QList<Task> tasks_pending_;
|
||||
@@ -136,6 +137,7 @@ signals:
|
||||
int current_copy_progress_;
|
||||
|
||||
QStringList files_with_errors_;
|
||||
QStringList log_;
|
||||
};
|
||||
|
||||
#endif // ORGANISE_H
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* 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_->overwrite->setChecked(false);
|
||||
ui_->mark_as_listened->setChecked(false);
|
||||
ui_->albumcover->setChecked(true);
|
||||
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_->replace_spaces->setChecked(s.value("replace_spaces", true).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_->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("overwrite", 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("eject_after", ui_->eject_after->isChecked());
|
||||
|
||||
@@ -382,19 +386,19 @@ void OrganiseDialog::accept() {
|
||||
|
||||
// It deletes itself when it's finished.
|
||||
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());
|
||||
connect(organise, SIGNAL(Finished(QStringList)), SLOT(OrganiseFinished(QStringList)));
|
||||
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, QStringList)), SLOT(OrganiseFinished(QStringList, QStringList)));
|
||||
connect(organise, SIGNAL(FileCopied(int)), this, SIGNAL(FileCopied(int)));
|
||||
organise->Start();
|
||||
|
||||
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;
|
||||
|
||||
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) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* 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 UpdatePreviews();
|
||||
|
||||
void OrganiseFinished(const QStringList &files_with_errors);
|
||||
void OrganiseFinished(const QStringList files_with_errors, const QStringList log);
|
||||
|
||||
private:
|
||||
SongList LoadSongsBlocking(const QStringList &filenames);
|
||||
|
||||
@@ -126,6 +126,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="albumcover">
|
||||
<property name="text">
|
||||
<string>Copy album cover artwork</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -52,17 +53,17 @@ OrganiseErrorDialog::~OrganiseErrorDialog() {
|
||||
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;
|
||||
for (const Song &song : songs_with_errors) {
|
||||
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;
|
||||
std::stable_sort(sorted_files.begin(), sorted_files.end());
|
||||
@@ -79,7 +80,8 @@ void OrganiseErrorDialog::Show(OperationType type, const QStringList &files_with
|
||||
break;
|
||||
}
|
||||
|
||||
ui_->list->addItems(sorted_files);
|
||||
ui_->files->addItems(sorted_files);
|
||||
ui_->log->addItems(log);
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -45,8 +46,8 @@ class OrganiseErrorDialog : public QDialog {
|
||||
Type_Delete,
|
||||
};
|
||||
|
||||
void Show(OperationType type, const SongList& songs_with_errors);
|
||||
void Show(OperationType type, const QStringList &files_with_errors);
|
||||
void Show(OperationType type, const SongList &songs_with_errors, const QStringList &log = QStringList());
|
||||
void Show(OperationType type, const QStringList &files_with_errors, const QStringList &log = QStringList());
|
||||
|
||||
private:
|
||||
Ui_OrganiseErrorDialog *ui_;
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<layout class="QVBoxLayout" name="layout_left">
|
||||
<item>
|
||||
<widget class="QLabel" name="icon"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<spacer name="spacer_left">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
@@ -32,12 +32,28 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QVBoxLayout" name="layout_centre">
|
||||
<item>
|
||||
<widget class="QLabel" name="label"/>
|
||||
</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>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
|
||||
@@ -334,7 +334,8 @@ void ListenBrainzScrobbler::UpdateNowPlaying(const Song &song) {
|
||||
if (!song.is_metadata_good()) return;
|
||||
|
||||
QString album = song.album();
|
||||
album = album.remove(Song::kCoverRemoveDisc);
|
||||
album = album.remove(Song::kAlbumRemoveDisc);
|
||||
album = album.remove(Song::kAlbumRemoveMisc);
|
||||
|
||||
QJsonObject object_track_metadata;
|
||||
object_track_metadata.insert("artist_name", QJsonValue::fromVariant(song.artist()));
|
||||
|
||||
@@ -182,9 +182,13 @@ ScrobblerCacheItem *ScrobblerCache::Add(const Song &song, const quint64 ×ta
|
||||
if (scrobbler_cache_.contains(timestamp)) return nullptr;
|
||||
|
||||
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);
|
||||
|
||||
if (loaded_) DoInAMinuteOrSo(this, SLOT(WriteCache()));
|
||||
|
||||
@@ -384,7 +384,8 @@ void ScrobblingAPI20::UpdateNowPlaying(const Song &song) {
|
||||
if (!IsAuthenticated() || !song.is_metadata_good()) return;
|
||||
|
||||
QString album = song.album();
|
||||
album = album.remove(Song::kCoverRemoveDisc);
|
||||
album = album.remove(Song::kAlbumRemoveDisc);
|
||||
album = album.remove(Song::kAlbumRemoveMisc);
|
||||
|
||||
ParamList params = ParamList()
|
||||
<< Param("method", "track.updateNowPlaying")
|
||||
|
||||
@@ -70,8 +70,8 @@ GlobalShortcutsSettingsPage::GlobalShortcutsSettingsPage(SettingsDialog *dialog)
|
||||
|
||||
#if !defined(Q_OS_WIN) && !defined(Q_OS_MACOS)
|
||||
#ifdef HAVE_DBUS
|
||||
connect(ui_->checkbox_dbus, SIGNAL(clicked(bool)), SLOT(DBusChanged(bool)));
|
||||
connect(ui_->button_dbus_open, SIGNAL(clicked()), SLOT(OpenGnomeKeybindingProperties()));
|
||||
connect(ui_->checkbox_gsd, SIGNAL(clicked(bool)), SLOT(GSDChanged(bool)));
|
||||
connect(ui_->button_gsd_open, SIGNAL(clicked()), SLOT(OpenGnomeKeybindingProperties()));
|
||||
#endif
|
||||
#ifdef HAVE_X11
|
||||
connect(ui_->checkbox_x11, SIGNAL(clicked(bool)), SLOT(X11Changed(bool)));
|
||||
@@ -102,15 +102,18 @@ void GlobalShortcutsSettingsPage::Load() {
|
||||
if (!initialised_) {
|
||||
initialised_ = true;
|
||||
|
||||
de_ = Utilities::DesktopEnvironment();
|
||||
ui_->widget_warning->hide();
|
||||
|
||||
connect(ui_->button_macos_open, SIGNAL(clicked()), manager, SLOT(ShowMacAccessibilityDialog()));
|
||||
|
||||
if (manager->IsGsdAvailable()) {
|
||||
qLog(Debug) << "Gnome D-Bus backend is available.";
|
||||
ui_->widget_dbus->show();
|
||||
qLog(Debug) << "Gnome (GSD) D-Bus backend is available.";
|
||||
ui_->widget_gsd->show();
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "Gnome D-Bus backend is unavailable.";
|
||||
ui_->widget_dbus->hide();
|
||||
qLog(Debug) << "Gnome (GSD) D-Bus backend is unavailable.";
|
||||
ui_->widget_gsd->hide();
|
||||
}
|
||||
|
||||
if (manager->IsX11Available()) {
|
||||
@@ -140,9 +143,9 @@ void GlobalShortcutsSettingsPage::Load() {
|
||||
SetShortcut(s.s.id, s.s.action->shortcut());
|
||||
}
|
||||
|
||||
bool use_dbus = settings_.value("use_dbus", true).toBool();
|
||||
if (ui_->widget_dbus->isVisibleTo(this)) {
|
||||
ui_->checkbox_dbus->setChecked(use_dbus);
|
||||
bool use_gsd = settings_.value("use_gsd", true).toBool();
|
||||
if (ui_->widget_gsd->isVisibleTo(this)) {
|
||||
ui_->checkbox_gsd->setChecked(use_gsd);
|
||||
}
|
||||
|
||||
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)
|
||||
#ifdef HAVE_DBUS
|
||||
DBusChanged(true);
|
||||
GSDChanged(true);
|
||||
#endif
|
||||
#ifdef HAVE_X11
|
||||
X11Changed(true);
|
||||
@@ -176,7 +179,7 @@ void GlobalShortcutsSettingsPage::Save() {
|
||||
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());
|
||||
|
||||
dialog()->global_shortcuts_manager()->ReloadSettings();
|
||||
@@ -192,31 +195,31 @@ void GlobalShortcutsSettingsPage::X11Changed(bool) {
|
||||
if (ui_->checkbox_x11->isChecked()) {
|
||||
ui_->list->setEnabled(true);
|
||||
ui_->shortcut_options->setEnabled(true);
|
||||
X11Warning();
|
||||
}
|
||||
else {
|
||||
ui_->list->setEnabled(false);
|
||||
ui_->shortcut_options->setEnabled(false);
|
||||
ui_->widget_warning->hide();
|
||||
}
|
||||
|
||||
}
|
||||
#endif // HAVE_X11
|
||||
#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()) {
|
||||
ui_->checkbox_x11->setEnabled(false);
|
||||
ui_->list->setEnabled(false);
|
||||
ui_->shortcut_options->setEnabled(false);
|
||||
}
|
||||
|
||||
if (ui_->checkbox_x11->isChecked()) {
|
||||
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 {
|
||||
if (!ui_->checkbox_x11->isEnabled()) {
|
||||
@@ -224,6 +227,7 @@ void GlobalShortcutsSettingsPage::DBusChanged(bool) {
|
||||
if (ui_->checkbox_x11->isChecked()) {
|
||||
ui_->list->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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class GlobalShortcutsSettingsPage : public SettingsPage {
|
||||
void X11Changed(bool);
|
||||
#endif
|
||||
#ifdef HAVE_DBUS
|
||||
void DBusChanged(bool);
|
||||
void GSDChanged(bool);
|
||||
void OpenGnomeKeybindingProperties();
|
||||
#endif
|
||||
#endif
|
||||
@@ -81,6 +81,8 @@ class GlobalShortcutsSettingsPage : public SettingsPage {
|
||||
|
||||
void SetShortcut(const QString &id, const QKeySequence &key);
|
||||
|
||||
void X11Warning();
|
||||
|
||||
private:
|
||||
Ui_GlobalShortcutsSettingsPage *ui_;
|
||||
|
||||
@@ -91,6 +93,8 @@ class GlobalShortcutsSettingsPage : public SettingsPage {
|
||||
QMap<QString, Shortcut> shortcuts_;
|
||||
|
||||
QString current_id_;
|
||||
QString de_;
|
||||
|
||||
};
|
||||
|
||||
#endif // GLOBALSHORTCUTSSETTINGSPAGE_H
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_dbus" native="true">
|
||||
<widget class="QWidget" name="widget_gsd" native="true">
|
||||
<layout class="QHBoxLayout" name="layout_gnome">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
@@ -34,7 +34,7 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkbox_dbus">
|
||||
<widget class="QCheckBox" name="checkbox_gsd">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -42,12 +42,12 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Gnome D-Bus shortcut keys</string>
|
||||
<string>Use Gnome (GSD) D-Bus shortcut keys</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_dbus_open">
|
||||
<widget class="QPushButton" name="button_gsd_open">
|
||||
<property name="text">
|
||||
<string>Open...</string>
|
||||
</property>
|
||||
@@ -90,6 +90,62 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
<widget class="QWidget" name="widget_macos" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
|
||||
@@ -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;
|
||||
|
||||
title.remove(Song::kTitleRemoveMisc);
|
||||
album.remove(Song::kAlbumRemoveMisc);
|
||||
|
||||
song.set_source(Song::Source_Tidal);
|
||||
song.set_id(song_id);
|
||||
song.set_album_id(album_id);
|
||||
@@ -911,7 +914,7 @@ void TidalService::Error(QString error, QVariant debug) {
|
||||
CheckFinish();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class TidalService : public InternetService {
|
||||
void ProgressSetMaximum(int max);
|
||||
void UpdateProgress(int max);
|
||||
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:
|
||||
void ShowConfig();
|
||||
|
||||
@@ -33,7 +33,7 @@ TidalUrlHandler::TidalUrlHandler(
|
||||
Application *app, TidalService *service)
|
||||
: 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;
|
||||
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));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class TidalUrlHandler : public UrlHandler {
|
||||
void CancelTask();
|
||||
|
||||
private slots:
|
||||
void GetStreamURLFinished(QUrl url, Song::FileType filetype);
|
||||
void GetStreamURLFinished(QUrl url, Song::FileType filetype, QString error = QString());
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
|
||||
@@ -303,7 +303,7 @@ void TranscodeDialog::SetFilenames(const QStringList &filenames) {
|
||||
|
||||
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));
|
||||
log_ui_->log->appendPlainText(QString("%1: %2").arg(date, message));
|
||||
|
||||
@@ -62,8 +62,8 @@ class TranscodeDialog : public QDialog {
|
||||
void Start();
|
||||
void Cancel();
|
||||
void JobComplete(const QString &input, const QString &output, bool success);
|
||||
void LogLine(const QString &message);
|
||||
void AllJobsComplete();
|
||||
void LogLine(const QString message);
|
||||
void Options();
|
||||
void AddDestination();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -30,9 +31,11 @@
|
||||
#include <QtGlobal>
|
||||
#include <QThread>
|
||||
#include <QCoreApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QVariant>
|
||||
@@ -94,10 +97,10 @@ GstElement *Transcoder::CreateElementForMimeType(const QString &element_type, co
|
||||
|
||||
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") {
|
||||
LogLine(QString("Using '%1' (rank %2)").arg("ffmux_mp4").arg(-1));
|
||||
return CreateElement("ffmux_mp4", bin);
|
||||
emit LogLine(QString("Using '%1' (rank %2)").arg("mp4mux").arg(-1));
|
||||
return CreateElement("mp4mux", bin);
|
||||
}
|
||||
|
||||
// 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());
|
||||
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") {
|
||||
// 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
|
||||
GstElement *mp3bin = gst_bin_new("mp3bin");
|
||||
@@ -217,7 +220,7 @@ Transcoder::Transcoder(QObject *parent, const QString &settings_postfix)
|
||||
s.setValue("target", 1); // 1 == bitrate
|
||||
}
|
||||
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) {
|
||||
|
||||
Job job;
|
||||
job.input = input;
|
||||
job.preset = preset;
|
||||
|
||||
// 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;
|
||||
|
||||
job.output = output;
|
||||
queued_jobs_ << job;
|
||||
|
||||
}
|
||||
@@ -387,6 +396,7 @@ GstBusSyncReply Transcoder::BusCallbackSync(GstBus*, GstMessage *msg, gpointer d
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return GST_BUS_PASS;
|
||||
|
||||
}
|
||||
@@ -429,12 +439,12 @@ bool Transcoder::StartJob(const Job &job) {
|
||||
if (!src || !decode || !convert || !sink) return false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -571,7 +581,7 @@ void Transcoder::SetElementProperties(const QString &name, GObject *object) {
|
||||
const QVariant value = s.value(property->name);
|
||||
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) {
|
||||
case G_TYPE_DOUBLE:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* 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_; }
|
||||
void set_max_threads(int count) { max_threads_ = count; }
|
||||
|
||||
void AddJob(const QString &input, const TranscoderPreset &preset, const QString &output = QString());
|
||||
void AddTemporaryJob(const QString &input, const TranscoderPreset &preset);
|
||||
QString GetFile(const QString &input, const TranscoderPreset &preset, const QString output = QString());
|
||||
void AddJob(const QString &input, const TranscoderPreset &preset, const QString &output);
|
||||
|
||||
QMap<QString, float> GetProgress() const;
|
||||
int QueuedJobsCount() const { return queued_jobs_.count(); }
|
||||
@@ -75,7 +76,7 @@ class Transcoder : public QObject {
|
||||
void Start();
|
||||
void Cancel();
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void JobComplete(const QString &input, const QString &output, bool success);
|
||||
void LogLine(const QString &message);
|
||||
void AllJobsComplete();
|
||||
|
||||
@@ -64,7 +64,7 @@ void TranscoderOptionsMP3::Load() {
|
||||
|
||||
ui_->quality_spinbox->setValue(s.value("quality", 10).toFloat());
|
||||
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_->mono->setChecked(s.value("mono", false).toBool());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user