Compare commits

..

42 Commits
0.5.1 ... 0.5.2

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

View File

@@ -12,30 +12,37 @@ compiler:
before_install:
- 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

View File

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

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -48,6 +48,14 @@ To build Strawberry from source you need the following installed on your system
* [ALSA library (linux)](https://www.alsa-project.org/)
* [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
[![Packaging status](https://repology.org/badge/vertical-allrepos/strawberry.svg)](https://repology.org/metapackage/strawberry/versions)
### :computer: Screenshot

View File

@@ -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
View File

@@ -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+

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

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

View File

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

View File

@@ -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_;
};

View File

@@ -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;

View File

@@ -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_;

View File

@@ -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);

View File

@@ -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) {}

View File

@@ -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.

View File

@@ -243,40 +243,6 @@ QString MakeTempDir(const QString template_name) {
}
QString GetTemporaryFileName() {
QString file;
{
QTemporaryFile tempfile;
// Do not delete the file, we want to do something with it
tempfile.setAutoRemove(false);
tempfile.open();
file = tempfile.fileName();
}
return file;
}
QString SaveToTemporaryFile(const QByteArray &data) {
QTemporaryFile tempfile;
tempfile.setAutoRemove(false);
if (!tempfile.open()) {
return QString();
}
if (tempfile.write(data) != data.size()) {
tempfile.remove();
return QString();
}
tempfile.close();
return tempfile.fileName();
}
bool RemoveRecursive(const QString &path) {
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)

View File

@@ -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:

View File

@@ -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");

View File

@@ -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;

View File

@@ -99,8 +99,18 @@ void ConnectedDevice::InitBackendDirectory(const QString &mount_point, bool firs
}
void ConnectedDevice::ConnectAsync() { emit ConnectFinished(unique_id_, true); }
void ConnectedDevice::Eject() {
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) {

View File

@@ -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);

View File

@@ -123,16 +123,11 @@ void DeviceInfo::LoadIcon(const QVariantList &icons, const QString &name_hint) {
QString hint = QString(icons.first().toString() + name_hint).toLower();
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_);

View File

@@ -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_;

View File

@@ -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();
}

View File

@@ -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); }

View File

@@ -89,7 +89,7 @@ void DeviceProperties::SetDeviceManager(DeviceManager *manager) {
}
void DeviceProperties::ShowDevice(int row) {
void DeviceProperties::ShowDevice(QModelIndex idx) {
if (ui_->icon->count() == 0) {
// 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) {

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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());

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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_;

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);

View File

@@ -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));
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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>

View File

@@ -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();
}

View File

@@ -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_;

View File

@@ -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">

View File

@@ -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()));

View File

@@ -182,9 +182,13 @@ ScrobblerCacheItem *ScrobblerCache::Add(const Song &song, const quint64 &timesta
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()));

View File

@@ -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")

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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">

View File

@@ -792,6 +792,9 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val
//qLog(Debug) << "id" << id << "track" << track << "disc" << disc << "title" << title << "album" << album << "artist" << artist << cover << allow_streaming << url;
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();
}
}

View File

@@ -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();

View File

@@ -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));
}

View File

@@ -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_;

View File

@@ -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));

View File

@@ -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();

View File

@@ -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:

View File

@@ -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();

View File

@@ -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());